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