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