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 }