github.com/lazyboychen7/engine@v17.12.1-ce-rc2+incompatible/daemon/logger/gelf/gelf.go (about) 1 // Package gelf provides the log driver for forwarding server logs to 2 // endpoints that support the Graylog Extended Log Format. 3 package gelf 4 5 import ( 6 "compress/flate" 7 "encoding/json" 8 "fmt" 9 "net" 10 "net/url" 11 "strconv" 12 "time" 13 14 "github.com/Graylog2/go-gelf/gelf" 15 "github.com/docker/docker/daemon/logger" 16 "github.com/docker/docker/daemon/logger/loggerutils" 17 "github.com/docker/docker/pkg/urlutil" 18 "github.com/sirupsen/logrus" 19 ) 20 21 const name = "gelf" 22 23 type gelfLogger struct { 24 writer gelf.Writer 25 info logger.Info 26 hostname string 27 rawExtra json.RawMessage 28 } 29 30 func init() { 31 if err := logger.RegisterLogDriver(name, New); err != nil { 32 logrus.Fatal(err) 33 } 34 if err := logger.RegisterLogOptValidator(name, ValidateLogOpt); err != nil { 35 logrus.Fatal(err) 36 } 37 } 38 39 // New creates a gelf logger using the configuration passed in on the 40 // context. The supported context configuration variable is gelf-address. 41 func New(info logger.Info) (logger.Logger, error) { 42 // parse gelf address 43 address, err := parseAddress(info.Config["gelf-address"]) 44 if err != nil { 45 return nil, err 46 } 47 48 // collect extra data for GELF message 49 hostname, err := info.Hostname() 50 if err != nil { 51 return nil, fmt.Errorf("gelf: cannot access hostname to set source field") 52 } 53 54 // parse log tag 55 tag, err := loggerutils.ParseLogTag(info, loggerutils.DefaultTemplate) 56 if err != nil { 57 return nil, err 58 } 59 60 extra := map[string]interface{}{ 61 "_container_id": info.ContainerID, 62 "_container_name": info.Name(), 63 "_image_id": info.ContainerImageID, 64 "_image_name": info.ContainerImageName, 65 "_command": info.Command(), 66 "_tag": tag, 67 "_created": info.ContainerCreated, 68 } 69 70 extraAttrs, err := info.ExtraAttributes(func(key string) string { 71 if key[0] == '_' { 72 return key 73 } 74 return "_" + key 75 }) 76 77 if err != nil { 78 return nil, err 79 } 80 81 for k, v := range extraAttrs { 82 extra[k] = v 83 } 84 85 rawExtra, err := json.Marshal(extra) 86 if err != nil { 87 return nil, err 88 } 89 90 var gelfWriter gelf.Writer 91 if address.Scheme == "udp" { 92 gelfWriter, err = newGELFUDPWriter(address.Host, info) 93 if err != nil { 94 return nil, err 95 } 96 } else if address.Scheme == "tcp" { 97 gelfWriter, err = newGELFTCPWriter(address.Host, info) 98 if err != nil { 99 return nil, err 100 } 101 } 102 103 return &gelfLogger{ 104 writer: gelfWriter, 105 info: info, 106 hostname: hostname, 107 rawExtra: rawExtra, 108 }, nil 109 } 110 111 // create new TCP gelfWriter 112 func newGELFTCPWriter(address string, info logger.Info) (gelf.Writer, error) { 113 gelfWriter, err := gelf.NewTCPWriter(address) 114 if err != nil { 115 return nil, fmt.Errorf("gelf: cannot connect to GELF endpoint: %s %v", address, err) 116 } 117 118 if v, ok := info.Config["gelf-tcp-max-reconnect"]; ok { 119 i, err := strconv.Atoi(v) 120 if err != nil || i < 0 { 121 return nil, fmt.Errorf("gelf-tcp-max-reconnect must be a positive integer") 122 } 123 gelfWriter.MaxReconnect = i 124 } 125 126 if v, ok := info.Config["gelf-tcp-reconnect-delay"]; ok { 127 i, err := strconv.Atoi(v) 128 if err != nil || i < 0 { 129 return nil, fmt.Errorf("gelf-tcp-reconnect-delay must be a positive integer") 130 } 131 gelfWriter.ReconnectDelay = time.Duration(i) 132 } 133 134 return gelfWriter, nil 135 } 136 137 // create new UDP gelfWriter 138 func newGELFUDPWriter(address string, info logger.Info) (gelf.Writer, error) { 139 gelfWriter, err := gelf.NewUDPWriter(address) 140 if err != nil { 141 return nil, fmt.Errorf("gelf: cannot connect to GELF endpoint: %s %v", address, err) 142 } 143 144 if v, ok := info.Config["gelf-compression-type"]; ok { 145 switch v { 146 case "gzip": 147 gelfWriter.CompressionType = gelf.CompressGzip 148 case "zlib": 149 gelfWriter.CompressionType = gelf.CompressZlib 150 case "none": 151 gelfWriter.CompressionType = gelf.CompressNone 152 default: 153 return nil, fmt.Errorf("gelf: invalid compression type %q", v) 154 } 155 } 156 157 if v, ok := info.Config["gelf-compression-level"]; ok { 158 val, err := strconv.Atoi(v) 159 if err != nil { 160 return nil, fmt.Errorf("gelf: invalid compression level %s, err %v", v, err) 161 } 162 gelfWriter.CompressionLevel = val 163 } 164 165 return gelfWriter, nil 166 } 167 168 func (s *gelfLogger) Log(msg *logger.Message) error { 169 level := gelf.LOG_INFO 170 if msg.Source == "stderr" { 171 level = gelf.LOG_ERR 172 } 173 174 m := gelf.Message{ 175 Version: "1.1", 176 Host: s.hostname, 177 Short: string(msg.Line), 178 TimeUnix: float64(msg.Timestamp.UnixNano()/int64(time.Millisecond)) / 1000.0, 179 Level: int32(level), 180 RawExtra: s.rawExtra, 181 } 182 logger.PutMessage(msg) 183 184 if err := s.writer.WriteMessage(&m); err != nil { 185 return fmt.Errorf("gelf: cannot send GELF message: %v", err) 186 } 187 return nil 188 } 189 190 func (s *gelfLogger) Close() error { 191 return s.writer.Close() 192 } 193 194 func (s *gelfLogger) Name() string { 195 return name 196 } 197 198 // ValidateLogOpt looks for gelf specific log option gelf-address. 199 func ValidateLogOpt(cfg map[string]string) error { 200 address, err := parseAddress(cfg["gelf-address"]) 201 if err != nil { 202 return err 203 } 204 205 for key, val := range cfg { 206 switch key { 207 case "gelf-address": 208 case "tag": 209 case "labels": 210 case "env": 211 case "env-regex": 212 case "gelf-compression-level": 213 if address.Scheme != "udp" { 214 return fmt.Errorf("compression is only supported on UDP") 215 } 216 i, err := strconv.Atoi(val) 217 if err != nil || i < flate.DefaultCompression || i > flate.BestCompression { 218 return fmt.Errorf("unknown value %q for log opt %q for gelf log driver", val, key) 219 } 220 case "gelf-compression-type": 221 if address.Scheme != "udp" { 222 return fmt.Errorf("compression is only supported on UDP") 223 } 224 switch val { 225 case "gzip", "zlib", "none": 226 default: 227 return fmt.Errorf("unknown value %q for log opt %q for gelf log driver", val, key) 228 } 229 case "gelf-tcp-max-reconnect", "gelf-tcp-reconnect-delay": 230 if address.Scheme != "tcp" { 231 return fmt.Errorf("%q is only valid for TCP", key) 232 } 233 i, err := strconv.Atoi(val) 234 if err != nil || i < 0 { 235 return fmt.Errorf("%q must be a positive integer", key) 236 } 237 default: 238 return fmt.Errorf("unknown log opt %q for gelf log driver", key) 239 } 240 } 241 242 return nil 243 } 244 245 func parseAddress(address string) (*url.URL, error) { 246 if address == "" { 247 return nil, fmt.Errorf("gelf-address is a required parameter") 248 } 249 if !urlutil.IsTransportURL(address) { 250 return nil, fmt.Errorf("gelf-address should be in form proto://address, got %v", address) 251 } 252 url, err := url.Parse(address) 253 if err != nil { 254 return nil, err 255 } 256 257 // we support only udp 258 if url.Scheme != "udp" && url.Scheme != "tcp" { 259 return nil, fmt.Errorf("gelf: endpoint needs to be TCP or UDP") 260 } 261 262 // get host and port 263 if _, _, err = net.SplitHostPort(url.Host); err != nil { 264 return nil, fmt.Errorf("gelf: please provide gelf-address as proto://host:port") 265 } 266 267 return url, nil 268 }