1
0
Fork 0
mirror of https://github.com/moby/moby.git synced 2022-11-09 12:21:53 -05:00

Merge pull request #36522 from IRCody/awslogs-non-blocking

Allow awslogs to use non-blocking mode
This commit is contained in:
Brian Goff 2018-05-01 16:30:52 -04:00 committed by GitHub
commit fe2d3a1551
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 185 additions and 22 deletions

View file

@ -61,6 +61,7 @@ type logStream struct {
logStreamName string logStreamName string
logGroupName string logGroupName string
logCreateGroup bool logCreateGroup bool
logNonBlocking bool
multilinePattern *regexp.Regexp multilinePattern *regexp.Regexp
client api client api
messages chan *logger.Message messages chan *logger.Message
@ -129,6 +130,8 @@ func New(info logger.Info) (logger.Logger, error) {
} }
} }
logNonBlocking := info.Config["mode"] == "non-blocking"
if info.Config[logStreamKey] != "" { if info.Config[logStreamKey] != "" {
logStreamName = info.Config[logStreamKey] logStreamName = info.Config[logStreamKey]
} }
@ -142,19 +145,54 @@ func New(info logger.Info) (logger.Logger, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
containerStream := &logStream{ containerStream := &logStream{
logStreamName: logStreamName, logStreamName: logStreamName,
logGroupName: logGroupName, logGroupName: logGroupName,
logCreateGroup: logCreateGroup, logCreateGroup: logCreateGroup,
logNonBlocking: logNonBlocking,
multilinePattern: multilinePattern, multilinePattern: multilinePattern,
client: client, client: client,
messages: make(chan *logger.Message, 4096), messages: make(chan *logger.Message, 4096),
} }
err = containerStream.create()
if err != nil { creationDone := make(chan bool)
return nil, err if logNonBlocking {
go func() {
backoff := 1
maxBackoff := 32
for {
// If logger is closed we are done
containerStream.lock.RLock()
if containerStream.closed {
containerStream.lock.RUnlock()
break
}
containerStream.lock.RUnlock()
err := containerStream.create()
if err == nil {
break
}
time.Sleep(time.Duration(backoff) * time.Second)
if backoff < maxBackoff {
backoff *= 2
}
logrus.
WithError(err).
WithField("container-id", info.ContainerID).
WithField("container-name", info.ContainerName).
Error("Error while trying to initialize awslogs. Retrying in: ", backoff, " seconds")
}
close(creationDone)
}()
} else {
if err = containerStream.create(); err != nil {
return nil, err
}
close(creationDone)
} }
go containerStream.collectBatch() go containerStream.collectBatch(creationDone)
return containerStream, nil return containerStream, nil
} }
@ -296,9 +334,18 @@ func (l *logStream) BufSize() int {
func (l *logStream) Log(msg *logger.Message) error { func (l *logStream) Log(msg *logger.Message) error {
l.lock.RLock() l.lock.RLock()
defer l.lock.RUnlock() defer l.lock.RUnlock()
if !l.closed { if l.closed {
l.messages <- msg return errors.New("awslogs is closed")
} }
if l.logNonBlocking {
select {
case l.messages <- msg:
return nil
default:
return errors.New("awslogs buffer is full")
}
}
l.messages <- msg
return nil return nil
} }
@ -324,7 +371,9 @@ func (l *logStream) create() error {
return l.createLogStream() return l.createLogStream()
} }
} }
return err if err != nil {
return err
}
} }
return nil return nil
@ -401,7 +450,9 @@ var newTicker = func(freq time.Duration) *time.Ticker {
// seconds. When events are ready to be processed for submission to CloudWatch // seconds. When events are ready to be processed for submission to CloudWatch
// Logs, the processEvents method is called. If a multiline pattern is not // Logs, the processEvents method is called. If a multiline pattern is not
// configured, log events are submitted to the processEvents method immediately. // configured, log events are submitted to the processEvents method immediately.
func (l *logStream) collectBatch() { func (l *logStream) collectBatch(created chan bool) {
// Wait for the logstream/group to be created
<-created
ticker := newTicker(batchPublishFrequency) ticker := newTicker(batchPublishFrequency)
var eventBuffer []byte var eventBuffer []byte
var eventBufferTimestamp int64 var eventBufferTimestamp int64

View file

@ -201,6 +201,93 @@ func TestCreateAlreadyExists(t *testing.T) {
} }
} }
func TestLogClosed(t *testing.T) {
mockClient := newMockClient()
stream := &logStream{
client: mockClient,
closed: true,
}
err := stream.Log(&logger.Message{})
if err == nil {
t.Fatal("Expected non-nil error")
}
}
func TestLogBlocking(t *testing.T) {
mockClient := newMockClient()
stream := &logStream{
client: mockClient,
messages: make(chan *logger.Message),
}
errorCh := make(chan error, 1)
started := make(chan bool)
go func() {
started <- true
err := stream.Log(&logger.Message{})
errorCh <- err
}()
<-started
select {
case err := <-errorCh:
t.Fatal("Expected stream.Log to block: ", err)
default:
break
}
select {
case <-stream.messages:
break
default:
t.Fatal("Expected to be able to read from stream.messages but was unable to")
}
select {
case err := <-errorCh:
if err != nil {
t.Fatal(err)
}
case <-time.After(30 * time.Second):
t.Fatal("timed out waiting for read")
}
}
func TestLogNonBlockingBufferEmpty(t *testing.T) {
mockClient := newMockClient()
stream := &logStream{
client: mockClient,
messages: make(chan *logger.Message, 1),
logNonBlocking: true,
}
err := stream.Log(&logger.Message{})
if err != nil {
t.Fatal(err)
}
}
func TestLogNonBlockingBufferFull(t *testing.T) {
mockClient := newMockClient()
stream := &logStream{
client: mockClient,
messages: make(chan *logger.Message, 1),
logNonBlocking: true,
}
stream.messages <- &logger.Message{}
errorCh := make(chan error)
started := make(chan bool)
go func() {
started <- true
err := stream.Log(&logger.Message{})
errorCh <- err
}()
<-started
select {
case err := <-errorCh:
if err == nil {
t.Fatal("Expected non-nil error")
}
case <-time.After(30 * time.Second):
t.Fatal("Expected Log call to not block")
}
}
func TestPublishBatchSuccess(t *testing.T) { func TestPublishBatchSuccess(t *testing.T) {
mockClient := newMockClient() mockClient := newMockClient()
stream := &logStream{ stream := &logStream{
@ -410,8 +497,9 @@ func TestCollectBatchSimple(t *testing.T) {
C: ticks, C: ticks,
} }
} }
d := make(chan bool)
go stream.collectBatch() close(d)
go stream.collectBatch(d)
stream.Log(&logger.Message{ stream.Log(&logger.Message{
Line: []byte(logline), Line: []byte(logline),
@ -454,7 +542,9 @@ func TestCollectBatchTicker(t *testing.T) {
} }
} }
go stream.collectBatch() d := make(chan bool)
close(d)
go stream.collectBatch(d)
stream.Log(&logger.Message{ stream.Log(&logger.Message{
Line: []byte(logline + " 1"), Line: []byte(logline + " 1"),
@ -526,7 +616,9 @@ func TestCollectBatchMultilinePattern(t *testing.T) {
} }
} }
go stream.collectBatch() d := make(chan bool)
close(d)
go stream.collectBatch(d)
stream.Log(&logger.Message{ stream.Log(&logger.Message{
Line: []byte(logline), Line: []byte(logline),
@ -580,7 +672,9 @@ func BenchmarkCollectBatch(b *testing.B) {
} }
} }
go stream.collectBatch() d := make(chan bool)
close(d)
go stream.collectBatch(d)
stream.logGenerator(10, 100) stream.logGenerator(10, 100)
ticks <- time.Time{} ticks <- time.Time{}
stream.Close() stream.Close()
@ -610,7 +704,9 @@ func BenchmarkCollectBatchMultilinePattern(b *testing.B) {
C: ticks, C: ticks,
} }
} }
go stream.collectBatch() d := make(chan bool)
close(d)
go stream.collectBatch(d)
stream.logGenerator(10, 100) stream.logGenerator(10, 100)
ticks <- time.Time{} ticks <- time.Time{}
stream.Close() stream.Close()
@ -640,7 +736,9 @@ func TestCollectBatchMultilinePatternMaxEventAge(t *testing.T) {
} }
} }
go stream.collectBatch() d := make(chan bool)
close(d)
go stream.collectBatch(d)
stream.Log(&logger.Message{ stream.Log(&logger.Message{
Line: []byte(logline), Line: []byte(logline),
@ -702,7 +800,9 @@ func TestCollectBatchMultilinePatternNegativeEventAge(t *testing.T) {
} }
} }
go stream.collectBatch() d := make(chan bool)
close(d)
go stream.collectBatch(d)
stream.Log(&logger.Message{ stream.Log(&logger.Message{
Line: []byte(logline), Line: []byte(logline),
@ -750,7 +850,9 @@ func TestCollectBatchMultilinePatternMaxEventSize(t *testing.T) {
} }
} }
go stream.collectBatch() d := make(chan bool)
close(d)
go stream.collectBatch(d)
// Log max event size // Log max event size
longline := strings.Repeat("A", maximumBytesPerEvent) longline := strings.Repeat("A", maximumBytesPerEvent)
@ -801,7 +903,9 @@ func TestCollectBatchClose(t *testing.T) {
} }
} }
go stream.collectBatch() d := make(chan bool)
close(d)
go stream.collectBatch(d)
stream.Log(&logger.Message{ stream.Log(&logger.Message{
Line: []byte(logline), Line: []byte(logline),
@ -844,7 +948,9 @@ func TestCollectBatchLineSplit(t *testing.T) {
} }
} }
go stream.collectBatch() d := make(chan bool)
close(d)
go stream.collectBatch(d)
longline := strings.Repeat("A", maximumBytesPerEvent) longline := strings.Repeat("A", maximumBytesPerEvent)
stream.Log(&logger.Message{ stream.Log(&logger.Message{
@ -891,7 +997,9 @@ func TestCollectBatchMaxEvents(t *testing.T) {
} }
} }
go stream.collectBatch() d := make(chan bool)
close(d)
go stream.collectBatch(d)
line := "A" line := "A"
for i := 0; i <= maximumLogEventsPerPut; i++ { for i := 0; i <= maximumLogEventsPerPut; i++ {
@ -946,7 +1054,9 @@ func TestCollectBatchMaxTotalBytes(t *testing.T) {
} }
} }
go stream.collectBatch() d := make(chan bool)
close(d)
go stream.collectBatch(d)
numPayloads := maximumBytesPerPut / (maximumBytesPerEvent + perEventBytes) numPayloads := maximumBytesPerPut / (maximumBytesPerEvent + perEventBytes)
// maxline is the maximum line that could be submitted after // maxline is the maximum line that could be submitted after
@ -1025,7 +1135,9 @@ func TestCollectBatchWithDuplicateTimestamps(t *testing.T) {
} }
} }
go stream.collectBatch() d := make(chan bool)
close(d)
go stream.collectBatch(d)
times := maximumLogEventsPerPut times := maximumLogEventsPerPut
expectedEvents := []*cloudwatchlogs.InputLogEvent{} expectedEvents := []*cloudwatchlogs.InputLogEvent{}