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