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