github.com/devdivbcp/moby@v17.12.0-ce-rc1.0.20200726071732-2d4bfdc789ad+incompatible/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 "fmt" 7 "math" 8 "net" 9 "net/url" 10 "strconv" 11 "strings" 12 "time" 13 14 "github.com/docker/docker/daemon/logger" 15 "github.com/docker/docker/daemon/logger/loggerutils" 16 "github.com/docker/docker/pkg/urlutil" 17 "github.com/docker/go-units" 18 "github.com/fluent/fluent-logger-golang/fluent" 19 "github.com/pkg/errors" 20 "github.com/sirupsen/logrus" 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 subSecondPrecisionKey = "fluentd-sub-second-precision" 57 ) 58 59 func init() { 60 if err := logger.RegisterLogDriver(name, New); err != nil { 61 logrus.Fatal(err) 62 } 63 if err := logger.RegisterLogOptValidator(name, ValidateLogOpt); err != nil { 64 logrus.Fatal(err) 65 } 66 } 67 68 // New creates a fluentd logger using the configuration passed in on 69 // the context. The supported context configuration variable is 70 // fluentd-address. 71 func New(info logger.Info) (logger.Logger, error) { 72 loc, err := parseAddress(info.Config[addressKey]) 73 if err != nil { 74 return nil, err 75 } 76 77 tag, err := loggerutils.ParseLogTag(info, loggerutils.DefaultTemplate) 78 if err != nil { 79 return nil, err 80 } 81 82 extra, err := info.ExtraAttributes(nil) 83 if err != nil { 84 return nil, err 85 } 86 87 bufferLimit := defaultBufferLimit 88 if info.Config[bufferLimitKey] != "" { 89 bl64, err := units.RAMInBytes(info.Config[bufferLimitKey]) 90 if err != nil { 91 return nil, err 92 } 93 bufferLimit = int(bl64) 94 } 95 96 retryWait := defaultRetryWait 97 if info.Config[retryWaitKey] != "" { 98 rwd, err := time.ParseDuration(info.Config[retryWaitKey]) 99 if err != nil { 100 return nil, err 101 } 102 retryWait = int(rwd.Seconds() * 1000) 103 } 104 105 maxRetries := defaultMaxRetries 106 if info.Config[maxRetriesKey] != "" { 107 mr64, err := strconv.ParseUint(info.Config[maxRetriesKey], 10, strconv.IntSize) 108 if err != nil { 109 return nil, err 110 } 111 maxRetries = int(mr64) 112 } 113 114 asyncConnect := false 115 if info.Config[asyncConnectKey] != "" { 116 if asyncConnect, err = strconv.ParseBool(info.Config[asyncConnectKey]); err != nil { 117 return nil, err 118 } 119 } 120 121 subSecondPrecision := false 122 if info.Config[subSecondPrecisionKey] != "" { 123 if subSecondPrecision, err = strconv.ParseBool(info.Config[subSecondPrecisionKey]); err != nil { 124 return nil, err 125 } 126 } 127 128 fluentConfig := fluent.Config{ 129 FluentPort: loc.port, 130 FluentHost: loc.host, 131 FluentNetwork: loc.protocol, 132 FluentSocketPath: loc.path, 133 BufferLimit: bufferLimit, 134 RetryWait: retryWait, 135 MaxRetry: maxRetries, 136 Async: asyncConnect, 137 SubSecondPrecision: subSecondPrecision, 138 } 139 140 logrus.WithField("container", info.ContainerID).WithField("config", fluentConfig). 141 Debug("logging driver fluentd configured") 142 143 log, err := fluent.New(fluentConfig) 144 if err != nil { 145 return nil, err 146 } 147 return &fluentd{ 148 tag: tag, 149 containerID: info.ContainerID, 150 containerName: info.ContainerName, 151 writer: log, 152 extra: extra, 153 }, nil 154 } 155 156 func (f *fluentd) Log(msg *logger.Message) error { 157 data := map[string]string{ 158 "container_id": f.containerID, 159 "container_name": f.containerName, 160 "source": msg.Source, 161 "log": string(msg.Line), 162 } 163 for k, v := range f.extra { 164 data[k] = v 165 } 166 if msg.PLogMetaData != nil { 167 data["partial_message"] = "true" 168 data["partial_id"] = msg.PLogMetaData.ID 169 data["partial_ordinal"] = strconv.Itoa(msg.PLogMetaData.Ordinal) 170 data["partial_last"] = strconv.FormatBool(msg.PLogMetaData.Last) 171 } 172 173 ts := msg.Timestamp 174 logger.PutMessage(msg) 175 // fluent-logger-golang buffers logs from failures and disconnections, 176 // and these are transferred again automatically. 177 return f.writer.PostWithTime(f.tag, ts, data) 178 } 179 180 func (f *fluentd) Close() error { 181 return f.writer.Close() 182 } 183 184 func (f *fluentd) Name() string { 185 return name 186 } 187 188 // ValidateLogOpt looks for fluentd specific log option fluentd-address. 189 func ValidateLogOpt(cfg map[string]string) error { 190 for key := range cfg { 191 switch key { 192 case "env": 193 case "env-regex": 194 case "labels": 195 case "tag": 196 case addressKey: 197 case bufferLimitKey: 198 case retryWaitKey: 199 case maxRetriesKey: 200 case asyncConnectKey: 201 case subSecondPrecisionKey: 202 // Accepted 203 default: 204 return fmt.Errorf("unknown log opt '%s' for fluentd log driver", key) 205 } 206 } 207 208 _, err := parseAddress(cfg[addressKey]) 209 return err 210 } 211 212 func parseAddress(address string) (*location, error) { 213 if address == "" { 214 return &location{ 215 protocol: defaultProtocol, 216 host: defaultHost, 217 port: defaultPort, 218 path: "", 219 }, nil 220 } 221 222 protocol := defaultProtocol 223 givenAddress := address 224 if urlutil.IsTransportURL(address) { 225 url, err := url.Parse(address) 226 if err != nil { 227 return nil, errors.Wrapf(err, "invalid fluentd-address %s", givenAddress) 228 } 229 // unix and unixgram socket 230 if url.Scheme == "unix" || url.Scheme == "unixgram" { 231 return &location{ 232 protocol: url.Scheme, 233 host: "", 234 port: 0, 235 path: url.Path, 236 }, nil 237 } 238 // tcp|udp 239 protocol = url.Scheme 240 address = url.Host 241 } 242 243 host, port, err := net.SplitHostPort(address) 244 if err != nil { 245 if !strings.Contains(err.Error(), "missing port in address") { 246 return nil, errors.Wrapf(err, "invalid fluentd-address %s", givenAddress) 247 } 248 return &location{ 249 protocol: protocol, 250 host: host, 251 port: defaultPort, 252 path: "", 253 }, nil 254 } 255 256 portnum, err := strconv.Atoi(port) 257 if err != nil { 258 return nil, errors.Wrapf(err, "invalid fluentd-address %s", givenAddress) 259 } 260 return &location{ 261 protocol: protocol, 262 host: host, 263 port: portnum, 264 path: "", 265 }, nil 266 }