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