github.com/kobeld/docker@v1.12.0-rc1/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  	"strconv"
    10  	"strings"
    11  	"time"
    12  
    13  	"github.com/Sirupsen/logrus"
    14  	"github.com/docker/docker/daemon/logger"
    15  	"github.com/docker/docker/daemon/logger/loggerutils"
    16  	"github.com/docker/go-units"
    17  	"github.com/fluent/fluent-logger-golang/fluent"
    18  )
    19  
    20  type fluentd struct {
    21  	tag           string
    22  	containerID   string
    23  	containerName string
    24  	writer        *fluent.Fluent
    25  	extra         map[string]string
    26  }
    27  
    28  const (
    29  	name = "fluentd"
    30  
    31  	defaultHost        = "127.0.0.1"
    32  	defaultPort        = 24224
    33  	defaultBufferLimit = 1024 * 1024
    34  	defaultTagPrefix   = "docker"
    35  
    36  	// logger tries to reconnect 2**32 - 1 times
    37  	// failed (and panic) after 204 years [ 1.5 ** (2**32 - 1) - 1 seconds]
    38  	defaultRetryWait              = 1000
    39  	defaultTimeout                = 3 * time.Second
    40  	defaultMaxRetries             = math.MaxInt32
    41  	defaultReconnectWaitIncreRate = 1.5
    42  
    43  	addressKey      = "fluentd-address"
    44  	bufferLimitKey  = "fluentd-buffer-limit"
    45  	retryWaitKey    = "fluentd-retry-wait"
    46  	maxRetriesKey   = "fluentd-max-retries"
    47  	asyncConnectKey = "fluentd-async-connect"
    48  )
    49  
    50  func init() {
    51  	if err := logger.RegisterLogDriver(name, New); err != nil {
    52  		logrus.Fatal(err)
    53  	}
    54  	if err := logger.RegisterLogOptValidator(name, ValidateLogOpt); err != nil {
    55  		logrus.Fatal(err)
    56  	}
    57  }
    58  
    59  // New creates a fluentd logger using the configuration passed in on
    60  // the context. The supported context configuration variable is
    61  // fluentd-address.
    62  func New(ctx logger.Context) (logger.Logger, error) {
    63  	host, port, err := parseAddress(ctx.Config[addressKey])
    64  	if err != nil {
    65  		return nil, err
    66  	}
    67  
    68  	tag, err := loggerutils.ParseLogTag(ctx, "{{.DaemonName}}.{{.ID}}")
    69  	if err != nil {
    70  		return nil, err
    71  	}
    72  
    73  	extra := ctx.ExtraAttributes(nil)
    74  
    75  	bufferLimit := defaultBufferLimit
    76  	if ctx.Config[bufferLimitKey] != "" {
    77  		bl64, err := units.RAMInBytes(ctx.Config[bufferLimitKey])
    78  		if err != nil {
    79  			return nil, err
    80  		}
    81  		bufferLimit = int(bl64)
    82  	}
    83  
    84  	retryWait := defaultRetryWait
    85  	if ctx.Config[retryWaitKey] != "" {
    86  		rwd, err := time.ParseDuration(ctx.Config[retryWaitKey])
    87  		if err != nil {
    88  			return nil, err
    89  		}
    90  		retryWait = int(rwd.Seconds() * 1000)
    91  	}
    92  
    93  	maxRetries := defaultMaxRetries
    94  	if ctx.Config[maxRetriesKey] != "" {
    95  		mr64, err := strconv.ParseUint(ctx.Config[maxRetriesKey], 10, strconv.IntSize)
    96  		if err != nil {
    97  			return nil, err
    98  		}
    99  		maxRetries = int(mr64)
   100  	}
   101  
   102  	asyncConnect := false
   103  	if ctx.Config[asyncConnectKey] != "" {
   104  		if asyncConnect, err = strconv.ParseBool(ctx.Config[asyncConnectKey]); err != nil {
   105  			return nil, err
   106  		}
   107  	}
   108  
   109  	fluentConfig := fluent.Config{
   110  		FluentPort:   port,
   111  		FluentHost:   host,
   112  		BufferLimit:  bufferLimit,
   113  		RetryWait:    retryWait,
   114  		MaxRetry:     maxRetries,
   115  		AsyncConnect: asyncConnect,
   116  	}
   117  
   118  	logrus.WithField("container", ctx.ContainerID).WithField("config", fluentConfig).
   119  		Debug("logging driver fluentd configured")
   120  
   121  	log, err := fluent.New(fluentConfig)
   122  	if err != nil {
   123  		return nil, err
   124  	}
   125  	return &fluentd{
   126  		tag:           tag,
   127  		containerID:   ctx.ContainerID,
   128  		containerName: ctx.ContainerName,
   129  		writer:        log,
   130  		extra:         extra,
   131  	}, nil
   132  }
   133  
   134  func (f *fluentd) Log(msg *logger.Message) error {
   135  	data := map[string]string{
   136  		"container_id":   f.containerID,
   137  		"container_name": f.containerName,
   138  		"source":         msg.Source,
   139  		"log":            string(msg.Line),
   140  	}
   141  	for k, v := range f.extra {
   142  		data[k] = v
   143  	}
   144  	// fluent-logger-golang buffers logs from failures and disconnections,
   145  	// and these are transferred again automatically.
   146  	return f.writer.PostWithTime(f.tag, msg.Timestamp, data)
   147  }
   148  
   149  func (f *fluentd) Close() error {
   150  	return f.writer.Close()
   151  }
   152  
   153  func (f *fluentd) Name() string {
   154  	return name
   155  }
   156  
   157  // ValidateLogOpt looks for fluentd specific log option fluentd-address.
   158  func ValidateLogOpt(cfg map[string]string) error {
   159  	for key := range cfg {
   160  		switch key {
   161  		case "env":
   162  		case "labels":
   163  		case "tag":
   164  		case addressKey:
   165  		case bufferLimitKey:
   166  		case retryWaitKey:
   167  		case maxRetriesKey:
   168  		case asyncConnectKey:
   169  			// Accepted
   170  		default:
   171  			return fmt.Errorf("unknown log opt '%s' for fluentd log driver", key)
   172  		}
   173  	}
   174  
   175  	if _, _, err := parseAddress(cfg["fluentd-address"]); err != nil {
   176  		return err
   177  	}
   178  
   179  	return nil
   180  }
   181  
   182  func parseAddress(address string) (string, int, error) {
   183  	if address == "" {
   184  		return defaultHost, defaultPort, nil
   185  	}
   186  
   187  	host, port, err := net.SplitHostPort(address)
   188  	if err != nil {
   189  		if !strings.Contains(err.Error(), "missing port in address") {
   190  			return "", 0, fmt.Errorf("invalid fluentd-address %s: %s", address, err)
   191  		}
   192  		return host, defaultPort, nil
   193  	}
   194  
   195  	portnum, err := strconv.Atoi(port)
   196  	if err != nil {
   197  		return "", 0, fmt.Errorf("invalid fluentd-address %s: %s", address, err)
   198  	}
   199  	return host, portnum, nil
   200  }