gopkg.in/docker/docker.v23@v23.0.11/daemon/logger/awslogs/cloudwatchlogs.go (about)

     1  // Package awslogs provides the logdriver for forwarding container logs to Amazon CloudWatch Logs
     2  package awslogs // import "github.com/docker/docker/daemon/logger/awslogs"
     3  
     4  import (
     5  	"fmt"
     6  	"os"
     7  	"regexp"
     8  	"runtime"
     9  	"sort"
    10  	"strconv"
    11  	"strings"
    12  	"sync"
    13  	"time"
    14  	"unicode/utf8"
    15  
    16  	"github.com/aws/aws-sdk-go/aws"
    17  	"github.com/aws/aws-sdk-go/aws/awserr"
    18  	"github.com/aws/aws-sdk-go/aws/credentials/endpointcreds"
    19  	"github.com/aws/aws-sdk-go/aws/ec2metadata"
    20  	"github.com/aws/aws-sdk-go/aws/request"
    21  	"github.com/aws/aws-sdk-go/aws/session"
    22  	"github.com/aws/aws-sdk-go/service/cloudwatchlogs"
    23  	"github.com/docker/docker/daemon/logger"
    24  	"github.com/docker/docker/daemon/logger/loggerutils"
    25  	"github.com/docker/docker/dockerversion"
    26  	"github.com/pkg/errors"
    27  	"github.com/sirupsen/logrus"
    28  )
    29  
    30  const (
    31  	name                   = "awslogs"
    32  	regionKey              = "awslogs-region"
    33  	endpointKey            = "awslogs-endpoint"
    34  	regionEnvKey           = "AWS_REGION"
    35  	logGroupKey            = "awslogs-group"
    36  	logStreamKey           = "awslogs-stream"
    37  	logCreateGroupKey      = "awslogs-create-group"
    38  	logCreateStreamKey     = "awslogs-create-stream"
    39  	tagKey                 = "tag"
    40  	datetimeFormatKey      = "awslogs-datetime-format"
    41  	multilinePatternKey    = "awslogs-multiline-pattern"
    42  	credentialsEndpointKey = "awslogs-credentials-endpoint" //nolint:gosec // G101: Potential hardcoded credentials
    43  	forceFlushIntervalKey  = "awslogs-force-flush-interval-seconds"
    44  	maxBufferedEventsKey   = "awslogs-max-buffered-events"
    45  	logFormatKey           = "awslogs-format"
    46  
    47  	defaultForceFlushInterval = 5 * time.Second
    48  	defaultMaxBufferedEvents  = 4096
    49  
    50  	// See: http://docs.aws.amazon.com/AmazonCloudWatchLogs/latest/APIReference/API_PutLogEvents.html
    51  	perEventBytes          = 26
    52  	maximumBytesPerPut     = 1048576
    53  	maximumLogEventsPerPut = 10000
    54  
    55  	// See: http://docs.aws.amazon.com/AmazonCloudWatch/latest/DeveloperGuide/cloudwatch_limits.html
    56  	// Because the events are interpreted as UTF-8 encoded Unicode, invalid UTF-8 byte sequences are replaced with the
    57  	// Unicode replacement character (U+FFFD), which is a 3-byte sequence in UTF-8.  To compensate for that and to avoid
    58  	// splitting valid UTF-8 characters into invalid byte sequences, we calculate the length of each event assuming that
    59  	// this replacement happens.
    60  	maximumBytesPerEvent = 262144 - perEventBytes
    61  
    62  	resourceAlreadyExistsCode = "ResourceAlreadyExistsException"
    63  	dataAlreadyAcceptedCode   = "DataAlreadyAcceptedException"
    64  	invalidSequenceTokenCode  = "InvalidSequenceTokenException"
    65  	resourceNotFoundCode      = "ResourceNotFoundException"
    66  
    67  	credentialsEndpoint = "http://169.254.170.2" //nolint:gosec // G101: Potential hardcoded credentials
    68  
    69  	userAgentHeader = "User-Agent"
    70  
    71  	// See: https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/CloudWatch_Embedded_Metric_Format_Specification.html
    72  	logsFormatHeader = "x-amzn-logs-format"
    73  	jsonEmfLogFormat = "json/emf"
    74  )
    75  
    76  type logStream struct {
    77  	logStreamName      string
    78  	logGroupName       string
    79  	logCreateGroup     bool
    80  	logCreateStream    bool
    81  	forceFlushInterval time.Duration
    82  	multilinePattern   *regexp.Regexp
    83  	client             api
    84  	messages           chan *logger.Message
    85  	lock               sync.RWMutex
    86  	closed             bool
    87  	sequenceToken      *string
    88  }
    89  
    90  type logStreamConfig struct {
    91  	logStreamName      string
    92  	logGroupName       string
    93  	logCreateGroup     bool
    94  	logCreateStream    bool
    95  	forceFlushInterval time.Duration
    96  	maxBufferedEvents  int
    97  	multilinePattern   *regexp.Regexp
    98  }
    99  
   100  var _ logger.SizedLogger = &logStream{}
   101  
   102  type api interface {
   103  	CreateLogGroup(*cloudwatchlogs.CreateLogGroupInput) (*cloudwatchlogs.CreateLogGroupOutput, error)
   104  	CreateLogStream(*cloudwatchlogs.CreateLogStreamInput) (*cloudwatchlogs.CreateLogStreamOutput, error)
   105  	PutLogEvents(*cloudwatchlogs.PutLogEventsInput) (*cloudwatchlogs.PutLogEventsOutput, error)
   106  }
   107  
   108  type regionFinder interface {
   109  	Region() (string, error)
   110  }
   111  
   112  type wrappedEvent struct {
   113  	inputLogEvent *cloudwatchlogs.InputLogEvent
   114  	insertOrder   int
   115  }
   116  type byTimestamp []wrappedEvent
   117  
   118  // init registers the awslogs driver
   119  func init() {
   120  	if err := logger.RegisterLogDriver(name, New); err != nil {
   121  		panic(err)
   122  	}
   123  	if err := logger.RegisterLogOptValidator(name, ValidateLogOpt); err != nil {
   124  		panic(err)
   125  	}
   126  }
   127  
   128  // eventBatch holds the events that are batched for submission and the
   129  // associated data about it.
   130  //
   131  // Warning: this type is not threadsafe and must not be used
   132  // concurrently. This type is expected to be consumed in a single go
   133  // routine and never concurrently.
   134  type eventBatch struct {
   135  	batch []wrappedEvent
   136  	bytes int
   137  }
   138  
   139  // New creates an awslogs logger using the configuration passed in on the
   140  // context.  Supported context configuration variables are awslogs-region,
   141  // awslogs-endpoint, awslogs-group, awslogs-stream, awslogs-create-group,
   142  // awslogs-multiline-pattern and awslogs-datetime-format.
   143  // When available, configuration is also taken from environment variables
   144  // AWS_REGION, AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, the shared credentials
   145  // file (~/.aws/credentials), and the EC2 Instance Metadata Service.
   146  func New(info logger.Info) (logger.Logger, error) {
   147  	containerStreamConfig, err := newStreamConfig(info)
   148  	if err != nil {
   149  		return nil, err
   150  	}
   151  	client, err := newAWSLogsClient(info)
   152  	if err != nil {
   153  		return nil, err
   154  	}
   155  
   156  	logNonBlocking := info.Config["mode"] == "non-blocking"
   157  
   158  	containerStream := &logStream{
   159  		logStreamName:      containerStreamConfig.logStreamName,
   160  		logGroupName:       containerStreamConfig.logGroupName,
   161  		logCreateGroup:     containerStreamConfig.logCreateGroup,
   162  		logCreateStream:    containerStreamConfig.logCreateStream,
   163  		forceFlushInterval: containerStreamConfig.forceFlushInterval,
   164  		multilinePattern:   containerStreamConfig.multilinePattern,
   165  		client:             client,
   166  		messages:           make(chan *logger.Message, containerStreamConfig.maxBufferedEvents),
   167  	}
   168  
   169  	creationDone := make(chan bool)
   170  	if logNonBlocking {
   171  		go func() {
   172  			backoff := 1
   173  			maxBackoff := 32
   174  			for {
   175  				// If logger is closed we are done
   176  				containerStream.lock.RLock()
   177  				if containerStream.closed {
   178  					containerStream.lock.RUnlock()
   179  					break
   180  				}
   181  				containerStream.lock.RUnlock()
   182  				err := containerStream.create()
   183  				if err == nil {
   184  					break
   185  				}
   186  
   187  				time.Sleep(time.Duration(backoff) * time.Second)
   188  				if backoff < maxBackoff {
   189  					backoff *= 2
   190  				}
   191  				logrus.
   192  					WithError(err).
   193  					WithField("container-id", info.ContainerID).
   194  					WithField("container-name", info.ContainerName).
   195  					Error("Error while trying to initialize awslogs. Retrying in: ", backoff, " seconds")
   196  			}
   197  			close(creationDone)
   198  		}()
   199  	} else {
   200  		if err = containerStream.create(); err != nil {
   201  			return nil, err
   202  		}
   203  		close(creationDone)
   204  	}
   205  	go containerStream.collectBatch(creationDone)
   206  
   207  	return containerStream, nil
   208  }
   209  
   210  // Parses most of the awslogs- options and prepares a config object to be used for newing the actual stream
   211  // It has been formed out to ease Utest of the New above
   212  func newStreamConfig(info logger.Info) (*logStreamConfig, error) {
   213  	logGroupName := info.Config[logGroupKey]
   214  	logStreamName, err := loggerutils.ParseLogTag(info, "{{.FullID}}")
   215  	if err != nil {
   216  		return nil, err
   217  	}
   218  	logCreateGroup := false
   219  	if info.Config[logCreateGroupKey] != "" {
   220  		logCreateGroup, err = strconv.ParseBool(info.Config[logCreateGroupKey])
   221  		if err != nil {
   222  			return nil, err
   223  		}
   224  	}
   225  
   226  	forceFlushInterval := defaultForceFlushInterval
   227  	if info.Config[forceFlushIntervalKey] != "" {
   228  		forceFlushIntervalAsInt, err := strconv.Atoi(info.Config[forceFlushIntervalKey])
   229  		if err != nil {
   230  			return nil, err
   231  		}
   232  		forceFlushInterval = time.Duration(forceFlushIntervalAsInt) * time.Second
   233  	}
   234  
   235  	maxBufferedEvents := int(defaultMaxBufferedEvents)
   236  	if info.Config[maxBufferedEventsKey] != "" {
   237  		maxBufferedEvents, err = strconv.Atoi(info.Config[maxBufferedEventsKey])
   238  		if err != nil {
   239  			return nil, err
   240  		}
   241  	}
   242  
   243  	if info.Config[logStreamKey] != "" {
   244  		logStreamName = info.Config[logStreamKey]
   245  	}
   246  	logCreateStream := true
   247  	if info.Config[logCreateStreamKey] != "" {
   248  		logCreateStream, err = strconv.ParseBool(info.Config[logCreateStreamKey])
   249  		if err != nil {
   250  			return nil, err
   251  		}
   252  	}
   253  
   254  	multilinePattern, err := parseMultilineOptions(info)
   255  	if err != nil {
   256  		return nil, err
   257  	}
   258  
   259  	containerStreamConfig := &logStreamConfig{
   260  		logStreamName:      logStreamName,
   261  		logGroupName:       logGroupName,
   262  		logCreateGroup:     logCreateGroup,
   263  		logCreateStream:    logCreateStream,
   264  		forceFlushInterval: forceFlushInterval,
   265  		maxBufferedEvents:  maxBufferedEvents,
   266  		multilinePattern:   multilinePattern,
   267  	}
   268  
   269  	return containerStreamConfig, nil
   270  }
   271  
   272  // Parses awslogs-multiline-pattern and awslogs-datetime-format options
   273  // If awslogs-datetime-format is present, convert the format from strftime
   274  // to regexp and return.
   275  // If awslogs-multiline-pattern is present, compile regexp and return
   276  func parseMultilineOptions(info logger.Info) (*regexp.Regexp, error) {
   277  	dateTimeFormat := info.Config[datetimeFormatKey]
   278  	multilinePatternKey := info.Config[multilinePatternKey]
   279  	// strftime input is parsed into a regular expression
   280  	if dateTimeFormat != "" {
   281  		// %. matches each strftime format sequence and ReplaceAllStringFunc
   282  		// looks up each format sequence in the conversion table strftimeToRegex
   283  		// to replace with a defined regular expression
   284  		r := regexp.MustCompile("%.")
   285  		multilinePatternKey = r.ReplaceAllStringFunc(dateTimeFormat, func(s string) string {
   286  			return strftimeToRegex[s]
   287  		})
   288  	}
   289  	if multilinePatternKey != "" {
   290  		multilinePattern, err := regexp.Compile(multilinePatternKey)
   291  		if err != nil {
   292  			return nil, errors.Wrapf(err, "awslogs could not parse multiline pattern key %q", multilinePatternKey)
   293  		}
   294  		return multilinePattern, nil
   295  	}
   296  	return nil, nil
   297  }
   298  
   299  // Maps strftime format strings to regex
   300  var strftimeToRegex = map[string]string{
   301  	/*weekdayShort          */ `%a`: `(?:Mon|Tue|Wed|Thu|Fri|Sat|Sun)`,
   302  	/*weekdayFull           */ `%A`: `(?:Monday|Tuesday|Wednesday|Thursday|Friday|Saturday|Sunday)`,
   303  	/*weekdayZeroIndex      */ `%w`: `[0-6]`,
   304  	/*dayZeroPadded         */ `%d`: `(?:0[1-9]|[1,2][0-9]|3[0,1])`,
   305  	/*monthShort            */ `%b`: `(?:Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)`,
   306  	/*monthFull             */ `%B`: `(?:January|February|March|April|May|June|July|August|September|October|November|December)`,
   307  	/*monthZeroPadded       */ `%m`: `(?:0[1-9]|1[0-2])`,
   308  	/*yearCentury           */ `%Y`: `\d{4}`,
   309  	/*yearZeroPadded        */ `%y`: `\d{2}`,
   310  	/*hour24ZeroPadded      */ `%H`: `(?:[0,1][0-9]|2[0-3])`,
   311  	/*hour12ZeroPadded      */ `%I`: `(?:0[0-9]|1[0-2])`,
   312  	/*AM or PM              */ `%p`: "[A,P]M",
   313  	/*minuteZeroPadded      */ `%M`: `[0-5][0-9]`,
   314  	/*secondZeroPadded      */ `%S`: `[0-5][0-9]`,
   315  	/*microsecondZeroPadded */ `%f`: `\d{6}`,
   316  	/*utcOffset             */ `%z`: `[+-]\d{4}`,
   317  	/*tzName                */ `%Z`: `[A-Z]{1,4}T`,
   318  	/*dayOfYearZeroPadded   */ `%j`: `(?:0[0-9][1-9]|[1,2][0-9][0-9]|3[0-5][0-9]|36[0-6])`,
   319  	/*milliseconds          */ `%L`: `\.\d{3}`,
   320  }
   321  
   322  // newRegionFinder is a variable such that the implementation
   323  // can be swapped out for unit tests.
   324  var newRegionFinder = func() (regionFinder, error) {
   325  	s, err := session.NewSession()
   326  	if err != nil {
   327  		return nil, err
   328  	}
   329  	return ec2metadata.New(s), nil
   330  }
   331  
   332  // newSDKEndpoint is a variable such that the implementation
   333  // can be swapped out for unit tests.
   334  var newSDKEndpoint = credentialsEndpoint
   335  
   336  // newAWSLogsClient creates the service client for Amazon CloudWatch Logs.
   337  // Customizations to the default client from the SDK include a Docker-specific
   338  // User-Agent string and automatic region detection using the EC2 Instance
   339  // Metadata Service when region is otherwise unspecified.
   340  func newAWSLogsClient(info logger.Info) (api, error) {
   341  	var region, endpoint *string
   342  	if os.Getenv(regionEnvKey) != "" {
   343  		region = aws.String(os.Getenv(regionEnvKey))
   344  	}
   345  	if info.Config[regionKey] != "" {
   346  		region = aws.String(info.Config[regionKey])
   347  	}
   348  	if info.Config[endpointKey] != "" {
   349  		endpoint = aws.String(info.Config[endpointKey])
   350  	}
   351  	if region == nil || *region == "" {
   352  		logrus.Info("Trying to get region from EC2 Metadata")
   353  		ec2MetadataClient, err := newRegionFinder()
   354  		if err != nil {
   355  			logrus.WithError(err).Error("could not create EC2 metadata client")
   356  			return nil, errors.Wrap(err, "could not create EC2 metadata client")
   357  		}
   358  
   359  		r, err := ec2MetadataClient.Region()
   360  		if err != nil {
   361  			logrus.WithError(err).Error("Could not get region from EC2 metadata, environment, or log option")
   362  			return nil, errors.New("Cannot determine region for awslogs driver")
   363  		}
   364  		region = &r
   365  	}
   366  
   367  	sess, err := session.NewSession()
   368  	if err != nil {
   369  		return nil, errors.New("Failed to create a service client session for awslogs driver")
   370  	}
   371  
   372  	// attach region to cloudwatchlogs config
   373  	sess.Config.Region = region
   374  
   375  	// attach endpoint to cloudwatchlogs config
   376  	if endpoint != nil {
   377  		sess.Config.Endpoint = endpoint
   378  	}
   379  
   380  	if uri, ok := info.Config[credentialsEndpointKey]; ok {
   381  		logrus.Debugf("Trying to get credentials from awslogs-credentials-endpoint")
   382  
   383  		endpoint := fmt.Sprintf("%s%s", newSDKEndpoint, uri)
   384  		creds := endpointcreds.NewCredentialsClient(*sess.Config, sess.Handlers, endpoint,
   385  			func(p *endpointcreds.Provider) {
   386  				p.ExpiryWindow = 5 * time.Minute
   387  			})
   388  
   389  		// attach credentials to cloudwatchlogs config
   390  		sess.Config.Credentials = creds
   391  	}
   392  
   393  	logrus.WithFields(logrus.Fields{
   394  		"region": *region,
   395  	}).Debug("Created awslogs client")
   396  
   397  	client := cloudwatchlogs.New(sess)
   398  
   399  	client.Handlers.Build.PushBackNamed(request.NamedHandler{
   400  		Name: "DockerUserAgentHandler",
   401  		Fn: func(r *request.Request) {
   402  			currentAgent := r.HTTPRequest.Header.Get(userAgentHeader)
   403  			r.HTTPRequest.Header.Set(userAgentHeader,
   404  				fmt.Sprintf("Docker %s (%s) %s",
   405  					dockerversion.Version, runtime.GOOS, currentAgent))
   406  		},
   407  	})
   408  
   409  	if info.Config[logFormatKey] != "" {
   410  		client.Handlers.Build.PushBackNamed(request.NamedHandler{
   411  			Name: "LogFormatHeaderHandler",
   412  			Fn: func(req *request.Request) {
   413  				req.HTTPRequest.Header.Set(logsFormatHeader, info.Config[logFormatKey])
   414  			},
   415  		})
   416  	}
   417  
   418  	return client, nil
   419  }
   420  
   421  // Name returns the name of the awslogs logging driver
   422  func (l *logStream) Name() string {
   423  	return name
   424  }
   425  
   426  // BufSize returns the maximum bytes CloudWatch can handle.
   427  func (l *logStream) BufSize() int {
   428  	return maximumBytesPerEvent
   429  }
   430  
   431  // Log submits messages for logging by an instance of the awslogs logging driver
   432  func (l *logStream) Log(msg *logger.Message) error {
   433  	l.lock.RLock()
   434  	defer l.lock.RUnlock()
   435  	if l.closed {
   436  		return errors.New("awslogs is closed")
   437  	}
   438  	l.messages <- msg
   439  	return nil
   440  }
   441  
   442  // Close closes the instance of the awslogs logging driver
   443  func (l *logStream) Close() error {
   444  	l.lock.Lock()
   445  	defer l.lock.Unlock()
   446  	if !l.closed {
   447  		close(l.messages)
   448  	}
   449  	l.closed = true
   450  	return nil
   451  }
   452  
   453  // create creates log group and log stream for the instance of the awslogs logging driver
   454  func (l *logStream) create() error {
   455  	err := l.createLogStream()
   456  	if err == nil {
   457  		return nil
   458  	}
   459  	if awsErr, ok := err.(awserr.Error); ok && awsErr.Code() == resourceNotFoundCode && l.logCreateGroup {
   460  		if err := l.createLogGroup(); err != nil {
   461  			return errors.Wrap(err, "failed to create Cloudwatch log group")
   462  		}
   463  		err = l.createLogStream()
   464  		if err == nil {
   465  			return nil
   466  		}
   467  	}
   468  	return errors.Wrap(err, "failed to create Cloudwatch log stream")
   469  }
   470  
   471  // createLogGroup creates a log group for the instance of the awslogs logging driver
   472  func (l *logStream) createLogGroup() error {
   473  	if _, err := l.client.CreateLogGroup(&cloudwatchlogs.CreateLogGroupInput{
   474  		LogGroupName: aws.String(l.logGroupName),
   475  	}); err != nil {
   476  		if awsErr, ok := err.(awserr.Error); ok {
   477  			fields := logrus.Fields{
   478  				"errorCode":      awsErr.Code(),
   479  				"message":        awsErr.Message(),
   480  				"origError":      awsErr.OrigErr(),
   481  				"logGroupName":   l.logGroupName,
   482  				"logCreateGroup": l.logCreateGroup,
   483  			}
   484  			if awsErr.Code() == resourceAlreadyExistsCode {
   485  				// Allow creation to succeed
   486  				logrus.WithFields(fields).Info("Log group already exists")
   487  				return nil
   488  			}
   489  			logrus.WithFields(fields).Error("Failed to create log group")
   490  		}
   491  		return err
   492  	}
   493  	return nil
   494  }
   495  
   496  // createLogStream creates a log stream for the instance of the awslogs logging driver
   497  func (l *logStream) createLogStream() error {
   498  	// Directly return if we do not want to create log stream.
   499  	if !l.logCreateStream {
   500  		logrus.WithFields(logrus.Fields{
   501  			"logGroupName":    l.logGroupName,
   502  			"logStreamName":   l.logStreamName,
   503  			"logCreateStream": l.logCreateStream,
   504  		}).Info("Skipping creating log stream")
   505  		return nil
   506  	}
   507  
   508  	input := &cloudwatchlogs.CreateLogStreamInput{
   509  		LogGroupName:  aws.String(l.logGroupName),
   510  		LogStreamName: aws.String(l.logStreamName),
   511  	}
   512  
   513  	_, err := l.client.CreateLogStream(input)
   514  
   515  	if err != nil {
   516  		if awsErr, ok := err.(awserr.Error); ok {
   517  			fields := logrus.Fields{
   518  				"errorCode":     awsErr.Code(),
   519  				"message":       awsErr.Message(),
   520  				"origError":     awsErr.OrigErr(),
   521  				"logGroupName":  l.logGroupName,
   522  				"logStreamName": l.logStreamName,
   523  			}
   524  			if awsErr.Code() == resourceAlreadyExistsCode {
   525  				// Allow creation to succeed
   526  				logrus.WithFields(fields).Info("Log stream already exists")
   527  				return nil
   528  			}
   529  			logrus.WithFields(fields).Error("Failed to create log stream")
   530  		}
   531  	}
   532  	return err
   533  }
   534  
   535  // newTicker is used for time-based batching.  newTicker is a variable such
   536  // that the implementation can be swapped out for unit tests.
   537  var newTicker = func(freq time.Duration) *time.Ticker {
   538  	return time.NewTicker(freq)
   539  }
   540  
   541  // collectBatch executes as a goroutine to perform batching of log events for
   542  // submission to the log stream.  If the awslogs-multiline-pattern or
   543  // awslogs-datetime-format options have been configured, multiline processing
   544  // is enabled, where log messages are stored in an event buffer until a multiline
   545  // pattern match is found, at which point the messages in the event buffer are
   546  // pushed to CloudWatch logs as a single log event.  Multiline messages are processed
   547  // according to the maximumBytesPerPut constraint, and the implementation only
   548  // allows for messages to be buffered for a maximum of 2*batchPublishFrequency
   549  // seconds.  When events are ready to be processed for submission to CloudWatch
   550  // Logs, the processEvents method is called.  If a multiline pattern is not
   551  // configured, log events are submitted to the processEvents method immediately.
   552  func (l *logStream) collectBatch(created chan bool) {
   553  	// Wait for the logstream/group to be created
   554  	<-created
   555  	flushInterval := l.forceFlushInterval
   556  	if flushInterval <= 0 {
   557  		flushInterval = defaultForceFlushInterval
   558  	}
   559  	ticker := newTicker(flushInterval)
   560  	var eventBuffer []byte
   561  	var eventBufferTimestamp int64
   562  	var batch = newEventBatch()
   563  	for {
   564  		select {
   565  		case t := <-ticker.C:
   566  			// If event buffer is older than batch publish frequency flush the event buffer
   567  			if eventBufferTimestamp > 0 && len(eventBuffer) > 0 {
   568  				eventBufferAge := t.UnixNano()/int64(time.Millisecond) - eventBufferTimestamp
   569  				eventBufferExpired := eventBufferAge >= int64(flushInterval)/int64(time.Millisecond)
   570  				eventBufferNegative := eventBufferAge < 0
   571  				if eventBufferExpired || eventBufferNegative {
   572  					l.processEvent(batch, eventBuffer, eventBufferTimestamp)
   573  					eventBuffer = eventBuffer[:0]
   574  				}
   575  			}
   576  			l.publishBatch(batch)
   577  			batch.reset()
   578  		case msg, more := <-l.messages:
   579  			if !more {
   580  				// Flush event buffer and release resources
   581  				l.processEvent(batch, eventBuffer, eventBufferTimestamp)
   582  				l.publishBatch(batch)
   583  				batch.reset()
   584  				return
   585  			}
   586  			if eventBufferTimestamp == 0 {
   587  				eventBufferTimestamp = msg.Timestamp.UnixNano() / int64(time.Millisecond)
   588  			}
   589  			line := msg.Line
   590  			if l.multilinePattern != nil {
   591  				lineEffectiveLen := effectiveLen(string(line))
   592  				if l.multilinePattern.Match(line) || effectiveLen(string(eventBuffer))+lineEffectiveLen > maximumBytesPerEvent {
   593  					// This is a new log event or we will exceed max bytes per event
   594  					// so flush the current eventBuffer to events and reset timestamp
   595  					l.processEvent(batch, eventBuffer, eventBufferTimestamp)
   596  					eventBufferTimestamp = msg.Timestamp.UnixNano() / int64(time.Millisecond)
   597  					eventBuffer = eventBuffer[:0]
   598  				}
   599  				// Append newline if event is less than max event size
   600  				if lineEffectiveLen < maximumBytesPerEvent {
   601  					line = append(line, "\n"...)
   602  				}
   603  				eventBuffer = append(eventBuffer, line...)
   604  				logger.PutMessage(msg)
   605  			} else {
   606  				l.processEvent(batch, line, msg.Timestamp.UnixNano()/int64(time.Millisecond))
   607  				logger.PutMessage(msg)
   608  			}
   609  		}
   610  	}
   611  }
   612  
   613  // processEvent processes log events that are ready for submission to CloudWatch
   614  // logs.  Batching is performed on time- and size-bases.  Time-based batching
   615  // occurs at a 5 second interval (defined in the batchPublishFrequency const).
   616  // Size-based batching is performed on the maximum number of events per batch
   617  // (defined in maximumLogEventsPerPut) and the maximum number of total bytes in a
   618  // batch (defined in maximumBytesPerPut).  Log messages are split by the maximum
   619  // bytes per event (defined in maximumBytesPerEvent).  There is a fixed per-event
   620  // byte overhead (defined in perEventBytes) which is accounted for in split- and
   621  // batch-calculations.  Because the events are interpreted as UTF-8 encoded
   622  // Unicode, invalid UTF-8 byte sequences are replaced with the Unicode
   623  // replacement character (U+FFFD), which is a 3-byte sequence in UTF-8.  To
   624  // compensate for that and to avoid splitting valid UTF-8 characters into
   625  // invalid byte sequences, we calculate the length of each event assuming that
   626  // this replacement happens.
   627  func (l *logStream) processEvent(batch *eventBatch, bytes []byte, timestamp int64) {
   628  	for len(bytes) > 0 {
   629  		// Split line length so it does not exceed the maximum
   630  		splitOffset, lineBytes := findValidSplit(string(bytes), maximumBytesPerEvent)
   631  		line := bytes[:splitOffset]
   632  		event := wrappedEvent{
   633  			inputLogEvent: &cloudwatchlogs.InputLogEvent{
   634  				Message:   aws.String(string(line)),
   635  				Timestamp: aws.Int64(timestamp),
   636  			},
   637  			insertOrder: batch.count(),
   638  		}
   639  
   640  		added := batch.add(event, lineBytes)
   641  		if added {
   642  			bytes = bytes[splitOffset:]
   643  		} else {
   644  			l.publishBatch(batch)
   645  			batch.reset()
   646  		}
   647  	}
   648  }
   649  
   650  // effectiveLen counts the effective number of bytes in the string, after
   651  // UTF-8 normalization.  UTF-8 normalization includes replacing bytes that do
   652  // not constitute valid UTF-8 encoded Unicode codepoints with the Unicode
   653  // replacement codepoint U+FFFD (a 3-byte UTF-8 sequence, represented in Go as
   654  // utf8.RuneError)
   655  func effectiveLen(line string) int {
   656  	effectiveBytes := 0
   657  	for _, rune := range line {
   658  		effectiveBytes += utf8.RuneLen(rune)
   659  	}
   660  	return effectiveBytes
   661  }
   662  
   663  // findValidSplit finds the byte offset to split a string without breaking valid
   664  // Unicode codepoints given a maximum number of total bytes.  findValidSplit
   665  // returns the byte offset for splitting a string or []byte, as well as the
   666  // effective number of bytes if the string were normalized to replace invalid
   667  // UTF-8 encoded bytes with the Unicode replacement character (a 3-byte UTF-8
   668  // sequence, represented in Go as utf8.RuneError)
   669  func findValidSplit(line string, maxBytes int) (splitOffset, effectiveBytes int) {
   670  	for offset, rune := range line {
   671  		splitOffset = offset
   672  		if effectiveBytes+utf8.RuneLen(rune) > maxBytes {
   673  			return splitOffset, effectiveBytes
   674  		}
   675  		effectiveBytes += utf8.RuneLen(rune)
   676  	}
   677  	splitOffset = len(line)
   678  	return
   679  }
   680  
   681  // publishBatch calls PutLogEvents for a given set of InputLogEvents,
   682  // accounting for sequencing requirements (each request must reference the
   683  // sequence token returned by the previous request).
   684  func (l *logStream) publishBatch(batch *eventBatch) {
   685  	if batch.isEmpty() {
   686  		return
   687  	}
   688  	cwEvents := unwrapEvents(batch.events())
   689  
   690  	nextSequenceToken, err := l.putLogEvents(cwEvents, l.sequenceToken)
   691  
   692  	if err != nil {
   693  		if awsErr, ok := err.(awserr.Error); ok {
   694  			if awsErr.Code() == dataAlreadyAcceptedCode {
   695  				// already submitted, just grab the correct sequence token
   696  				parts := strings.Split(awsErr.Message(), " ")
   697  				nextSequenceToken = &parts[len(parts)-1]
   698  				logrus.WithFields(logrus.Fields{
   699  					"errorCode":     awsErr.Code(),
   700  					"message":       awsErr.Message(),
   701  					"logGroupName":  l.logGroupName,
   702  					"logStreamName": l.logStreamName,
   703  				}).Info("Data already accepted, ignoring error")
   704  				err = nil
   705  			} else if awsErr.Code() == invalidSequenceTokenCode {
   706  				// sequence code is bad, grab the correct one and retry
   707  				parts := strings.Split(awsErr.Message(), " ")
   708  				token := parts[len(parts)-1]
   709  				nextSequenceToken, err = l.putLogEvents(cwEvents, &token)
   710  			}
   711  		}
   712  	}
   713  	if err != nil {
   714  		logrus.Error(err)
   715  	} else {
   716  		l.sequenceToken = nextSequenceToken
   717  	}
   718  }
   719  
   720  // putLogEvents wraps the PutLogEvents API
   721  func (l *logStream) putLogEvents(events []*cloudwatchlogs.InputLogEvent, sequenceToken *string) (*string, error) {
   722  	input := &cloudwatchlogs.PutLogEventsInput{
   723  		LogEvents:     events,
   724  		SequenceToken: sequenceToken,
   725  		LogGroupName:  aws.String(l.logGroupName),
   726  		LogStreamName: aws.String(l.logStreamName),
   727  	}
   728  	resp, err := l.client.PutLogEvents(input)
   729  	if err != nil {
   730  		if awsErr, ok := err.(awserr.Error); ok {
   731  			logrus.WithFields(logrus.Fields{
   732  				"errorCode":     awsErr.Code(),
   733  				"message":       awsErr.Message(),
   734  				"origError":     awsErr.OrigErr(),
   735  				"logGroupName":  l.logGroupName,
   736  				"logStreamName": l.logStreamName,
   737  			}).Error("Failed to put log events")
   738  		}
   739  		return nil, err
   740  	}
   741  	return resp.NextSequenceToken, nil
   742  }
   743  
   744  // ValidateLogOpt looks for awslogs-specific log options awslogs-region, awslogs-endpoint
   745  // awslogs-group, awslogs-stream, awslogs-create-group, awslogs-datetime-format,
   746  // awslogs-multiline-pattern
   747  func ValidateLogOpt(cfg map[string]string) error {
   748  	for key := range cfg {
   749  		switch key {
   750  		case logGroupKey:
   751  		case logStreamKey:
   752  		case logCreateGroupKey:
   753  		case regionKey:
   754  		case endpointKey:
   755  		case tagKey:
   756  		case datetimeFormatKey:
   757  		case multilinePatternKey:
   758  		case credentialsEndpointKey:
   759  		case forceFlushIntervalKey:
   760  		case maxBufferedEventsKey:
   761  		case logFormatKey:
   762  		default:
   763  			return fmt.Errorf("unknown log opt '%s' for %s log driver", key, name)
   764  		}
   765  	}
   766  	if cfg[logGroupKey] == "" {
   767  		return fmt.Errorf("must specify a value for log opt '%s'", logGroupKey)
   768  	}
   769  	if cfg[logCreateGroupKey] != "" {
   770  		if _, err := strconv.ParseBool(cfg[logCreateGroupKey]); err != nil {
   771  			return fmt.Errorf("must specify valid value for log opt '%s': %v", logCreateGroupKey, err)
   772  		}
   773  	}
   774  	if cfg[forceFlushIntervalKey] != "" {
   775  		if value, err := strconv.Atoi(cfg[forceFlushIntervalKey]); err != nil || value <= 0 {
   776  			return fmt.Errorf("must specify a positive integer for log opt '%s': %v", forceFlushIntervalKey, cfg[forceFlushIntervalKey])
   777  		}
   778  	}
   779  	if cfg[maxBufferedEventsKey] != "" {
   780  		if value, err := strconv.Atoi(cfg[maxBufferedEventsKey]); err != nil || value <= 0 {
   781  			return fmt.Errorf("must specify a positive integer for log opt '%s': %v", maxBufferedEventsKey, cfg[maxBufferedEventsKey])
   782  		}
   783  	}
   784  	_, datetimeFormatKeyExists := cfg[datetimeFormatKey]
   785  	_, multilinePatternKeyExists := cfg[multilinePatternKey]
   786  	if datetimeFormatKeyExists && multilinePatternKeyExists {
   787  		return fmt.Errorf("you cannot configure log opt '%s' and '%s' at the same time", datetimeFormatKey, multilinePatternKey)
   788  	}
   789  
   790  	if cfg[logFormatKey] != "" {
   791  		// For now, only the "json/emf" log format is supported
   792  		if cfg[logFormatKey] != jsonEmfLogFormat {
   793  			return fmt.Errorf("unsupported log format '%s'", cfg[logFormatKey])
   794  		}
   795  		if datetimeFormatKeyExists || multilinePatternKeyExists {
   796  			return fmt.Errorf("you cannot configure log opt '%s' or '%s' when log opt '%s' is set to '%s'", datetimeFormatKey, multilinePatternKey, logFormatKey, jsonEmfLogFormat)
   797  		}
   798  	}
   799  
   800  	return nil
   801  }
   802  
   803  // Len returns the length of a byTimestamp slice.  Len is required by the
   804  // sort.Interface interface.
   805  func (slice byTimestamp) Len() int {
   806  	return len(slice)
   807  }
   808  
   809  // Less compares two values in a byTimestamp slice by Timestamp.  Less is
   810  // required by the sort.Interface interface.
   811  func (slice byTimestamp) Less(i, j int) bool {
   812  	iTimestamp, jTimestamp := int64(0), int64(0)
   813  	if slice != nil && slice[i].inputLogEvent.Timestamp != nil {
   814  		iTimestamp = *slice[i].inputLogEvent.Timestamp
   815  	}
   816  	if slice != nil && slice[j].inputLogEvent.Timestamp != nil {
   817  		jTimestamp = *slice[j].inputLogEvent.Timestamp
   818  	}
   819  	if iTimestamp == jTimestamp {
   820  		return slice[i].insertOrder < slice[j].insertOrder
   821  	}
   822  	return iTimestamp < jTimestamp
   823  }
   824  
   825  // Swap swaps two values in a byTimestamp slice with each other.  Swap is
   826  // required by the sort.Interface interface.
   827  func (slice byTimestamp) Swap(i, j int) {
   828  	slice[i], slice[j] = slice[j], slice[i]
   829  }
   830  
   831  func unwrapEvents(events []wrappedEvent) []*cloudwatchlogs.InputLogEvent {
   832  	cwEvents := make([]*cloudwatchlogs.InputLogEvent, len(events))
   833  	for i, input := range events {
   834  		cwEvents[i] = input.inputLogEvent
   835  	}
   836  	return cwEvents
   837  }
   838  
   839  func newEventBatch() *eventBatch {
   840  	return &eventBatch{
   841  		batch: make([]wrappedEvent, 0),
   842  		bytes: 0,
   843  	}
   844  }
   845  
   846  // events returns a slice of wrappedEvents sorted in order of their
   847  // timestamps and then by their insertion order (see `byTimestamp`).
   848  //
   849  // Warning: this method is not threadsafe and must not be used
   850  // concurrently.
   851  func (b *eventBatch) events() []wrappedEvent {
   852  	sort.Sort(byTimestamp(b.batch))
   853  	return b.batch
   854  }
   855  
   856  // add adds an event to the batch of events accounting for the
   857  // necessary overhead for an event to be logged. An error will be
   858  // returned if the event cannot be added to the batch due to service
   859  // limits.
   860  //
   861  // Warning: this method is not threadsafe and must not be used
   862  // concurrently.
   863  func (b *eventBatch) add(event wrappedEvent, size int) bool {
   864  	addBytes := size + perEventBytes
   865  
   866  	// verify we are still within service limits
   867  	switch {
   868  	case len(b.batch)+1 > maximumLogEventsPerPut:
   869  		return false
   870  	case b.bytes+addBytes > maximumBytesPerPut:
   871  		return false
   872  	}
   873  
   874  	b.bytes += addBytes
   875  	b.batch = append(b.batch, event)
   876  
   877  	return true
   878  }
   879  
   880  // count is the number of batched events.  Warning: this method
   881  // is not threadsafe and must not be used concurrently.
   882  func (b *eventBatch) count() int {
   883  	return len(b.batch)
   884  }
   885  
   886  // size is the total number of bytes that the batch represents.
   887  //
   888  // Warning: this method is not threadsafe and must not be used
   889  // concurrently.
   890  func (b *eventBatch) size() int {
   891  	return b.bytes
   892  }
   893  
   894  func (b *eventBatch) isEmpty() bool {
   895  	zeroEvents := b.count() == 0
   896  	zeroSize := b.size() == 0
   897  	return zeroEvents && zeroSize
   898  }
   899  
   900  // reset prepares the batch for reuse.
   901  func (b *eventBatch) reset() {
   902  	b.bytes = 0
   903  	b.batch = b.batch[:0]
   904  }