123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053 |
- package awslogs
- import (
- "errors"
- "fmt"
- "net/http"
- "reflect"
- "regexp"
- "runtime"
- "strings"
- "testing"
- "time"
- "github.com/aws/aws-sdk-go/aws"
- "github.com/aws/aws-sdk-go/aws/awserr"
- "github.com/aws/aws-sdk-go/aws/request"
- "github.com/aws/aws-sdk-go/service/cloudwatchlogs"
- "github.com/docker/docker/daemon/logger"
- "github.com/docker/docker/daemon/logger/loggerutils"
- "github.com/docker/docker/dockerversion"
- "github.com/stretchr/testify/assert"
- )
- const (
- groupName = "groupName"
- streamName = "streamName"
- sequenceToken = "sequenceToken"
- nextSequenceToken = "nextSequenceToken"
- logline = "this is a log line\r"
- multilineLogline = "2017-01-01 01:01:44 This is a multiline log entry\r"
- )
- // Generates i multi-line events each with j lines
- func (l *logStream) logGenerator(lineCount int, multilineCount int) {
- for i := 0; i < multilineCount; i++ {
- l.Log(&logger.Message{
- Line: []byte(multilineLogline),
- Timestamp: time.Time{},
- })
- for j := 0; j < lineCount; j++ {
- l.Log(&logger.Message{
- Line: []byte(logline),
- Timestamp: time.Time{},
- })
- }
- }
- }
- func TestNewAWSLogsClientUserAgentHandler(t *testing.T) {
- info := logger.Info{
- Config: map[string]string{
- regionKey: "us-east-1",
- },
- }
- client, err := newAWSLogsClient(info)
- if err != nil {
- t.Fatal(err)
- }
- realClient, ok := client.(*cloudwatchlogs.CloudWatchLogs)
- if !ok {
- t.Fatal("Could not cast client to cloudwatchlogs.CloudWatchLogs")
- }
- buildHandlerList := realClient.Handlers.Build
- request := &request.Request{
- HTTPRequest: &http.Request{
- Header: http.Header{},
- },
- }
- buildHandlerList.Run(request)
- expectedUserAgentString := fmt.Sprintf("Docker %s (%s) %s/%s (%s; %s; %s)",
- dockerversion.Version, runtime.GOOS, aws.SDKName, aws.SDKVersion, runtime.Version(), runtime.GOOS, runtime.GOARCH)
- userAgent := request.HTTPRequest.Header.Get("User-Agent")
- if userAgent != expectedUserAgentString {
- t.Errorf("Wrong User-Agent string, expected \"%s\" but was \"%s\"",
- expectedUserAgentString, userAgent)
- }
- }
- func TestNewAWSLogsClientRegionDetect(t *testing.T) {
- info := logger.Info{
- Config: map[string]string{},
- }
- mockMetadata := newMockMetadataClient()
- newRegionFinder = func() regionFinder {
- return mockMetadata
- }
- mockMetadata.regionResult <- ®ionResult{
- successResult: "us-east-1",
- }
- _, err := newAWSLogsClient(info)
- if err != nil {
- t.Fatal(err)
- }
- }
- func TestCreateSuccess(t *testing.T) {
- mockClient := newMockClient()
- stream := &logStream{
- client: mockClient,
- logGroupName: groupName,
- logStreamName: streamName,
- }
- mockClient.createLogStreamResult <- &createLogStreamResult{}
- err := stream.create()
- if err != nil {
- t.Errorf("Received unexpected err: %v\n", err)
- }
- argument := <-mockClient.createLogStreamArgument
- if argument.LogGroupName == nil {
- t.Fatal("Expected non-nil LogGroupName")
- }
- if *argument.LogGroupName != groupName {
- t.Errorf("Expected LogGroupName to be %s", groupName)
- }
- if argument.LogStreamName == nil {
- t.Fatal("Expected non-nil LogStreamName")
- }
- if *argument.LogStreamName != streamName {
- t.Errorf("Expected LogStreamName to be %s", streamName)
- }
- }
- func TestCreateLogGroupSuccess(t *testing.T) {
- mockClient := newMockClient()
- stream := &logStream{
- client: mockClient,
- logGroupName: groupName,
- logStreamName: streamName,
- logCreateGroup: true,
- }
- mockClient.createLogGroupResult <- &createLogGroupResult{}
- mockClient.createLogStreamResult <- &createLogStreamResult{}
- err := stream.create()
- if err != nil {
- t.Errorf("Received unexpected err: %v\n", err)
- }
- argument := <-mockClient.createLogStreamArgument
- if argument.LogGroupName == nil {
- t.Fatal("Expected non-nil LogGroupName")
- }
- if *argument.LogGroupName != groupName {
- t.Errorf("Expected LogGroupName to be %s", groupName)
- }
- if argument.LogStreamName == nil {
- t.Fatal("Expected non-nil LogStreamName")
- }
- if *argument.LogStreamName != streamName {
- t.Errorf("Expected LogStreamName to be %s", streamName)
- }
- }
- func TestCreateError(t *testing.T) {
- mockClient := newMockClient()
- stream := &logStream{
- client: mockClient,
- }
- mockClient.createLogStreamResult <- &createLogStreamResult{
- errorResult: errors.New("Error!"),
- }
- err := stream.create()
- if err == nil {
- t.Fatal("Expected non-nil err")
- }
- }
- func TestCreateAlreadyExists(t *testing.T) {
- mockClient := newMockClient()
- stream := &logStream{
- client: mockClient,
- }
- mockClient.createLogStreamResult <- &createLogStreamResult{
- errorResult: awserr.New(resourceAlreadyExistsCode, "", nil),
- }
- err := stream.create()
- if err != nil {
- t.Fatal("Expected nil err")
- }
- }
- func TestPublishBatchSuccess(t *testing.T) {
- mockClient := newMockClient()
- stream := &logStream{
- client: mockClient,
- logGroupName: groupName,
- logStreamName: streamName,
- sequenceToken: aws.String(sequenceToken),
- }
- mockClient.putLogEventsResult <- &putLogEventsResult{
- successResult: &cloudwatchlogs.PutLogEventsOutput{
- NextSequenceToken: aws.String(nextSequenceToken),
- },
- }
- events := []wrappedEvent{
- {
- inputLogEvent: &cloudwatchlogs.InputLogEvent{
- Message: aws.String(logline),
- },
- },
- }
- stream.publishBatch(events)
- if stream.sequenceToken == nil {
- t.Fatal("Expected non-nil sequenceToken")
- }
- if *stream.sequenceToken != nextSequenceToken {
- t.Errorf("Expected sequenceToken to be %s, but was %s", nextSequenceToken, *stream.sequenceToken)
- }
- argument := <-mockClient.putLogEventsArgument
- if argument == nil {
- t.Fatal("Expected non-nil PutLogEventsInput")
- }
- if argument.SequenceToken == nil {
- t.Fatal("Expected non-nil PutLogEventsInput.SequenceToken")
- }
- if *argument.SequenceToken != sequenceToken {
- t.Errorf("Expected PutLogEventsInput.SequenceToken to be %s, but was %s", sequenceToken, *argument.SequenceToken)
- }
- if len(argument.LogEvents) != 1 {
- t.Errorf("Expected LogEvents to contain 1 element, but contains %d", len(argument.LogEvents))
- }
- if argument.LogEvents[0] != events[0].inputLogEvent {
- t.Error("Expected event to equal input")
- }
- }
- func TestPublishBatchError(t *testing.T) {
- mockClient := newMockClient()
- stream := &logStream{
- client: mockClient,
- logGroupName: groupName,
- logStreamName: streamName,
- sequenceToken: aws.String(sequenceToken),
- }
- mockClient.putLogEventsResult <- &putLogEventsResult{
- errorResult: errors.New("Error!"),
- }
- events := []wrappedEvent{
- {
- inputLogEvent: &cloudwatchlogs.InputLogEvent{
- Message: aws.String(logline),
- },
- },
- }
- stream.publishBatch(events)
- if stream.sequenceToken == nil {
- t.Fatal("Expected non-nil sequenceToken")
- }
- if *stream.sequenceToken != sequenceToken {
- t.Errorf("Expected sequenceToken to be %s, but was %s", sequenceToken, *stream.sequenceToken)
- }
- }
- func TestPublishBatchInvalidSeqSuccess(t *testing.T) {
- mockClient := newMockClientBuffered(2)
- stream := &logStream{
- client: mockClient,
- logGroupName: groupName,
- logStreamName: streamName,
- sequenceToken: aws.String(sequenceToken),
- }
- mockClient.putLogEventsResult <- &putLogEventsResult{
- errorResult: awserr.New(invalidSequenceTokenCode, "use token token", nil),
- }
- mockClient.putLogEventsResult <- &putLogEventsResult{
- successResult: &cloudwatchlogs.PutLogEventsOutput{
- NextSequenceToken: aws.String(nextSequenceToken),
- },
- }
- events := []wrappedEvent{
- {
- inputLogEvent: &cloudwatchlogs.InputLogEvent{
- Message: aws.String(logline),
- },
- },
- }
- stream.publishBatch(events)
- if stream.sequenceToken == nil {
- t.Fatal("Expected non-nil sequenceToken")
- }
- if *stream.sequenceToken != nextSequenceToken {
- t.Errorf("Expected sequenceToken to be %s, but was %s", nextSequenceToken, *stream.sequenceToken)
- }
- argument := <-mockClient.putLogEventsArgument
- if argument == nil {
- t.Fatal("Expected non-nil PutLogEventsInput")
- }
- if argument.SequenceToken == nil {
- t.Fatal("Expected non-nil PutLogEventsInput.SequenceToken")
- }
- if *argument.SequenceToken != sequenceToken {
- t.Errorf("Expected PutLogEventsInput.SequenceToken to be %s, but was %s", sequenceToken, *argument.SequenceToken)
- }
- if len(argument.LogEvents) != 1 {
- t.Errorf("Expected LogEvents to contain 1 element, but contains %d", len(argument.LogEvents))
- }
- if argument.LogEvents[0] != events[0].inputLogEvent {
- t.Error("Expected event to equal input")
- }
- argument = <-mockClient.putLogEventsArgument
- if argument == nil {
- t.Fatal("Expected non-nil PutLogEventsInput")
- }
- if argument.SequenceToken == nil {
- t.Fatal("Expected non-nil PutLogEventsInput.SequenceToken")
- }
- if *argument.SequenceToken != "token" {
- t.Errorf("Expected PutLogEventsInput.SequenceToken to be %s, but was %s", "token", *argument.SequenceToken)
- }
- if len(argument.LogEvents) != 1 {
- t.Errorf("Expected LogEvents to contain 1 element, but contains %d", len(argument.LogEvents))
- }
- if argument.LogEvents[0] != events[0].inputLogEvent {
- t.Error("Expected event to equal input")
- }
- }
- func TestPublishBatchAlreadyAccepted(t *testing.T) {
- mockClient := newMockClient()
- stream := &logStream{
- client: mockClient,
- logGroupName: groupName,
- logStreamName: streamName,
- sequenceToken: aws.String(sequenceToken),
- }
- mockClient.putLogEventsResult <- &putLogEventsResult{
- errorResult: awserr.New(dataAlreadyAcceptedCode, "use token token", nil),
- }
- events := []wrappedEvent{
- {
- inputLogEvent: &cloudwatchlogs.InputLogEvent{
- Message: aws.String(logline),
- },
- },
- }
- stream.publishBatch(events)
- if stream.sequenceToken == nil {
- t.Fatal("Expected non-nil sequenceToken")
- }
- if *stream.sequenceToken != "token" {
- t.Errorf("Expected sequenceToken to be %s, but was %s", "token", *stream.sequenceToken)
- }
- argument := <-mockClient.putLogEventsArgument
- if argument == nil {
- t.Fatal("Expected non-nil PutLogEventsInput")
- }
- if argument.SequenceToken == nil {
- t.Fatal("Expected non-nil PutLogEventsInput.SequenceToken")
- }
- if *argument.SequenceToken != sequenceToken {
- t.Errorf("Expected PutLogEventsInput.SequenceToken to be %s, but was %s", sequenceToken, *argument.SequenceToken)
- }
- if len(argument.LogEvents) != 1 {
- t.Errorf("Expected LogEvents to contain 1 element, but contains %d", len(argument.LogEvents))
- }
- if argument.LogEvents[0] != events[0].inputLogEvent {
- t.Error("Expected event to equal input")
- }
- }
- func TestCollectBatchSimple(t *testing.T) {
- mockClient := newMockClient()
- stream := &logStream{
- client: mockClient,
- logGroupName: groupName,
- logStreamName: streamName,
- sequenceToken: aws.String(sequenceToken),
- messages: make(chan *logger.Message),
- }
- mockClient.putLogEventsResult <- &putLogEventsResult{
- successResult: &cloudwatchlogs.PutLogEventsOutput{
- NextSequenceToken: aws.String(nextSequenceToken),
- },
- }
- ticks := make(chan time.Time)
- newTicker = func(_ time.Duration) *time.Ticker {
- return &time.Ticker{
- C: ticks,
- }
- }
- go stream.collectBatch()
- stream.Log(&logger.Message{
- Line: []byte(logline),
- Timestamp: time.Time{},
- })
- ticks <- time.Time{}
- stream.Close()
- argument := <-mockClient.putLogEventsArgument
- if argument == nil {
- t.Fatal("Expected non-nil PutLogEventsInput")
- }
- if len(argument.LogEvents) != 1 {
- t.Errorf("Expected LogEvents to contain 1 element, but contains %d", len(argument.LogEvents))
- }
- if *argument.LogEvents[0].Message != logline {
- t.Errorf("Expected message to be %s but was %s", logline, *argument.LogEvents[0].Message)
- }
- }
- func TestCollectBatchTicker(t *testing.T) {
- mockClient := newMockClient()
- stream := &logStream{
- client: mockClient,
- logGroupName: groupName,
- logStreamName: streamName,
- sequenceToken: aws.String(sequenceToken),
- messages: make(chan *logger.Message),
- }
- mockClient.putLogEventsResult <- &putLogEventsResult{
- successResult: &cloudwatchlogs.PutLogEventsOutput{
- NextSequenceToken: aws.String(nextSequenceToken),
- },
- }
- ticks := make(chan time.Time)
- newTicker = func(_ time.Duration) *time.Ticker {
- return &time.Ticker{
- C: ticks,
- }
- }
- go stream.collectBatch()
- stream.Log(&logger.Message{
- Line: []byte(logline + " 1"),
- Timestamp: time.Time{},
- })
- stream.Log(&logger.Message{
- Line: []byte(logline + " 2"),
- Timestamp: time.Time{},
- })
- ticks <- time.Time{}
- // Verify first batch
- argument := <-mockClient.putLogEventsArgument
- if argument == nil {
- t.Fatal("Expected non-nil PutLogEventsInput")
- }
- if len(argument.LogEvents) != 2 {
- t.Errorf("Expected LogEvents to contain 2 elements, but contains %d", len(argument.LogEvents))
- }
- if *argument.LogEvents[0].Message != logline+" 1" {
- t.Errorf("Expected message to be %s but was %s", logline+" 1", *argument.LogEvents[0].Message)
- }
- if *argument.LogEvents[1].Message != logline+" 2" {
- t.Errorf("Expected message to be %s but was %s", logline+" 2", *argument.LogEvents[0].Message)
- }
- stream.Log(&logger.Message{
- Line: []byte(logline + " 3"),
- Timestamp: time.Time{},
- })
- ticks <- time.Time{}
- argument = <-mockClient.putLogEventsArgument
- if argument == nil {
- t.Fatal("Expected non-nil PutLogEventsInput")
- }
- if len(argument.LogEvents) != 1 {
- t.Errorf("Expected LogEvents to contain 1 elements, but contains %d", len(argument.LogEvents))
- }
- if *argument.LogEvents[0].Message != logline+" 3" {
- t.Errorf("Expected message to be %s but was %s", logline+" 3", *argument.LogEvents[0].Message)
- }
- stream.Close()
- }
- func TestCollectBatchMultilinePattern(t *testing.T) {
- mockClient := newMockClient()
- multilinePattern := regexp.MustCompile("xxxx")
- stream := &logStream{
- client: mockClient,
- logGroupName: groupName,
- logStreamName: streamName,
- multilinePattern: multilinePattern,
- sequenceToken: aws.String(sequenceToken),
- messages: make(chan *logger.Message),
- }
- mockClient.putLogEventsResult <- &putLogEventsResult{
- successResult: &cloudwatchlogs.PutLogEventsOutput{
- NextSequenceToken: aws.String(nextSequenceToken),
- },
- }
- ticks := make(chan time.Time)
- newTicker = func(_ time.Duration) *time.Ticker {
- return &time.Ticker{
- C: ticks,
- }
- }
- go stream.collectBatch()
- stream.Log(&logger.Message{
- Line: []byte(logline),
- Timestamp: time.Now(),
- })
- stream.Log(&logger.Message{
- Line: []byte(logline),
- Timestamp: time.Now(),
- })
- stream.Log(&logger.Message{
- Line: []byte("xxxx " + logline),
- Timestamp: time.Now(),
- })
- ticks <- time.Now()
- // Verify single multiline event
- argument := <-mockClient.putLogEventsArgument
- assert.NotNil(t, argument, "Expected non-nil PutLogEventsInput")
- assert.Equal(t, 1, len(argument.LogEvents), "Expected single multiline event")
- assert.Equal(t, logline+"\n"+logline+"\n", *argument.LogEvents[0].Message, "Received incorrect multiline message")
- stream.Close()
- // Verify single event
- argument = <-mockClient.putLogEventsArgument
- assert.NotNil(t, argument, "Expected non-nil PutLogEventsInput")
- assert.Equal(t, 1, len(argument.LogEvents), "Expected single multiline event")
- assert.Equal(t, "xxxx "+logline+"\n", *argument.LogEvents[0].Message, "Received incorrect multiline message")
- }
- func BenchmarkCollectBatch(b *testing.B) {
- for i := 0; i < b.N; i++ {
- mockClient := newMockClient()
- stream := &logStream{
- client: mockClient,
- logGroupName: groupName,
- logStreamName: streamName,
- sequenceToken: aws.String(sequenceToken),
- messages: make(chan *logger.Message),
- }
- mockClient.putLogEventsResult <- &putLogEventsResult{
- successResult: &cloudwatchlogs.PutLogEventsOutput{
- NextSequenceToken: aws.String(nextSequenceToken),
- },
- }
- ticks := make(chan time.Time)
- newTicker = func(_ time.Duration) *time.Ticker {
- return &time.Ticker{
- C: ticks,
- }
- }
- go stream.collectBatch()
- stream.logGenerator(10, 100)
- ticks <- time.Time{}
- stream.Close()
- }
- }
- func BenchmarkCollectBatchMultilinePattern(b *testing.B) {
- for i := 0; i < b.N; i++ {
- mockClient := newMockClient()
- multilinePattern := regexp.MustCompile(`\d{4}-(?:0[1-9]|1[0-2])-(?:0[1-9]|[1,2][0-9]|3[0,1]) (?:[0,1][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9]`)
- stream := &logStream{
- client: mockClient,
- logGroupName: groupName,
- logStreamName: streamName,
- multilinePattern: multilinePattern,
- sequenceToken: aws.String(sequenceToken),
- messages: make(chan *logger.Message),
- }
- mockClient.putLogEventsResult <- &putLogEventsResult{
- successResult: &cloudwatchlogs.PutLogEventsOutput{
- NextSequenceToken: aws.String(nextSequenceToken),
- },
- }
- ticks := make(chan time.Time)
- newTicker = func(_ time.Duration) *time.Ticker {
- return &time.Ticker{
- C: ticks,
- }
- }
- go stream.collectBatch()
- stream.logGenerator(10, 100)
- ticks <- time.Time{}
- stream.Close()
- }
- }
- func TestCollectBatchMultilinePatternMaxEventAge(t *testing.T) {
- mockClient := newMockClient()
- multilinePattern := regexp.MustCompile("xxxx")
- stream := &logStream{
- client: mockClient,
- logGroupName: groupName,
- logStreamName: streamName,
- multilinePattern: multilinePattern,
- sequenceToken: aws.String(sequenceToken),
- messages: make(chan *logger.Message),
- }
- mockClient.putLogEventsResult <- &putLogEventsResult{
- successResult: &cloudwatchlogs.PutLogEventsOutput{
- NextSequenceToken: aws.String(nextSequenceToken),
- },
- }
- ticks := make(chan time.Time)
- newTicker = func(_ time.Duration) *time.Ticker {
- return &time.Ticker{
- C: ticks,
- }
- }
- go stream.collectBatch()
- stream.Log(&logger.Message{
- Line: []byte(logline),
- Timestamp: time.Now(),
- })
- // Log an event 1 second later
- stream.Log(&logger.Message{
- Line: []byte(logline),
- Timestamp: time.Now().Add(time.Second),
- })
- // Fire ticker batchPublishFrequency seconds later
- ticks <- time.Now().Add(batchPublishFrequency * time.Second)
- // Verify single multiline event is flushed after maximum event buffer age (batchPublishFrequency)
- argument := <-mockClient.putLogEventsArgument
- assert.NotNil(t, argument, "Expected non-nil PutLogEventsInput")
- assert.Equal(t, 1, len(argument.LogEvents), "Expected single multiline event")
- assert.Equal(t, logline+"\n"+logline+"\n", *argument.LogEvents[0].Message, "Received incorrect multiline message")
- stream.Close()
- }
- func TestCollectBatchMultilinePatternNegativeEventAge(t *testing.T) {
- mockClient := newMockClient()
- multilinePattern := regexp.MustCompile("xxxx")
- stream := &logStream{
- client: mockClient,
- logGroupName: groupName,
- logStreamName: streamName,
- multilinePattern: multilinePattern,
- sequenceToken: aws.String(sequenceToken),
- messages: make(chan *logger.Message),
- }
- mockClient.putLogEventsResult <- &putLogEventsResult{
- successResult: &cloudwatchlogs.PutLogEventsOutput{
- NextSequenceToken: aws.String(nextSequenceToken),
- },
- }
- ticks := make(chan time.Time)
- newTicker = func(_ time.Duration) *time.Ticker {
- return &time.Ticker{
- C: ticks,
- }
- }
- go stream.collectBatch()
- stream.Log(&logger.Message{
- Line: []byte(logline),
- Timestamp: time.Now(),
- })
- // Log an event 1 second later
- stream.Log(&logger.Message{
- Line: []byte(logline),
- Timestamp: time.Now().Add(time.Second),
- })
- // Fire ticker in past to simulate negative event buffer age
- ticks <- time.Now().Add(-time.Second)
- // Verify single multiline event is flushed with a negative event buffer age
- argument := <-mockClient.putLogEventsArgument
- assert.NotNil(t, argument, "Expected non-nil PutLogEventsInput")
- assert.Equal(t, 1, len(argument.LogEvents), "Expected single multiline event")
- assert.Equal(t, logline+"\n"+logline+"\n", *argument.LogEvents[0].Message, "Received incorrect multiline message")
- stream.Close()
- }
- func TestCollectBatchClose(t *testing.T) {
- mockClient := newMockClient()
- stream := &logStream{
- client: mockClient,
- logGroupName: groupName,
- logStreamName: streamName,
- sequenceToken: aws.String(sequenceToken),
- messages: make(chan *logger.Message),
- }
- mockClient.putLogEventsResult <- &putLogEventsResult{
- successResult: &cloudwatchlogs.PutLogEventsOutput{
- NextSequenceToken: aws.String(nextSequenceToken),
- },
- }
- var ticks = make(chan time.Time)
- newTicker = func(_ time.Duration) *time.Ticker {
- return &time.Ticker{
- C: ticks,
- }
- }
- go stream.collectBatch()
- stream.Log(&logger.Message{
- Line: []byte(logline),
- Timestamp: time.Time{},
- })
- // no ticks
- stream.Close()
- argument := <-mockClient.putLogEventsArgument
- if argument == nil {
- t.Fatal("Expected non-nil PutLogEventsInput")
- }
- if len(argument.LogEvents) != 1 {
- t.Errorf("Expected LogEvents to contain 1 element, but contains %d", len(argument.LogEvents))
- }
- if *argument.LogEvents[0].Message != logline {
- t.Errorf("Expected message to be %s but was %s", logline, *argument.LogEvents[0].Message)
- }
- }
- func TestCollectBatchLineSplit(t *testing.T) {
- mockClient := newMockClient()
- stream := &logStream{
- client: mockClient,
- logGroupName: groupName,
- logStreamName: streamName,
- sequenceToken: aws.String(sequenceToken),
- messages: make(chan *logger.Message),
- }
- mockClient.putLogEventsResult <- &putLogEventsResult{
- successResult: &cloudwatchlogs.PutLogEventsOutput{
- NextSequenceToken: aws.String(nextSequenceToken),
- },
- }
- var ticks = make(chan time.Time)
- newTicker = func(_ time.Duration) *time.Ticker {
- return &time.Ticker{
- C: ticks,
- }
- }
- go stream.collectBatch()
- longline := strings.Repeat("A", maximumBytesPerEvent)
- stream.Log(&logger.Message{
- Line: []byte(longline + "B"),
- Timestamp: time.Time{},
- })
- // no ticks
- stream.Close()
- argument := <-mockClient.putLogEventsArgument
- if argument == nil {
- t.Fatal("Expected non-nil PutLogEventsInput")
- }
- if len(argument.LogEvents) != 2 {
- t.Errorf("Expected LogEvents to contain 2 elements, but contains %d", len(argument.LogEvents))
- }
- if *argument.LogEvents[0].Message != longline {
- t.Errorf("Expected message to be %s but was %s", longline, *argument.LogEvents[0].Message)
- }
- if *argument.LogEvents[1].Message != "B" {
- t.Errorf("Expected message to be %s but was %s", "B", *argument.LogEvents[1].Message)
- }
- }
- func TestCollectBatchMaxEvents(t *testing.T) {
- mockClient := newMockClientBuffered(1)
- stream := &logStream{
- client: mockClient,
- logGroupName: groupName,
- logStreamName: streamName,
- sequenceToken: aws.String(sequenceToken),
- messages: make(chan *logger.Message),
- }
- mockClient.putLogEventsResult <- &putLogEventsResult{
- successResult: &cloudwatchlogs.PutLogEventsOutput{
- NextSequenceToken: aws.String(nextSequenceToken),
- },
- }
- var ticks = make(chan time.Time)
- newTicker = func(_ time.Duration) *time.Ticker {
- return &time.Ticker{
- C: ticks,
- }
- }
- go stream.collectBatch()
- line := "A"
- for i := 0; i <= maximumLogEventsPerPut; i++ {
- stream.Log(&logger.Message{
- Line: []byte(line),
- Timestamp: time.Time{},
- })
- }
- // no ticks
- stream.Close()
- argument := <-mockClient.putLogEventsArgument
- if argument == nil {
- t.Fatal("Expected non-nil PutLogEventsInput")
- }
- if len(argument.LogEvents) != maximumLogEventsPerPut {
- t.Errorf("Expected LogEvents to contain %d elements, but contains %d", maximumLogEventsPerPut, len(argument.LogEvents))
- }
- argument = <-mockClient.putLogEventsArgument
- if argument == nil {
- t.Fatal("Expected non-nil PutLogEventsInput")
- }
- if len(argument.LogEvents) != 1 {
- t.Errorf("Expected LogEvents to contain %d elements, but contains %d", 1, len(argument.LogEvents))
- }
- }
- func TestCollectBatchMaxTotalBytes(t *testing.T) {
- mockClient := newMockClientBuffered(1)
- stream := &logStream{
- client: mockClient,
- logGroupName: groupName,
- logStreamName: streamName,
- sequenceToken: aws.String(sequenceToken),
- messages: make(chan *logger.Message),
- }
- mockClient.putLogEventsResult <- &putLogEventsResult{
- successResult: &cloudwatchlogs.PutLogEventsOutput{
- NextSequenceToken: aws.String(nextSequenceToken),
- },
- }
- var ticks = make(chan time.Time)
- newTicker = func(_ time.Duration) *time.Ticker {
- return &time.Ticker{
- C: ticks,
- }
- }
- go stream.collectBatch()
- longline := strings.Repeat("A", maximumBytesPerPut)
- stream.Log(&logger.Message{
- Line: []byte(longline + "B"),
- Timestamp: time.Time{},
- })
- // no ticks
- stream.Close()
- argument := <-mockClient.putLogEventsArgument
- if argument == nil {
- t.Fatal("Expected non-nil PutLogEventsInput")
- }
- bytes := 0
- for _, event := range argument.LogEvents {
- bytes += len(*event.Message)
- }
- if bytes > maximumBytesPerPut {
- t.Errorf("Expected <= %d bytes but was %d", maximumBytesPerPut, bytes)
- }
- argument = <-mockClient.putLogEventsArgument
- if len(argument.LogEvents) != 1 {
- t.Errorf("Expected LogEvents to contain 1 elements, but contains %d", len(argument.LogEvents))
- }
- message := *argument.LogEvents[0].Message
- if message[len(message)-1:] != "B" {
- t.Errorf("Expected message to be %s but was %s", "B", message[len(message)-1:])
- }
- }
- func TestCollectBatchWithDuplicateTimestamps(t *testing.T) {
- mockClient := newMockClient()
- stream := &logStream{
- client: mockClient,
- logGroupName: groupName,
- logStreamName: streamName,
- sequenceToken: aws.String(sequenceToken),
- messages: make(chan *logger.Message),
- }
- mockClient.putLogEventsResult <- &putLogEventsResult{
- successResult: &cloudwatchlogs.PutLogEventsOutput{
- NextSequenceToken: aws.String(nextSequenceToken),
- },
- }
- ticks := make(chan time.Time)
- newTicker = func(_ time.Duration) *time.Ticker {
- return &time.Ticker{
- C: ticks,
- }
- }
- go stream.collectBatch()
- times := maximumLogEventsPerPut
- expectedEvents := []*cloudwatchlogs.InputLogEvent{}
- timestamp := time.Now()
- for i := 0; i < times; i++ {
- line := fmt.Sprintf("%d", i)
- if i%2 == 0 {
- timestamp.Add(1 * time.Nanosecond)
- }
- stream.Log(&logger.Message{
- Line: []byte(line),
- Timestamp: timestamp,
- })
- expectedEvents = append(expectedEvents, &cloudwatchlogs.InputLogEvent{
- Message: aws.String(line),
- Timestamp: aws.Int64(timestamp.UnixNano() / int64(time.Millisecond)),
- })
- }
- ticks <- time.Time{}
- stream.Close()
- argument := <-mockClient.putLogEventsArgument
- if argument == nil {
- t.Fatal("Expected non-nil PutLogEventsInput")
- }
- if len(argument.LogEvents) != times {
- t.Errorf("Expected LogEvents to contain %d elements, but contains %d", times, len(argument.LogEvents))
- }
- for i := 0; i < times; i++ {
- if !reflect.DeepEqual(*argument.LogEvents[i], *expectedEvents[i]) {
- t.Errorf("Expected event to be %v but was %v", *expectedEvents[i], *argument.LogEvents[i])
- }
- }
- }
- func TestParseLogOptionsMultilinePattern(t *testing.T) {
- info := logger.Info{
- Config: map[string]string{
- multilinePatternKey: "^xxxx",
- },
- }
- multilinePattern, err := parseMultilineOptions(info)
- assert.Nil(t, err, "Received unexpected error")
- assert.True(t, multilinePattern.MatchString("xxxx"), "No multiline pattern match found")
- }
- func TestParseLogOptionsDatetimeFormat(t *testing.T) {
- datetimeFormatTests := []struct {
- format string
- match string
- }{
- {"%d/%m/%y %a %H:%M:%S%L %Z", "31/12/10 Mon 08:42:44.345 NZDT"},
- {"%Y-%m-%d %A %I:%M:%S.%f%p%z", "2007-12-04 Monday 08:42:44.123456AM+1200"},
- {"%b|%b|%b|%b|%b|%b|%b|%b|%b|%b|%b|%b", "Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec"},
- {"%B|%B|%B|%B|%B|%B|%B|%B|%B|%B|%B|%B", "January|February|March|April|May|June|July|August|September|October|November|December"},
- {"%A|%A|%A|%A|%A|%A|%A", "Monday|Tuesday|Wednesday|Thursday|Friday|Saturday|Sunday"},
- {"%a|%a|%a|%a|%a|%a|%a", "Mon|Tue|Wed|Thu|Fri|Sat|Sun"},
- {"Day of the week: %w, Day of the year: %j", "Day of the week: 4, Day of the year: 091"},
- }
- for _, dt := range datetimeFormatTests {
- t.Run(dt.match, func(t *testing.T) {
- info := logger.Info{
- Config: map[string]string{
- datetimeFormatKey: dt.format,
- },
- }
- multilinePattern, err := parseMultilineOptions(info)
- assert.Nil(t, err, "Received unexpected error")
- assert.True(t, multilinePattern.MatchString(dt.match), "No multiline pattern match found")
- })
- }
- }
- func TestValidateLogOptionsDatetimeFormatAndMultilinePattern(t *testing.T) {
- cfg := map[string]string{
- multilinePatternKey: "^xxxx",
- datetimeFormatKey: "%Y-%m-%d",
- logGroupKey: groupName,
- }
- conflictingLogOptionsError := "you cannot configure log opt 'awslogs-datetime-format' and 'awslogs-multiline-pattern' at the same time"
- err := ValidateLogOpt(cfg)
- assert.NotNil(t, err, "Expected an error")
- assert.Equal(t, err.Error(), conflictingLogOptionsError, "Received invalid error")
- }
- func TestCreateTagSuccess(t *testing.T) {
- mockClient := newMockClient()
- info := logger.Info{
- ContainerName: "/test-container",
- ContainerID: "container-abcdefghijklmnopqrstuvwxyz01234567890",
- Config: map[string]string{"tag": "{{.Name}}/{{.FullID}}"},
- }
- logStreamName, e := loggerutils.ParseLogTag(info, loggerutils.DefaultTemplate)
- if e != nil {
- t.Errorf("Error generating tag: %q", e)
- }
- stream := &logStream{
- client: mockClient,
- logGroupName: groupName,
- logStreamName: logStreamName,
- }
- mockClient.createLogStreamResult <- &createLogStreamResult{}
- err := stream.create()
- if err != nil {
- t.Errorf("Received unexpected err: %v\n", err)
- }
- argument := <-mockClient.createLogStreamArgument
- if *argument.LogStreamName != "test-container/container-abcdefghijklmnopqrstuvwxyz01234567890" {
- t.Errorf("Expected LogStreamName to be %s", "test-container/container-abcdefghijklmnopqrstuvwxyz01234567890")
- }
- }
- func BenchmarkUnwrapEvents(b *testing.B) {
- events := make([]wrappedEvent, maximumLogEventsPerPut)
- for i := 0; i < maximumLogEventsPerPut; i++ {
- mes := strings.Repeat("0", maximumBytesPerEvent)
- events[i].inputLogEvent = &cloudwatchlogs.InputLogEvent{
- Message: &mes,
- }
- }
- as := assert.New(b)
- b.ResetTimer()
- for i := 0; i < b.N; i++ {
- res := unwrapEvents(events)
- as.Len(res, maximumLogEventsPerPut)
- }
- }
|