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