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 }