github.com/mheon/docker@v0.11.2-0.20150922122814-44f47903a831/daemon/logger/gelf/gelf.go (about)

     1  // +build linux
     2  
     3  // Package gelf provides the log driver for forwarding server logs to
     4  // endpoints that support the Graylog Extended Log Format.
     5  package gelf
     6  
     7  import (
     8  	"bytes"
     9  	"fmt"
    10  	"net"
    11  	"net/url"
    12  	"time"
    13  
    14  	"github.com/Graylog2/go-gelf/gelf"
    15  	"github.com/Sirupsen/logrus"
    16  	"github.com/docker/docker/daemon/logger"
    17  	"github.com/docker/docker/daemon/logger/loggerutils"
    18  	"github.com/docker/docker/pkg/urlutil"
    19  )
    20  
    21  const name = "gelf"
    22  
    23  type gelfLogger struct {
    24  	writer *gelf.Writer
    25  	ctx    logger.Context
    26  	fields gelfFields
    27  }
    28  
    29  type gelfFields struct {
    30  	hostname      string
    31  	containerID   string
    32  	containerName string
    33  	imageID       string
    34  	imageName     string
    35  	command       string
    36  	tag           string
    37  	created       time.Time
    38  }
    39  
    40  func init() {
    41  	if err := logger.RegisterLogDriver(name, New); err != nil {
    42  		logrus.Fatal(err)
    43  	}
    44  	if err := logger.RegisterLogOptValidator(name, ValidateLogOpt); err != nil {
    45  		logrus.Fatal(err)
    46  	}
    47  }
    48  
    49  // New creates a gelf logger using the configuration passed in on the
    50  // context. Supported context configuration variables are
    51  // gelf-address, & gelf-tag.
    52  func New(ctx logger.Context) (logger.Logger, error) {
    53  	// parse gelf address
    54  	address, err := parseAddress(ctx.Config["gelf-address"])
    55  	if err != nil {
    56  		return nil, err
    57  	}
    58  
    59  	// collect extra data for GELF message
    60  	hostname, err := ctx.Hostname()
    61  	if err != nil {
    62  		return nil, fmt.Errorf("gelf: cannot access hostname to set source field")
    63  	}
    64  
    65  	// remove trailing slash from container name
    66  	containerName := bytes.TrimLeft([]byte(ctx.ContainerName), "/")
    67  
    68  	// parse log tag
    69  	tag, err := loggerutils.ParseLogTag(ctx, "")
    70  	if err != nil {
    71  		return nil, err
    72  	}
    73  
    74  	fields := gelfFields{
    75  		hostname:      hostname,
    76  		containerID:   ctx.ContainerID,
    77  		containerName: string(containerName),
    78  		imageID:       ctx.ContainerImageID,
    79  		imageName:     ctx.ContainerImageName,
    80  		command:       ctx.Command(),
    81  		tag:           tag,
    82  		created:       ctx.ContainerCreated,
    83  	}
    84  
    85  	// create new gelfWriter
    86  	gelfWriter, err := gelf.NewWriter(address)
    87  	if err != nil {
    88  		return nil, fmt.Errorf("gelf: cannot connect to GELF endpoint: %s %v", address, err)
    89  	}
    90  
    91  	return &gelfLogger{
    92  		writer: gelfWriter,
    93  		ctx:    ctx,
    94  		fields: fields,
    95  	}, nil
    96  }
    97  
    98  func (s *gelfLogger) Log(msg *logger.Message) error {
    99  	// remove trailing and leading whitespace
   100  	short := bytes.TrimSpace([]byte(msg.Line))
   101  
   102  	level := gelf.LOG_INFO
   103  	if msg.Source == "stderr" {
   104  		level = gelf.LOG_ERR
   105  	}
   106  
   107  	m := gelf.Message{
   108  		Version:  "1.1",
   109  		Host:     s.fields.hostname,
   110  		Short:    string(short),
   111  		TimeUnix: float64(msg.Timestamp.UnixNano()/int64(time.Millisecond)) / 1000.0,
   112  		Level:    level,
   113  		Extra: map[string]interface{}{
   114  			"_container_id":   s.fields.containerID,
   115  			"_container_name": s.fields.containerName,
   116  			"_image_id":       s.fields.imageID,
   117  			"_image_name":     s.fields.imageName,
   118  			"_command":        s.fields.command,
   119  			"_tag":            s.fields.tag,
   120  			"_created":        s.fields.created,
   121  		},
   122  	}
   123  
   124  	if err := s.writer.WriteMessage(&m); err != nil {
   125  		return fmt.Errorf("gelf: cannot send GELF message: %v", err)
   126  	}
   127  	return nil
   128  }
   129  
   130  func (s *gelfLogger) Close() error {
   131  	return s.writer.Close()
   132  }
   133  
   134  func (s *gelfLogger) Name() string {
   135  	return name
   136  }
   137  
   138  // ValidateLogOpt looks for gelf specific log options gelf-address, &
   139  // gelf-tag.
   140  func ValidateLogOpt(cfg map[string]string) error {
   141  	for key := range cfg {
   142  		switch key {
   143  		case "gelf-address":
   144  		case "gelf-tag":
   145  		case "tag":
   146  		default:
   147  			return fmt.Errorf("unknown log opt '%s' for gelf log driver", key)
   148  		}
   149  	}
   150  
   151  	if _, err := parseAddress(cfg["gelf-address"]); err != nil {
   152  		return err
   153  	}
   154  
   155  	return nil
   156  }
   157  
   158  func parseAddress(address string) (string, error) {
   159  	if address == "" {
   160  		return "", nil
   161  	}
   162  	if !urlutil.IsTransportURL(address) {
   163  		return "", fmt.Errorf("gelf-address should be in form proto://address, got %v", address)
   164  	}
   165  	url, err := url.Parse(address)
   166  	if err != nil {
   167  		return "", err
   168  	}
   169  
   170  	// we support only udp
   171  	if url.Scheme != "udp" {
   172  		return "", fmt.Errorf("gelf: endpoint needs to be UDP")
   173  	}
   174  
   175  	// get host and port
   176  	if _, _, err = net.SplitHostPort(url.Host); err != nil {
   177  		return "", fmt.Errorf("gelf: please provide gelf-address as udp://host:port")
   178  	}
   179  
   180  	return url.Host, nil
   181  }