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