github.com/jiasir/docker@v1.3.3-0.20170609024000-252e610103e7/daemon/logger/fluentd/fluentd.go (about) 1 // Package fluentd provides the log driver for forwarding server logs 2 // to fluentd endpoints. 3 package fluentd 4 5 import ( 6 "fmt" 7 "math" 8 "net" 9 "net/url" 10 "strconv" 11 "strings" 12 "time" 13 14 "github.com/Sirupsen/logrus" 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/docker/go-units" 19 "github.com/fluent/fluent-logger-golang/fluent" 20 "github.com/pkg/errors" 21 ) 22 23 type fluentd struct { 24 tag string 25 containerID string 26 containerName string 27 writer *fluent.Fluent 28 extra map[string]string 29 } 30 31 type location struct { 32 protocol string 33 host string 34 port int 35 path string 36 } 37 38 const ( 39 name = "fluentd" 40 41 defaultProtocol = "tcp" 42 defaultHost = "127.0.0.1" 43 defaultPort = 24224 44 defaultBufferLimit = 1024 * 1024 45 46 // logger tries to reconnect 2**32 - 1 times 47 // failed (and panic) after 204 years [ 1.5 ** (2**32 - 1) - 1 seconds] 48 defaultRetryWait = 1000 49 defaultMaxRetries = math.MaxInt32 50 51 addressKey = "fluentd-address" 52 bufferLimitKey = "fluentd-buffer-limit" 53 retryWaitKey = "fluentd-retry-wait" 54 maxRetriesKey = "fluentd-max-retries" 55 asyncConnectKey = "fluentd-async-connect" 56 ) 57 58 func init() { 59 if err := logger.RegisterLogDriver(name, New); err != nil { 60 logrus.Fatal(err) 61 } 62 if err := logger.RegisterLogOptValidator(name, ValidateLogOpt); err != nil { 63 logrus.Fatal(err) 64 } 65 } 66 67 // New creates a fluentd logger using the configuration passed in on 68 // the context. The supported context configuration variable is 69 // fluentd-address. 70 func New(info logger.Info) (logger.Logger, error) { 71 loc, err := parseAddress(info.Config[addressKey]) 72 if err != nil { 73 return nil, err 74 } 75 76 tag, err := loggerutils.ParseLogTag(info, loggerutils.DefaultTemplate) 77 if err != nil { 78 return nil, err 79 } 80 81 extra, err := info.ExtraAttributes(nil) 82 if err != nil { 83 return nil, err 84 } 85 86 bufferLimit := defaultBufferLimit 87 if info.Config[bufferLimitKey] != "" { 88 bl64, err := units.RAMInBytes(info.Config[bufferLimitKey]) 89 if err != nil { 90 return nil, err 91 } 92 bufferLimit = int(bl64) 93 } 94 95 retryWait := defaultRetryWait 96 if info.Config[retryWaitKey] != "" { 97 rwd, err := time.ParseDuration(info.Config[retryWaitKey]) 98 if err != nil { 99 return nil, err 100 } 101 retryWait = int(rwd.Seconds() * 1000) 102 } 103 104 maxRetries := defaultMaxRetries 105 if info.Config[maxRetriesKey] != "" { 106 mr64, err := strconv.ParseUint(info.Config[maxRetriesKey], 10, strconv.IntSize) 107 if err != nil { 108 return nil, err 109 } 110 maxRetries = int(mr64) 111 } 112 113 asyncConnect := false 114 if info.Config[asyncConnectKey] != "" { 115 if asyncConnect, err = strconv.ParseBool(info.Config[asyncConnectKey]); err != nil { 116 return nil, err 117 } 118 } 119 120 fluentConfig := fluent.Config{ 121 FluentPort: loc.port, 122 FluentHost: loc.host, 123 FluentNetwork: loc.protocol, 124 FluentSocketPath: loc.path, 125 BufferLimit: bufferLimit, 126 RetryWait: retryWait, 127 MaxRetry: maxRetries, 128 AsyncConnect: asyncConnect, 129 } 130 131 logrus.WithField("container", info.ContainerID).WithField("config", fluentConfig). 132 Debug("logging driver fluentd configured") 133 134 log, err := fluent.New(fluentConfig) 135 if err != nil { 136 return nil, err 137 } 138 return &fluentd{ 139 tag: tag, 140 containerID: info.ContainerID, 141 containerName: info.ContainerName, 142 writer: log, 143 extra: extra, 144 }, nil 145 } 146 147 func (f *fluentd) Log(msg *logger.Message) error { 148 data := map[string]string{ 149 "container_id": f.containerID, 150 "container_name": f.containerName, 151 "source": msg.Source, 152 "log": string(msg.Line), 153 } 154 for k, v := range f.extra { 155 data[k] = v 156 } 157 158 ts := msg.Timestamp 159 logger.PutMessage(msg) 160 // fluent-logger-golang buffers logs from failures and disconnections, 161 // and these are transferred again automatically. 162 return f.writer.PostWithTime(f.tag, ts, data) 163 } 164 165 func (f *fluentd) Close() error { 166 return f.writer.Close() 167 } 168 169 func (f *fluentd) Name() string { 170 return name 171 } 172 173 // ValidateLogOpt looks for fluentd specific log option fluentd-address. 174 func ValidateLogOpt(cfg map[string]string) error { 175 for key := range cfg { 176 switch key { 177 case "env": 178 case "env-regex": 179 case "labels": 180 case "tag": 181 case addressKey: 182 case bufferLimitKey: 183 case retryWaitKey: 184 case maxRetriesKey: 185 case asyncConnectKey: 186 // Accepted 187 default: 188 return fmt.Errorf("unknown log opt '%s' for fluentd log driver", key) 189 } 190 } 191 192 _, err := parseAddress(cfg["fluentd-address"]) 193 return err 194 } 195 196 func parseAddress(address string) (*location, error) { 197 if address == "" { 198 return &location{ 199 protocol: defaultProtocol, 200 host: defaultHost, 201 port: defaultPort, 202 path: "", 203 }, nil 204 } 205 206 protocol := defaultProtocol 207 givenAddress := address 208 if urlutil.IsTransportURL(address) { 209 url, err := url.Parse(address) 210 if err != nil { 211 return nil, errors.Wrapf(err, "invalid fluentd-address %s", givenAddress) 212 } 213 // unix and unixgram socket 214 if url.Scheme == "unix" || url.Scheme == "unixgram" { 215 return &location{ 216 protocol: url.Scheme, 217 host: "", 218 port: 0, 219 path: url.Path, 220 }, nil 221 } 222 // tcp|udp 223 protocol = url.Scheme 224 address = url.Host 225 } 226 227 host, port, err := net.SplitHostPort(address) 228 if err != nil { 229 if !strings.Contains(err.Error(), "missing port in address") { 230 return nil, errors.Wrapf(err, "invalid fluentd-address %s", givenAddress) 231 } 232 return &location{ 233 protocol: protocol, 234 host: host, 235 port: defaultPort, 236 path: "", 237 }, nil 238 } 239 240 portnum, err := strconv.Atoi(port) 241 if err != nil { 242 return nil, errors.Wrapf(err, "invalid fluentd-address %s", givenAddress) 243 } 244 return &location{ 245 protocol: protocol, 246 host: host, 247 port: portnum, 248 path: "", 249 }, nil 250 }