github.com/Heebron/moby@v0.0.0-20221111184709-6eab4f55faf7/daemon/logger/fluentd/fluentd.go (about) 1 // Package fluentd provides the log driver for forwarding server logs 2 // to fluentd endpoints. 3 package fluentd // import "github.com/docker/docker/daemon/logger/fluentd" 4 5 import ( 6 "math" 7 "net/url" 8 "strconv" 9 "strings" 10 "time" 11 12 "github.com/docker/docker/daemon/logger" 13 "github.com/docker/docker/daemon/logger/loggerutils" 14 "github.com/docker/docker/errdefs" 15 units "github.com/docker/go-units" 16 "github.com/fluent/fluent-logger-golang/fluent" 17 "github.com/pkg/errors" 18 "github.com/sirupsen/logrus" 19 ) 20 21 type fluentd struct { 22 tag string 23 containerID string 24 containerName string 25 writer *fluent.Fluent 26 extra map[string]string 27 } 28 29 type location struct { 30 protocol string 31 host string 32 port int 33 path string 34 } 35 36 const ( 37 name = "fluentd" 38 39 defaultBufferLimit = 1024 * 1024 40 defaultHost = "127.0.0.1" 41 defaultPort = 24224 42 defaultProtocol = "tcp" 43 44 // logger tries to reconnect 2**32 - 1 times 45 // failed (and panic) after 204 years [ 1.5 ** (2**32 - 1) - 1 seconds] 46 defaultMaxRetries = math.MaxInt32 47 defaultRetryWait = 1000 48 49 minReconnectInterval = 100 * time.Millisecond 50 maxReconnectInterval = 10 * time.Second 51 52 addressKey = "fluentd-address" 53 asyncKey = "fluentd-async" 54 asyncConnectKey = "fluentd-async-connect" // deprecated option (use fluent-async instead) 55 asyncReconnectIntervalKey = "fluentd-async-reconnect-interval" 56 bufferLimitKey = "fluentd-buffer-limit" 57 maxRetriesKey = "fluentd-max-retries" 58 requestAckKey = "fluentd-request-ack" 59 retryWaitKey = "fluentd-retry-wait" 60 subSecondPrecisionKey = "fluentd-sub-second-precision" 61 ) 62 63 func init() { 64 if err := logger.RegisterLogDriver(name, New); err != nil { 65 panic(err) 66 } 67 if err := logger.RegisterLogOptValidator(name, ValidateLogOpt); err != nil { 68 panic(err) 69 } 70 } 71 72 // New creates a fluentd logger using the configuration passed in on 73 // the context. The supported context configuration variable is 74 // fluentd-address. 75 func New(info logger.Info) (logger.Logger, error) { 76 fluentConfig, err := parseConfig(info.Config) 77 if err != nil { 78 return nil, errdefs.InvalidParameter(err) 79 } 80 81 tag, err := loggerutils.ParseLogTag(info, loggerutils.DefaultTemplate) 82 if err != nil { 83 return nil, errdefs.InvalidParameter(err) 84 } 85 86 extra, err := info.ExtraAttributes(nil) 87 if err != nil { 88 return nil, errdefs.InvalidParameter(err) 89 } 90 91 logrus.WithField("container", info.ContainerID).WithField("config", fluentConfig). 92 Debug("logging driver fluentd configured") 93 94 log, err := fluent.New(fluentConfig) 95 if err != nil { 96 return nil, err 97 } 98 return &fluentd{ 99 tag: tag, 100 containerID: info.ContainerID, 101 containerName: info.ContainerName, 102 writer: log, 103 extra: extra, 104 }, nil 105 } 106 107 func (f *fluentd) Log(msg *logger.Message) error { 108 data := map[string]string{ 109 "container_id": f.containerID, 110 "container_name": f.containerName, 111 "source": msg.Source, 112 "log": string(msg.Line), 113 } 114 for k, v := range f.extra { 115 data[k] = v 116 } 117 if msg.PLogMetaData != nil { 118 data["partial_message"] = "true" 119 data["partial_id"] = msg.PLogMetaData.ID 120 data["partial_ordinal"] = strconv.Itoa(msg.PLogMetaData.Ordinal) 121 data["partial_last"] = strconv.FormatBool(msg.PLogMetaData.Last) 122 } 123 124 ts := msg.Timestamp 125 logger.PutMessage(msg) 126 // fluent-logger-golang buffers logs from failures and disconnections, 127 // and these are transferred again automatically. 128 return f.writer.PostWithTime(f.tag, ts, data) 129 } 130 131 func (f *fluentd) Close() error { 132 return f.writer.Close() 133 } 134 135 func (f *fluentd) Name() string { 136 return name 137 } 138 139 // ValidateLogOpt looks for fluentd specific log option fluentd-address. 140 func ValidateLogOpt(cfg map[string]string) error { 141 for key := range cfg { 142 switch key { 143 case "env": 144 case "env-regex": 145 case "labels": 146 case "labels-regex": 147 case "tag": 148 149 case addressKey: 150 case asyncKey: 151 case asyncConnectKey: 152 case asyncReconnectIntervalKey: 153 case bufferLimitKey: 154 case maxRetriesKey: 155 case requestAckKey: 156 case retryWaitKey: 157 case subSecondPrecisionKey: 158 // Accepted 159 default: 160 return errors.Errorf("unknown log opt '%s' for fluentd log driver", key) 161 } 162 } 163 164 _, err := parseConfig(cfg) 165 return err 166 } 167 168 func parseConfig(cfg map[string]string) (fluent.Config, error) { 169 var config fluent.Config 170 171 loc, err := parseAddress(cfg[addressKey]) 172 if err != nil { 173 return config, errors.Wrapf(err, "invalid fluentd-address (%s)", cfg[addressKey]) 174 } 175 176 bufferLimit := defaultBufferLimit 177 if cfg[bufferLimitKey] != "" { 178 bl64, err := units.RAMInBytes(cfg[bufferLimitKey]) 179 if err != nil { 180 return config, err 181 } 182 bufferLimit = int(bl64) 183 } 184 185 retryWait := defaultRetryWait 186 if cfg[retryWaitKey] != "" { 187 rwd, err := time.ParseDuration(cfg[retryWaitKey]) 188 if err != nil { 189 return config, err 190 } 191 retryWait = int(rwd.Seconds() * 1000) 192 } 193 194 maxRetries := defaultMaxRetries 195 if cfg[maxRetriesKey] != "" { 196 mr64, err := strconv.ParseUint(cfg[maxRetriesKey], 10, strconv.IntSize) 197 if err != nil { 198 return config, err 199 } 200 maxRetries = int(mr64) 201 } 202 203 if cfg[asyncKey] != "" && cfg[asyncConnectKey] != "" { 204 return config, errors.Errorf("conflicting options: cannot specify both '%s' and '%s", asyncKey, asyncConnectKey) 205 } 206 207 async := false 208 if cfg[asyncKey] != "" { 209 if async, err = strconv.ParseBool(cfg[asyncKey]); err != nil { 210 return config, err 211 } 212 } 213 214 // TODO fluentd-async-connect is deprecated in driver v1.4.0. Remove after two stable releases 215 asyncConnect := false 216 if cfg[asyncConnectKey] != "" { 217 if asyncConnect, err = strconv.ParseBool(cfg[asyncConnectKey]); err != nil { 218 return config, err 219 } 220 } 221 222 asyncReconnectInterval := 0 223 if cfg[asyncReconnectIntervalKey] != "" { 224 interval, err := time.ParseDuration(cfg[asyncReconnectIntervalKey]) 225 if err != nil { 226 return config, errors.Wrapf(err, "invalid value for %s", asyncReconnectIntervalKey) 227 } 228 if interval != 0 && (interval < minReconnectInterval || interval > maxReconnectInterval) { 229 return config, errors.Errorf("invalid value for %s: value (%q) must be between %s and %s", 230 asyncReconnectIntervalKey, interval, minReconnectInterval, maxReconnectInterval) 231 } 232 asyncReconnectInterval = int(interval.Milliseconds()) 233 } 234 235 subSecondPrecision := false 236 if cfg[subSecondPrecisionKey] != "" { 237 if subSecondPrecision, err = strconv.ParseBool(cfg[subSecondPrecisionKey]); err != nil { 238 return config, err 239 } 240 } 241 242 requestAck := false 243 if cfg[requestAckKey] != "" { 244 if requestAck, err = strconv.ParseBool(cfg[requestAckKey]); err != nil { 245 return config, err 246 } 247 } 248 249 config = fluent.Config{ 250 FluentPort: loc.port, 251 FluentHost: loc.host, 252 FluentNetwork: loc.protocol, 253 FluentSocketPath: loc.path, 254 BufferLimit: bufferLimit, 255 RetryWait: retryWait, 256 MaxRetry: maxRetries, 257 Async: async, 258 AsyncConnect: asyncConnect, 259 AsyncReconnectInterval: asyncReconnectInterval, 260 SubSecondPrecision: subSecondPrecision, 261 RequestAck: requestAck, 262 ForceStopAsyncSend: async || asyncConnect, 263 } 264 265 return config, nil 266 } 267 268 func parseAddress(address string) (*location, error) { 269 if address == "" { 270 return &location{ 271 protocol: defaultProtocol, 272 host: defaultHost, 273 port: defaultPort, 274 path: "", 275 }, nil 276 } 277 278 if !strings.Contains(address, "://") { 279 address = defaultProtocol + "://" + address 280 } 281 282 addr, err := url.Parse(address) 283 if err != nil { 284 return nil, err 285 } 286 287 switch addr.Scheme { 288 case "unix": 289 if strings.TrimLeft(addr.Path, "/") == "" { 290 return nil, errors.New("path is empty") 291 } 292 return &location{protocol: addr.Scheme, path: addr.Path}, nil 293 case "tcp", "tls": 294 // continue processing below 295 default: 296 return nil, errors.Errorf("unsupported scheme: '%s'", addr.Scheme) 297 } 298 299 if addr.Path != "" { 300 return nil, errors.New("should not contain a path element") 301 } 302 303 host := defaultHost 304 port := defaultPort 305 306 if h := addr.Hostname(); h != "" { 307 host = h 308 } 309 if p := addr.Port(); p != "" { 310 // Port numbers are 16 bit: https://www.ietf.org/rfc/rfc793.html#section-3.1 311 portNum, err := strconv.ParseUint(p, 10, 16) 312 if err != nil { 313 return nil, errors.Wrap(err, "invalid port") 314 } 315 port = int(portNum) 316 } 317 return &location{ 318 protocol: addr.Scheme, 319 host: host, 320 port: port, 321 path: "", 322 }, nil 323 }