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