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  }