github.com/fabiokung/docker@v0.11.2-0.20170222101415-4534dcd49497/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 := info.ExtraAttributes(nil) 82 83 bufferLimit := defaultBufferLimit 84 if info.Config[bufferLimitKey] != "" { 85 bl64, err := units.RAMInBytes(info.Config[bufferLimitKey]) 86 if err != nil { 87 return nil, err 88 } 89 bufferLimit = int(bl64) 90 } 91 92 retryWait := defaultRetryWait 93 if info.Config[retryWaitKey] != "" { 94 rwd, err := time.ParseDuration(info.Config[retryWaitKey]) 95 if err != nil { 96 return nil, err 97 } 98 retryWait = int(rwd.Seconds() * 1000) 99 } 100 101 maxRetries := defaultMaxRetries 102 if info.Config[maxRetriesKey] != "" { 103 mr64, err := strconv.ParseUint(info.Config[maxRetriesKey], 10, strconv.IntSize) 104 if err != nil { 105 return nil, err 106 } 107 maxRetries = int(mr64) 108 } 109 110 asyncConnect := false 111 if info.Config[asyncConnectKey] != "" { 112 if asyncConnect, err = strconv.ParseBool(info.Config[asyncConnectKey]); err != nil { 113 return nil, err 114 } 115 } 116 117 fluentConfig := fluent.Config{ 118 FluentPort: loc.port, 119 FluentHost: loc.host, 120 FluentNetwork: loc.protocol, 121 FluentSocketPath: loc.path, 122 BufferLimit: bufferLimit, 123 RetryWait: retryWait, 124 MaxRetry: maxRetries, 125 AsyncConnect: asyncConnect, 126 } 127 128 logrus.WithField("container", info.ContainerID).WithField("config", fluentConfig). 129 Debug("logging driver fluentd configured") 130 131 log, err := fluent.New(fluentConfig) 132 if err != nil { 133 return nil, err 134 } 135 return &fluentd{ 136 tag: tag, 137 containerID: info.ContainerID, 138 containerName: info.ContainerName, 139 writer: log, 140 extra: extra, 141 }, nil 142 } 143 144 func (f *fluentd) Log(msg *logger.Message) error { 145 data := map[string]string{ 146 "container_id": f.containerID, 147 "container_name": f.containerName, 148 "source": msg.Source, 149 "log": string(msg.Line), 150 } 151 for k, v := range f.extra { 152 data[k] = v 153 } 154 155 ts := msg.Timestamp 156 logger.PutMessage(msg) 157 // fluent-logger-golang buffers logs from failures and disconnections, 158 // and these are transferred again automatically. 159 return f.writer.PostWithTime(f.tag, ts, data) 160 } 161 162 func (f *fluentd) Close() error { 163 return f.writer.Close() 164 } 165 166 func (f *fluentd) Name() string { 167 return name 168 } 169 170 // ValidateLogOpt looks for fluentd specific log option fluentd-address. 171 func ValidateLogOpt(cfg map[string]string) error { 172 for key := range cfg { 173 switch key { 174 case "env": 175 case "labels": 176 case "tag": 177 case addressKey: 178 case bufferLimitKey: 179 case retryWaitKey: 180 case maxRetriesKey: 181 case asyncConnectKey: 182 // Accepted 183 default: 184 return fmt.Errorf("unknown log opt '%s' for fluentd log driver", key) 185 } 186 } 187 188 _, err := parseAddress(cfg["fluentd-address"]) 189 return err 190 } 191 192 func parseAddress(address string) (*location, error) { 193 if address == "" { 194 return &location{ 195 protocol: defaultProtocol, 196 host: defaultHost, 197 port: defaultPort, 198 path: "", 199 }, nil 200 } 201 202 protocol := defaultProtocol 203 givenAddress := address 204 if urlutil.IsTransportURL(address) { 205 url, err := url.Parse(address) 206 if err != nil { 207 return nil, errors.Wrapf(err, "invalid fluentd-address %s", givenAddress) 208 } 209 // unix and unixgram socket 210 if url.Scheme == "unix" || url.Scheme == "unixgram" { 211 return &location{ 212 protocol: url.Scheme, 213 host: "", 214 port: 0, 215 path: url.Path, 216 }, nil 217 } 218 // tcp|udp 219 protocol = url.Scheme 220 address = url.Host 221 } 222 223 host, port, err := net.SplitHostPort(address) 224 if err != nil { 225 if !strings.Contains(err.Error(), "missing port in address") { 226 return nil, errors.Wrapf(err, "invalid fluentd-address %s", givenAddress) 227 } 228 return &location{ 229 protocol: protocol, 230 host: host, 231 port: defaultPort, 232 path: "", 233 }, nil 234 } 235 236 portnum, err := strconv.Atoi(port) 237 if err != nil { 238 return nil, errors.Wrapf(err, "invalid fluentd-address %s", givenAddress) 239 } 240 return &location{ 241 protocol: protocol, 242 host: host, 243 port: portnum, 244 path: "", 245 }, nil 246 }