github.com/sijibomii/docker@v0.0.0-20231230191044-5cf6ca554647/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 "compress/flate" 10 "encoding/json" 11 "fmt" 12 "net" 13 "net/url" 14 "strconv" 15 "time" 16 17 "github.com/Graylog2/go-gelf/gelf" 18 "github.com/Sirupsen/logrus" 19 "github.com/docker/docker/daemon/logger" 20 "github.com/docker/docker/daemon/logger/loggerutils" 21 "github.com/docker/docker/pkg/urlutil" 22 ) 23 24 const name = "gelf" 25 26 type gelfLogger struct { 27 writer *gelf.Writer 28 ctx logger.Context 29 hostname string 30 rawExtra json.RawMessage 31 } 32 33 func init() { 34 if err := logger.RegisterLogDriver(name, New); err != nil { 35 logrus.Fatal(err) 36 } 37 if err := logger.RegisterLogOptValidator(name, ValidateLogOpt); err != nil { 38 logrus.Fatal(err) 39 } 40 } 41 42 // New creates a gelf logger using the configuration passed in on the 43 // context. Supported context configuration variables are 44 // gelf-address, & gelf-tag. 45 func New(ctx logger.Context) (logger.Logger, error) { 46 // parse gelf address 47 address, err := parseAddress(ctx.Config["gelf-address"]) 48 if err != nil { 49 return nil, err 50 } 51 52 // collect extra data for GELF message 53 hostname, err := ctx.Hostname() 54 if err != nil { 55 return nil, fmt.Errorf("gelf: cannot access hostname to set source field") 56 } 57 58 // remove trailing slash from container name 59 containerName := bytes.TrimLeft([]byte(ctx.ContainerName), "/") 60 61 // parse log tag 62 tag, err := loggerutils.ParseLogTag(ctx, "") 63 if err != nil { 64 return nil, err 65 } 66 67 extra := map[string]interface{}{ 68 "_container_id": ctx.ContainerID, 69 "_container_name": string(containerName), 70 "_image_id": ctx.ContainerImageID, 71 "_image_name": ctx.ContainerImageName, 72 "_command": ctx.Command(), 73 "_tag": tag, 74 "_created": ctx.ContainerCreated, 75 } 76 77 extraAttrs := ctx.ExtraAttributes(func(key string) string { 78 if key[0] == '_' { 79 return key 80 } 81 return "_" + key 82 }) 83 for k, v := range extraAttrs { 84 extra[k] = v 85 } 86 87 rawExtra, err := json.Marshal(extra) 88 if err != nil { 89 return nil, err 90 } 91 92 // create new gelfWriter 93 gelfWriter, err := gelf.NewWriter(address) 94 if err != nil { 95 return nil, fmt.Errorf("gelf: cannot connect to GELF endpoint: %s %v", address, err) 96 } 97 98 if v, ok := ctx.Config["gelf-compression-type"]; ok { 99 switch v { 100 case "gzip": 101 gelfWriter.CompressionType = gelf.CompressGzip 102 case "zlib": 103 gelfWriter.CompressionType = gelf.CompressZlib 104 case "none": 105 gelfWriter.CompressionType = gelf.CompressNone 106 default: 107 return nil, fmt.Errorf("gelf: invalid compression type %q", v) 108 } 109 } 110 111 if v, ok := ctx.Config["gelf-compression-level"]; ok { 112 val, err := strconv.Atoi(v) 113 if err != nil { 114 return nil, fmt.Errorf("gelf: invalid compression level %s, err %v", v, err) 115 } 116 gelfWriter.CompressionLevel = val 117 } 118 119 return &gelfLogger{ 120 writer: gelfWriter, 121 ctx: ctx, 122 hostname: hostname, 123 rawExtra: rawExtra, 124 }, nil 125 } 126 127 func (s *gelfLogger) Log(msg *logger.Message) error { 128 level := gelf.LOG_INFO 129 if msg.Source == "stderr" { 130 level = gelf.LOG_ERR 131 } 132 133 m := gelf.Message{ 134 Version: "1.1", 135 Host: s.hostname, 136 Short: string(msg.Line), 137 TimeUnix: float64(msg.Timestamp.UnixNano()/int64(time.Millisecond)) / 1000.0, 138 Level: level, 139 RawExtra: s.rawExtra, 140 } 141 142 if err := s.writer.WriteMessage(&m); err != nil { 143 return fmt.Errorf("gelf: cannot send GELF message: %v", err) 144 } 145 return nil 146 } 147 148 func (s *gelfLogger) Close() error { 149 return s.writer.Close() 150 } 151 152 func (s *gelfLogger) Name() string { 153 return name 154 } 155 156 // ValidateLogOpt looks for gelf specific log options gelf-address, & 157 // gelf-tag. 158 func ValidateLogOpt(cfg map[string]string) error { 159 for key, val := range cfg { 160 switch key { 161 case "gelf-address": 162 case "gelf-tag": 163 case "tag": 164 case "labels": 165 case "env": 166 case "gelf-compression-level": 167 i, err := strconv.Atoi(val) 168 if err != nil || i < flate.DefaultCompression || i > flate.BestCompression { 169 return fmt.Errorf("unknown value %q for log opt %q for gelf log driver", val, key) 170 } 171 case "gelf-compression-type": 172 switch val { 173 case "gzip", "zlib", "none": 174 default: 175 return fmt.Errorf("unknown value %q for log opt %q for gelf log driver", val, key) 176 } 177 default: 178 return fmt.Errorf("unknown log opt %q for gelf log driver", key) 179 } 180 } 181 182 if _, err := parseAddress(cfg["gelf-address"]); err != nil { 183 return err 184 } 185 186 return nil 187 } 188 189 func parseAddress(address string) (string, error) { 190 if address == "" { 191 return "", nil 192 } 193 if !urlutil.IsTransportURL(address) { 194 return "", fmt.Errorf("gelf-address should be in form proto://address, got %v", address) 195 } 196 url, err := url.Parse(address) 197 if err != nil { 198 return "", err 199 } 200 201 // we support only udp 202 if url.Scheme != "udp" { 203 return "", fmt.Errorf("gelf: endpoint needs to be UDP") 204 } 205 206 // get host and port 207 if _, _, err = net.SplitHostPort(url.Host); err != nil { 208 return "", fmt.Errorf("gelf: please provide gelf-address as udp://host:port") 209 } 210 211 return url.Host, nil 212 }