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