github.com/kaisenlinux/docker.io@v0.0.0-20230510090727-ea55db55fac7/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 "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" //nolint:gosec // G101: Potential hardcoded credentials 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" //nolint:gosec // G101: Potential hardcoded credentials 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, error) { 312 s, err := session.NewSession() 313 if err != nil { 314 return nil, err 315 } 316 return ec2metadata.New(s), nil 317 } 318 319 // newSDKEndpoint is a variable such that the implementation 320 // can be swapped out for unit tests. 321 var newSDKEndpoint = credentialsEndpoint 322 323 // newAWSLogsClient creates the service client for Amazon CloudWatch Logs. 324 // Customizations to the default client from the SDK include a Docker-specific 325 // User-Agent string and automatic region detection using the EC2 Instance 326 // Metadata Service when region is otherwise unspecified. 327 func newAWSLogsClient(info logger.Info) (api, error) { 328 var region, endpoint *string 329 if os.Getenv(regionEnvKey) != "" { 330 region = aws.String(os.Getenv(regionEnvKey)) 331 } 332 if info.Config[regionKey] != "" { 333 region = aws.String(info.Config[regionKey]) 334 } 335 if info.Config[endpointKey] != "" { 336 endpoint = aws.String(info.Config[endpointKey]) 337 } 338 if region == nil || *region == "" { 339 logrus.Info("Trying to get region from EC2 Metadata") 340 ec2MetadataClient, err := newRegionFinder() 341 if err != nil { 342 logrus.WithError(err).Error("could not create EC2 metadata client") 343 return nil, errors.Wrap(err, "could not create EC2 metadata client") 344 } 345 346 r, err := ec2MetadataClient.Region() 347 if err != nil { 348 logrus.WithError(err).Error("Could not get region from EC2 metadata, environment, or log option") 349 return nil, errors.New("Cannot determine region for awslogs driver") 350 } 351 region = &r 352 } 353 354 sess, err := session.NewSession() 355 if err != nil { 356 return nil, errors.New("Failed to create a service client session for awslogs driver") 357 } 358 359 // attach region to cloudwatchlogs config 360 sess.Config.Region = region 361 362 // attach endpoint to cloudwatchlogs config 363 if endpoint != nil { 364 sess.Config.Endpoint = endpoint 365 } 366 367 if uri, ok := info.Config[credentialsEndpointKey]; ok { 368 logrus.Debugf("Trying to get credentials from awslogs-credentials-endpoint") 369 370 endpoint := fmt.Sprintf("%s%s", newSDKEndpoint, uri) 371 creds := endpointcreds.NewCredentialsClient(*sess.Config, sess.Handlers, endpoint, 372 func(p *endpointcreds.Provider) { 373 p.ExpiryWindow = 5 * time.Minute 374 }) 375 376 // attach credentials to cloudwatchlogs config 377 sess.Config.Credentials = creds 378 } 379 380 logrus.WithFields(logrus.Fields{ 381 "region": *region, 382 }).Debug("Created awslogs client") 383 384 client := cloudwatchlogs.New(sess) 385 386 client.Handlers.Build.PushBackNamed(request.NamedHandler{ 387 Name: "DockerUserAgentHandler", 388 Fn: func(r *request.Request) { 389 currentAgent := r.HTTPRequest.Header.Get(userAgentHeader) 390 r.HTTPRequest.Header.Set(userAgentHeader, 391 fmt.Sprintf("Docker %s (%s) %s", 392 dockerversion.Version, runtime.GOOS, currentAgent)) 393 }, 394 }) 395 return client, nil 396 } 397 398 // Name returns the name of the awslogs logging driver 399 func (l *logStream) Name() string { 400 return name 401 } 402 403 // BufSize returns the maximum bytes CloudWatch can handle. 404 func (l *logStream) BufSize() int { 405 return maximumBytesPerEvent 406 } 407 408 // Log submits messages for logging by an instance of the awslogs logging driver 409 func (l *logStream) Log(msg *logger.Message) error { 410 l.lock.RLock() 411 defer l.lock.RUnlock() 412 if l.closed { 413 return errors.New("awslogs is closed") 414 } 415 if l.logNonBlocking { 416 select { 417 case l.messages <- msg: 418 return nil 419 default: 420 return errors.New("awslogs buffer is full") 421 } 422 } 423 l.messages <- msg 424 return nil 425 } 426 427 // Close closes the instance of the awslogs logging driver 428 func (l *logStream) Close() error { 429 l.lock.Lock() 430 defer l.lock.Unlock() 431 if !l.closed { 432 close(l.messages) 433 } 434 l.closed = true 435 return nil 436 } 437 438 // create creates log group and log stream for the instance of the awslogs logging driver 439 func (l *logStream) create() error { 440 err := l.createLogStream() 441 if err == nil { 442 return nil 443 } 444 if awsErr, ok := err.(awserr.Error); ok && awsErr.Code() == resourceNotFoundCode && l.logCreateGroup { 445 if err := l.createLogGroup(); err != nil { 446 return errors.Wrap(err, "failed to create Cloudwatch log group") 447 } 448 err = l.createLogStream() 449 if err == nil { 450 return nil 451 } 452 } 453 return errors.Wrap(err, "failed to create Cloudwatch log stream") 454 } 455 456 // createLogGroup creates a log group for the instance of the awslogs logging driver 457 func (l *logStream) createLogGroup() error { 458 if _, err := l.client.CreateLogGroup(&cloudwatchlogs.CreateLogGroupInput{ 459 LogGroupName: aws.String(l.logGroupName), 460 }); err != nil { 461 if awsErr, ok := err.(awserr.Error); ok { 462 fields := logrus.Fields{ 463 "errorCode": awsErr.Code(), 464 "message": awsErr.Message(), 465 "origError": awsErr.OrigErr(), 466 "logGroupName": l.logGroupName, 467 "logCreateGroup": l.logCreateGroup, 468 } 469 if awsErr.Code() == resourceAlreadyExistsCode { 470 // Allow creation to succeed 471 logrus.WithFields(fields).Info("Log group already exists") 472 return nil 473 } 474 logrus.WithFields(fields).Error("Failed to create log group") 475 } 476 return err 477 } 478 return nil 479 } 480 481 // createLogStream creates a log stream for the instance of the awslogs logging driver 482 func (l *logStream) createLogStream() error { 483 input := &cloudwatchlogs.CreateLogStreamInput{ 484 LogGroupName: aws.String(l.logGroupName), 485 LogStreamName: aws.String(l.logStreamName), 486 } 487 488 _, err := l.client.CreateLogStream(input) 489 490 if err != nil { 491 if awsErr, ok := err.(awserr.Error); ok { 492 fields := logrus.Fields{ 493 "errorCode": awsErr.Code(), 494 "message": awsErr.Message(), 495 "origError": awsErr.OrigErr(), 496 "logGroupName": l.logGroupName, 497 "logStreamName": l.logStreamName, 498 } 499 if awsErr.Code() == resourceAlreadyExistsCode { 500 // Allow creation to succeed 501 logrus.WithFields(fields).Info("Log stream already exists") 502 return nil 503 } 504 logrus.WithFields(fields).Error("Failed to create log stream") 505 } 506 } 507 return err 508 } 509 510 // newTicker is used for time-based batching. newTicker is a variable such 511 // that the implementation can be swapped out for unit tests. 512 var newTicker = func(freq time.Duration) *time.Ticker { 513 return time.NewTicker(freq) 514 } 515 516 // collectBatch executes as a goroutine to perform batching of log events for 517 // submission to the log stream. If the awslogs-multiline-pattern or 518 // awslogs-datetime-format options have been configured, multiline processing 519 // is enabled, where log messages are stored in an event buffer until a multiline 520 // pattern match is found, at which point the messages in the event buffer are 521 // pushed to CloudWatch logs as a single log event. Multiline messages are processed 522 // according to the maximumBytesPerPut constraint, and the implementation only 523 // allows for messages to be buffered for a maximum of 2*batchPublishFrequency 524 // seconds. When events are ready to be processed for submission to CloudWatch 525 // Logs, the processEvents method is called. If a multiline pattern is not 526 // configured, log events are submitted to the processEvents method immediately. 527 func (l *logStream) collectBatch(created chan bool) { 528 // Wait for the logstream/group to be created 529 <-created 530 flushInterval := l.forceFlushInterval 531 if flushInterval <= 0 { 532 flushInterval = defaultForceFlushInterval 533 } 534 ticker := newTicker(flushInterval) 535 var eventBuffer []byte 536 var eventBufferTimestamp int64 537 var batch = newEventBatch() 538 for { 539 select { 540 case t := <-ticker.C: 541 // If event buffer is older than batch publish frequency flush the event buffer 542 if eventBufferTimestamp > 0 && len(eventBuffer) > 0 { 543 eventBufferAge := t.UnixNano()/int64(time.Millisecond) - eventBufferTimestamp 544 eventBufferExpired := eventBufferAge >= int64(flushInterval)/int64(time.Millisecond) 545 eventBufferNegative := eventBufferAge < 0 546 if eventBufferExpired || eventBufferNegative { 547 l.processEvent(batch, eventBuffer, eventBufferTimestamp) 548 eventBuffer = eventBuffer[:0] 549 } 550 } 551 l.publishBatch(batch) 552 batch.reset() 553 case msg, more := <-l.messages: 554 if !more { 555 // Flush event buffer and release resources 556 l.processEvent(batch, eventBuffer, eventBufferTimestamp) 557 l.publishBatch(batch) 558 batch.reset() 559 return 560 } 561 if eventBufferTimestamp == 0 { 562 eventBufferTimestamp = msg.Timestamp.UnixNano() / int64(time.Millisecond) 563 } 564 line := msg.Line 565 if l.multilinePattern != nil { 566 lineEffectiveLen := effectiveLen(string(line)) 567 if l.multilinePattern.Match(line) || effectiveLen(string(eventBuffer))+lineEffectiveLen > maximumBytesPerEvent { 568 // This is a new log event or we will exceed max bytes per event 569 // so flush the current eventBuffer to events and reset timestamp 570 l.processEvent(batch, eventBuffer, eventBufferTimestamp) 571 eventBufferTimestamp = msg.Timestamp.UnixNano() / int64(time.Millisecond) 572 eventBuffer = eventBuffer[:0] 573 } 574 // Append newline if event is less than max event size 575 if lineEffectiveLen < maximumBytesPerEvent { 576 line = append(line, "\n"...) 577 } 578 eventBuffer = append(eventBuffer, line...) 579 logger.PutMessage(msg) 580 } else { 581 l.processEvent(batch, line, msg.Timestamp.UnixNano()/int64(time.Millisecond)) 582 logger.PutMessage(msg) 583 } 584 } 585 } 586 } 587 588 // processEvent processes log events that are ready for submission to CloudWatch 589 // logs. Batching is performed on time- and size-bases. Time-based batching 590 // occurs at a 5 second interval (defined in the batchPublishFrequency const). 591 // Size-based batching is performed on the maximum number of events per batch 592 // (defined in maximumLogEventsPerPut) and the maximum number of total bytes in a 593 // batch (defined in maximumBytesPerPut). Log messages are split by the maximum 594 // bytes per event (defined in maximumBytesPerEvent). There is a fixed per-event 595 // byte overhead (defined in perEventBytes) which is accounted for in split- and 596 // batch-calculations. Because the events are interpreted as UTF-8 encoded 597 // Unicode, invalid UTF-8 byte sequences are replaced with the Unicode 598 // replacement character (U+FFFD), which is a 3-byte sequence in UTF-8. To 599 // compensate for that and to avoid splitting valid UTF-8 characters into 600 // invalid byte sequences, we calculate the length of each event assuming that 601 // this replacement happens. 602 func (l *logStream) processEvent(batch *eventBatch, bytes []byte, timestamp int64) { 603 for len(bytes) > 0 { 604 // Split line length so it does not exceed the maximum 605 splitOffset, lineBytes := findValidSplit(string(bytes), maximumBytesPerEvent) 606 line := bytes[:splitOffset] 607 event := wrappedEvent{ 608 inputLogEvent: &cloudwatchlogs.InputLogEvent{ 609 Message: aws.String(string(line)), 610 Timestamp: aws.Int64(timestamp), 611 }, 612 insertOrder: batch.count(), 613 } 614 615 added := batch.add(event, lineBytes) 616 if added { 617 bytes = bytes[splitOffset:] 618 } else { 619 l.publishBatch(batch) 620 batch.reset() 621 } 622 } 623 } 624 625 // effectiveLen counts the effective number of bytes in the string, after 626 // UTF-8 normalization. UTF-8 normalization includes replacing bytes that do 627 // not constitute valid UTF-8 encoded Unicode codepoints with the Unicode 628 // replacement codepoint U+FFFD (a 3-byte UTF-8 sequence, represented in Go as 629 // utf8.RuneError) 630 func effectiveLen(line string) int { 631 effectiveBytes := 0 632 for _, rune := range line { 633 effectiveBytes += utf8.RuneLen(rune) 634 } 635 return effectiveBytes 636 } 637 638 // findValidSplit finds the byte offset to split a string without breaking valid 639 // Unicode codepoints given a maximum number of total bytes. findValidSplit 640 // returns the byte offset for splitting a string or []byte, as well as the 641 // effective number of bytes if the string were normalized to replace invalid 642 // UTF-8 encoded bytes with the Unicode replacement character (a 3-byte UTF-8 643 // sequence, represented in Go as utf8.RuneError) 644 func findValidSplit(line string, maxBytes int) (splitOffset, effectiveBytes int) { 645 for offset, rune := range line { 646 splitOffset = offset 647 if effectiveBytes+utf8.RuneLen(rune) > maxBytes { 648 return splitOffset, effectiveBytes 649 } 650 effectiveBytes += utf8.RuneLen(rune) 651 } 652 splitOffset = len(line) 653 return 654 } 655 656 // publishBatch calls PutLogEvents for a given set of InputLogEvents, 657 // accounting for sequencing requirements (each request must reference the 658 // sequence token returned by the previous request). 659 func (l *logStream) publishBatch(batch *eventBatch) { 660 if batch.isEmpty() { 661 return 662 } 663 cwEvents := unwrapEvents(batch.events()) 664 665 nextSequenceToken, err := l.putLogEvents(cwEvents, l.sequenceToken) 666 667 if err != nil { 668 if awsErr, ok := err.(awserr.Error); ok { 669 if awsErr.Code() == dataAlreadyAcceptedCode { 670 // already submitted, just grab the correct sequence token 671 parts := strings.Split(awsErr.Message(), " ") 672 nextSequenceToken = &parts[len(parts)-1] 673 logrus.WithFields(logrus.Fields{ 674 "errorCode": awsErr.Code(), 675 "message": awsErr.Message(), 676 "logGroupName": l.logGroupName, 677 "logStreamName": l.logStreamName, 678 }).Info("Data already accepted, ignoring error") 679 err = nil 680 } else if awsErr.Code() == invalidSequenceTokenCode { 681 // sequence code is bad, grab the correct one and retry 682 parts := strings.Split(awsErr.Message(), " ") 683 token := parts[len(parts)-1] 684 nextSequenceToken, err = l.putLogEvents(cwEvents, &token) 685 } 686 } 687 } 688 if err != nil { 689 logrus.Error(err) 690 } else { 691 l.sequenceToken = nextSequenceToken 692 } 693 } 694 695 // putLogEvents wraps the PutLogEvents API 696 func (l *logStream) putLogEvents(events []*cloudwatchlogs.InputLogEvent, sequenceToken *string) (*string, error) { 697 input := &cloudwatchlogs.PutLogEventsInput{ 698 LogEvents: events, 699 SequenceToken: sequenceToken, 700 LogGroupName: aws.String(l.logGroupName), 701 LogStreamName: aws.String(l.logStreamName), 702 } 703 resp, err := l.client.PutLogEvents(input) 704 if err != nil { 705 if awsErr, ok := err.(awserr.Error); ok { 706 logrus.WithFields(logrus.Fields{ 707 "errorCode": awsErr.Code(), 708 "message": awsErr.Message(), 709 "origError": awsErr.OrigErr(), 710 "logGroupName": l.logGroupName, 711 "logStreamName": l.logStreamName, 712 }).Error("Failed to put log events") 713 } 714 return nil, err 715 } 716 return resp.NextSequenceToken, nil 717 } 718 719 // ValidateLogOpt looks for awslogs-specific log options awslogs-region, awslogs-endpoint 720 // awslogs-group, awslogs-stream, awslogs-create-group, awslogs-datetime-format, 721 // awslogs-multiline-pattern 722 func ValidateLogOpt(cfg map[string]string) error { 723 for key := range cfg { 724 switch key { 725 case logGroupKey: 726 case logStreamKey: 727 case logCreateGroupKey: 728 case regionKey: 729 case endpointKey: 730 case tagKey: 731 case datetimeFormatKey: 732 case multilinePatternKey: 733 case credentialsEndpointKey: 734 case forceFlushIntervalKey: 735 case maxBufferedEventsKey: 736 default: 737 return fmt.Errorf("unknown log opt '%s' for %s log driver", key, name) 738 } 739 } 740 if cfg[logGroupKey] == "" { 741 return fmt.Errorf("must specify a value for log opt '%s'", logGroupKey) 742 } 743 if cfg[logCreateGroupKey] != "" { 744 if _, err := strconv.ParseBool(cfg[logCreateGroupKey]); err != nil { 745 return fmt.Errorf("must specify valid value for log opt '%s': %v", logCreateGroupKey, err) 746 } 747 } 748 if cfg[forceFlushIntervalKey] != "" { 749 if value, err := strconv.Atoi(cfg[forceFlushIntervalKey]); err != nil || value <= 0 { 750 return fmt.Errorf("must specify a positive integer for log opt '%s': %v", forceFlushIntervalKey, cfg[forceFlushIntervalKey]) 751 } 752 } 753 if cfg[maxBufferedEventsKey] != "" { 754 if value, err := strconv.Atoi(cfg[maxBufferedEventsKey]); err != nil || value <= 0 { 755 return fmt.Errorf("must specify a positive integer for log opt '%s': %v", maxBufferedEventsKey, cfg[maxBufferedEventsKey]) 756 } 757 } 758 _, datetimeFormatKeyExists := cfg[datetimeFormatKey] 759 _, multilinePatternKeyExists := cfg[multilinePatternKey] 760 if datetimeFormatKeyExists && multilinePatternKeyExists { 761 return fmt.Errorf("you cannot configure log opt '%s' and '%s' at the same time", datetimeFormatKey, multilinePatternKey) 762 } 763 return nil 764 } 765 766 // Len returns the length of a byTimestamp slice. Len is required by the 767 // sort.Interface interface. 768 func (slice byTimestamp) Len() int { 769 return len(slice) 770 } 771 772 // Less compares two values in a byTimestamp slice by Timestamp. Less is 773 // required by the sort.Interface interface. 774 func (slice byTimestamp) Less(i, j int) bool { 775 iTimestamp, jTimestamp := int64(0), int64(0) 776 if slice != nil && slice[i].inputLogEvent.Timestamp != nil { 777 iTimestamp = *slice[i].inputLogEvent.Timestamp 778 } 779 if slice != nil && slice[j].inputLogEvent.Timestamp != nil { 780 jTimestamp = *slice[j].inputLogEvent.Timestamp 781 } 782 if iTimestamp == jTimestamp { 783 return slice[i].insertOrder < slice[j].insertOrder 784 } 785 return iTimestamp < jTimestamp 786 } 787 788 // Swap swaps two values in a byTimestamp slice with each other. Swap is 789 // required by the sort.Interface interface. 790 func (slice byTimestamp) Swap(i, j int) { 791 slice[i], slice[j] = slice[j], slice[i] 792 } 793 794 func unwrapEvents(events []wrappedEvent) []*cloudwatchlogs.InputLogEvent { 795 cwEvents := make([]*cloudwatchlogs.InputLogEvent, len(events)) 796 for i, input := range events { 797 cwEvents[i] = input.inputLogEvent 798 } 799 return cwEvents 800 } 801 802 func newEventBatch() *eventBatch { 803 return &eventBatch{ 804 batch: make([]wrappedEvent, 0), 805 bytes: 0, 806 } 807 } 808 809 // events returns a slice of wrappedEvents sorted in order of their 810 // timestamps and then by their insertion order (see `byTimestamp`). 811 // 812 // Warning: this method is not threadsafe and must not be used 813 // concurrently. 814 func (b *eventBatch) events() []wrappedEvent { 815 sort.Sort(byTimestamp(b.batch)) 816 return b.batch 817 } 818 819 // add adds an event to the batch of events accounting for the 820 // necessary overhead for an event to be logged. An error will be 821 // returned if the event cannot be added to the batch due to service 822 // limits. 823 // 824 // Warning: this method is not threadsafe and must not be used 825 // concurrently. 826 func (b *eventBatch) add(event wrappedEvent, size int) bool { 827 addBytes := size + perEventBytes 828 829 // verify we are still within service limits 830 switch { 831 case len(b.batch)+1 > maximumLogEventsPerPut: 832 return false 833 case b.bytes+addBytes > maximumBytesPerPut: 834 return false 835 } 836 837 b.bytes += addBytes 838 b.batch = append(b.batch, event) 839 840 return true 841 } 842 843 // count is the number of batched events. Warning: this method 844 // is not threadsafe and must not be used concurrently. 845 func (b *eventBatch) count() int { 846 return len(b.batch) 847 } 848 849 // size is the total number of bytes that the batch represents. 850 // 851 // Warning: this method is not threadsafe and must not be used 852 // concurrently. 853 func (b *eventBatch) size() int { 854 return b.bytes 855 } 856 857 func (b *eventBatch) isEmpty() bool { 858 zeroEvents := b.count() == 0 859 zeroSize := b.size() == 0 860 return zeroEvents && zeroSize 861 } 862 863 // reset prepares the batch for reuse. 864 func (b *eventBatch) reset() { 865 b.bytes = 0 866 b.batch = b.batch[:0] 867 }