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  }