github.com/ncdc/docker@v0.10.1-0.20160129113957-6c6729ef5b74/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  
    12  	"github.com/Sirupsen/logrus"
    13  	"github.com/docker/docker/daemon/logger"
    14  	"github.com/docker/docker/daemon/logger/loggerutils"
    15  	"github.com/fluent/fluent-logger-golang/fluent"
    16  )
    17  
    18  type fluentd struct {
    19  	tag           string
    20  	containerID   string
    21  	containerName string
    22  	writer        *fluent.Fluent
    23  	extra         map[string]string
    24  }
    25  
    26  const (
    27  	name                             = "fluentd"
    28  	defaultHostName                  = "localhost"
    29  	defaultPort                      = 24224
    30  	defaultTagPrefix                 = "docker"
    31  	defaultIgnoreConnectErrorOnStart = false           // So that we do not break existing behaviour
    32  	defaultBufferLimit               = 1 * 1024 * 1024 // 1M buffer by default
    33  )
    34  
    35  func init() {
    36  	if err := logger.RegisterLogDriver(name, New); err != nil {
    37  		logrus.Fatal(err)
    38  	}
    39  	if err := logger.RegisterLogOptValidator(name, ValidateLogOpt); err != nil {
    40  		logrus.Fatal(err)
    41  	}
    42  }
    43  
    44  // New creates a fluentd logger using the configuration passed in on
    45  // the context. Supported context configuration variables are
    46  // fluentd-address & fluentd-tag.
    47  func New(ctx logger.Context) (logger.Logger, error) {
    48  	host, port, err := parseAddress(ctx.Config["fluentd-address"])
    49  	if err != nil {
    50  		return nil, err
    51  	}
    52  
    53  	tag, err := loggerutils.ParseLogTag(ctx, "docker.{{.ID}}")
    54  	if err != nil {
    55  		return nil, err
    56  	}
    57  	failOnStartupError, err := loggerutils.ParseFailOnStartupErrorFlag(ctx)
    58  	if err != nil {
    59  		return nil, err
    60  	}
    61  	bufferLimit, err := parseBufferLimit(ctx.Config["buffer-limit"])
    62  	if err != nil {
    63  		return nil, err
    64  	}
    65  	extra := ctx.ExtraAttributes(nil)
    66  	logrus.Debugf("logging driver fluentd configured for container:%s, host:%s, port:%d, tag:%s, extra:%v.", ctx.ContainerID, host, port, tag, extra)
    67  	// logger tries to reconnect 2**32 - 1 times
    68  	// failed (and panic) after 204 years [ 1.5 ** (2**32 - 1) - 1 seconds]
    69  	log, err := fluent.New(fluent.Config{FluentPort: port, FluentHost: host, RetryWait: 1000, MaxRetry: math.MaxInt32, BufferLimit: bufferLimit})
    70  	if err != nil {
    71  		if failOnStartupError {
    72  			return nil, err
    73  		}
    74  		logrus.Warnf("fluentd cannot connect to configured endpoint. Ignoring as instructed. Error: %q", err)
    75  	}
    76  	return &fluentd{
    77  		tag:           tag,
    78  		containerID:   ctx.ContainerID,
    79  		containerName: ctx.ContainerName,
    80  		writer:        log,
    81  		extra:         extra,
    82  	}, nil
    83  }
    84  
    85  func (f *fluentd) Log(msg *logger.Message) error {
    86  	data := map[string]string{
    87  		"container_id":   f.containerID,
    88  		"container_name": f.containerName,
    89  		"source":         msg.Source,
    90  		"log":            string(msg.Line),
    91  	}
    92  	for k, v := range f.extra {
    93  		data[k] = v
    94  	}
    95  	// fluent-logger-golang buffers logs from failures and disconnections,
    96  	// and these are transferred again automatically.
    97  	return f.writer.PostWithTime(f.tag, msg.Timestamp, data)
    98  }
    99  
   100  func (f *fluentd) Close() error {
   101  	return f.writer.Close()
   102  }
   103  
   104  func (f *fluentd) Name() string {
   105  	return name
   106  }
   107  
   108  // ValidateLogOpt looks for fluentd specific log options fluentd-address & fluentd-tag.
   109  func ValidateLogOpt(cfg map[string]string) error {
   110  	for key := range cfg {
   111  		switch key {
   112  		case "fluentd-address":
   113  		case "fluentd-tag":
   114  		case "tag":
   115  		case "labels":
   116  		case "env":
   117  		case "fail-on-startup-error":
   118  		case "buffer-limit":
   119  		default:
   120  			return fmt.Errorf("unknown log opt '%s' for fluentd log driver", key)
   121  		}
   122  	}
   123  
   124  	if _, _, err := parseAddress(cfg["fluentd-address"]); err != nil {
   125  		return err
   126  	}
   127  
   128  	return nil
   129  }
   130  
   131  func parseAddress(address string) (string, int, error) {
   132  	if address == "" {
   133  		return defaultHostName, defaultPort, nil
   134  	}
   135  
   136  	host, port, err := net.SplitHostPort(address)
   137  	if err != nil {
   138  		if !strings.Contains(err.Error(), "missing port in address") {
   139  			return "", 0, fmt.Errorf("invalid fluentd-address %s: %s", address, err)
   140  		}
   141  		return host, defaultPort, nil
   142  	}
   143  
   144  	portnum, err := strconv.Atoi(port)
   145  	if err != nil {
   146  		return "", 0, fmt.Errorf("invalid fluentd-address %s: %s", address, err)
   147  	}
   148  	return host, portnum, nil
   149  }
   150  
   151  func parseBufferLimit(bufferLimit string) (int, error) {
   152  	if bufferLimit == "" {
   153  		return defaultBufferLimit, nil
   154  	}
   155  	limit, err := strconv.Atoi(bufferLimit)
   156  	if err != nil {
   157  		return 0, fmt.Errorf("invalid buffer limit %s: %s", bufferLimit, err)
   158  	}
   159  	return limit, nil
   160  }