github.com/kaisenlinux/docker.io@v0.0.0-20230510090727-ea55db55fac7/engine/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 "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 if len(msg.Line) == 0 { 170 return nil 171 } 172 173 level := gelf.LOG_INFO 174 if msg.Source == "stderr" { 175 level = gelf.LOG_ERR 176 } 177 178 m := gelf.Message{ 179 Version: "1.1", 180 Host: s.hostname, 181 Short: string(msg.Line), 182 TimeUnix: float64(msg.Timestamp.UnixNano()/int64(time.Millisecond)) / 1000.0, 183 Level: int32(level), 184 RawExtra: s.rawExtra, 185 } 186 logger.PutMessage(msg) 187 188 if err := s.writer.WriteMessage(&m); err != nil { 189 return fmt.Errorf("gelf: cannot send GELF message: %v", err) 190 } 191 return nil 192 } 193 194 func (s *gelfLogger) Close() error { 195 return s.writer.Close() 196 } 197 198 func (s *gelfLogger) Name() string { 199 return name 200 } 201 202 // ValidateLogOpt looks for gelf specific log option gelf-address. 203 func ValidateLogOpt(cfg map[string]string) error { 204 address, err := parseAddress(cfg["gelf-address"]) 205 if err != nil { 206 return err 207 } 208 209 for key, val := range cfg { 210 switch key { 211 case "gelf-address": 212 case "tag": 213 case "labels": 214 case "labels-regex": 215 case "env": 216 case "env-regex": 217 case "gelf-compression-level": 218 if address.Scheme != "udp" { 219 return fmt.Errorf("compression is only supported on UDP") 220 } 221 i, err := strconv.Atoi(val) 222 if err != nil || i < flate.DefaultCompression || i > flate.BestCompression { 223 return fmt.Errorf("unknown value %q for log opt %q for gelf log driver", val, key) 224 } 225 case "gelf-compression-type": 226 if address.Scheme != "udp" { 227 return fmt.Errorf("compression is only supported on UDP") 228 } 229 switch val { 230 case "gzip", "zlib", "none": 231 default: 232 return fmt.Errorf("unknown value %q for log opt %q for gelf log driver", val, key) 233 } 234 case "gelf-tcp-max-reconnect", "gelf-tcp-reconnect-delay": 235 if address.Scheme != "tcp" { 236 return fmt.Errorf("%q is only valid for TCP", key) 237 } 238 i, err := strconv.Atoi(val) 239 if err != nil || i < 0 { 240 return fmt.Errorf("%q must be a positive integer", key) 241 } 242 default: 243 return fmt.Errorf("unknown log opt %q for gelf log driver", key) 244 } 245 } 246 247 return nil 248 } 249 250 func parseAddress(address string) (*url.URL, error) { 251 if address == "" { 252 return nil, fmt.Errorf("gelf-address is a required parameter") 253 } 254 if !urlutil.IsTransportURL(address) { 255 return nil, fmt.Errorf("gelf-address should be in form proto://address, got %v", address) 256 } 257 url, err := url.Parse(address) 258 if err != nil { 259 return nil, err 260 } 261 262 // we support only udp 263 if url.Scheme != "udp" && url.Scheme != "tcp" { 264 return nil, fmt.Errorf("gelf: endpoint needs to be TCP or UDP") 265 } 266 267 // get host and port 268 if _, _, err = net.SplitHostPort(url.Host); err != nil { 269 return nil, fmt.Errorf("gelf: please provide gelf-address as proto://host:port") 270 } 271 272 return url, nil 273 }