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