|
@@ -39,7 +39,11 @@ const (
|
|
datetimeFormatKey = "awslogs-datetime-format"
|
|
datetimeFormatKey = "awslogs-datetime-format"
|
|
multilinePatternKey = "awslogs-multiline-pattern"
|
|
multilinePatternKey = "awslogs-multiline-pattern"
|
|
credentialsEndpointKey = "awslogs-credentials-endpoint"
|
|
credentialsEndpointKey = "awslogs-credentials-endpoint"
|
|
- batchPublishFrequency = 5 * time.Second
|
|
|
|
|
|
+ forceFlushIntervalKey = "awslogs-force-flush-interval-seconds"
|
|
|
|
+ maxBufferedEventsKey = "awslogs-max-buffered-events"
|
|
|
|
+
|
|
|
|
+ defaultForceFlushInterval = 5 * time.Second
|
|
|
|
+ defaultMaxBufferedEvents = 4096
|
|
|
|
|
|
// See: http://docs.aws.amazon.com/AmazonCloudWatchLogs/latest/APIReference/API_PutLogEvents.html
|
|
// See: http://docs.aws.amazon.com/AmazonCloudWatchLogs/latest/APIReference/API_PutLogEvents.html
|
|
perEventBytes = 26
|
|
perEventBytes = 26
|
|
@@ -64,16 +68,27 @@ const (
|
|
)
|
|
)
|
|
|
|
|
|
type logStream struct {
|
|
type logStream struct {
|
|
- logStreamName string
|
|
|
|
- logGroupName string
|
|
|
|
- logCreateGroup bool
|
|
|
|
- logNonBlocking bool
|
|
|
|
- multilinePattern *regexp.Regexp
|
|
|
|
- client api
|
|
|
|
- messages chan *logger.Message
|
|
|
|
- lock sync.RWMutex
|
|
|
|
- closed bool
|
|
|
|
- sequenceToken *string
|
|
|
|
|
|
+ logStreamName string
|
|
|
|
+ logGroupName string
|
|
|
|
+ logCreateGroup bool
|
|
|
|
+ logNonBlocking bool
|
|
|
|
+ forceFlushInterval time.Duration
|
|
|
|
+ multilinePattern *regexp.Regexp
|
|
|
|
+ client api
|
|
|
|
+ messages chan *logger.Message
|
|
|
|
+ lock sync.RWMutex
|
|
|
|
+ closed bool
|
|
|
|
+ sequenceToken *string
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+type logStreamConfig struct {
|
|
|
|
+ logStreamName string
|
|
|
|
+ logGroupName string
|
|
|
|
+ logCreateGroup bool
|
|
|
|
+ logNonBlocking bool
|
|
|
|
+ forceFlushInterval time.Duration
|
|
|
|
+ maxBufferedEvents int
|
|
|
|
+ multilinePattern *regexp.Regexp
|
|
}
|
|
}
|
|
|
|
|
|
var _ logger.SizedLogger = &logStream{}
|
|
var _ logger.SizedLogger = &logStream{}
|
|
@@ -123,47 +138,28 @@ type eventBatch struct {
|
|
// AWS_REGION, AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, the shared credentials
|
|
// AWS_REGION, AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, the shared credentials
|
|
// file (~/.aws/credentials), and the EC2 Instance Metadata Service.
|
|
// file (~/.aws/credentials), and the EC2 Instance Metadata Service.
|
|
func New(info logger.Info) (logger.Logger, error) {
|
|
func New(info logger.Info) (logger.Logger, error) {
|
|
- logGroupName := info.Config[logGroupKey]
|
|
|
|
- logStreamName, err := loggerutils.ParseLogTag(info, "{{.FullID}}")
|
|
|
|
|
|
+ containerStreamConfig, err := newStreamConfig(info)
|
|
if err != nil {
|
|
if err != nil {
|
|
return nil, err
|
|
return nil, err
|
|
}
|
|
}
|
|
- logCreateGroup := false
|
|
|
|
- if info.Config[logCreateGroupKey] != "" {
|
|
|
|
- logCreateGroup, err = strconv.ParseBool(info.Config[logCreateGroupKey])
|
|
|
|
- if err != nil {
|
|
|
|
- return nil, err
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- logNonBlocking := info.Config["mode"] == "non-blocking"
|
|
|
|
-
|
|
|
|
- if info.Config[logStreamKey] != "" {
|
|
|
|
- logStreamName = info.Config[logStreamKey]
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- multilinePattern, err := parseMultilineOptions(info)
|
|
|
|
- if err != nil {
|
|
|
|
- return nil, err
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
client, err := newAWSLogsClient(info)
|
|
client, err := newAWSLogsClient(info)
|
|
if err != nil {
|
|
if err != nil {
|
|
return nil, err
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
|
|
containerStream := &logStream{
|
|
containerStream := &logStream{
|
|
- logStreamName: logStreamName,
|
|
|
|
- logGroupName: logGroupName,
|
|
|
|
- logCreateGroup: logCreateGroup,
|
|
|
|
- logNonBlocking: logNonBlocking,
|
|
|
|
- multilinePattern: multilinePattern,
|
|
|
|
- client: client,
|
|
|
|
- messages: make(chan *logger.Message, 4096),
|
|
|
|
|
|
+ logStreamName: containerStreamConfig.logStreamName,
|
|
|
|
+ logGroupName: containerStreamConfig.logGroupName,
|
|
|
|
+ logCreateGroup: containerStreamConfig.logCreateGroup,
|
|
|
|
+ logNonBlocking: containerStreamConfig.logNonBlocking,
|
|
|
|
+ forceFlushInterval: containerStreamConfig.forceFlushInterval,
|
|
|
|
+ multilinePattern: containerStreamConfig.multilinePattern,
|
|
|
|
+ client: client,
|
|
|
|
+ messages: make(chan *logger.Message, containerStreamConfig.maxBufferedEvents),
|
|
}
|
|
}
|
|
|
|
|
|
creationDone := make(chan bool)
|
|
creationDone := make(chan bool)
|
|
- if logNonBlocking {
|
|
|
|
|
|
+ if containerStream.logNonBlocking {
|
|
go func() {
|
|
go func() {
|
|
backoff := 1
|
|
backoff := 1
|
|
maxBackoff := 32
|
|
maxBackoff := 32
|
|
@@ -203,6 +199,63 @@ func New(info logger.Info) (logger.Logger, error) {
|
|
return containerStream, nil
|
|
return containerStream, nil
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+// Parses most of the awslogs- options and prepares a config object to be used for newing the actual stream
|
|
|
|
+// It has been formed out to ease Utest of the New above
|
|
|
|
+func newStreamConfig(info logger.Info) (*logStreamConfig, error) {
|
|
|
|
+ logGroupName := info.Config[logGroupKey]
|
|
|
|
+ logStreamName, err := loggerutils.ParseLogTag(info, "{{.FullID}}")
|
|
|
|
+ if err != nil {
|
|
|
|
+ return nil, err
|
|
|
|
+ }
|
|
|
|
+ logCreateGroup := false
|
|
|
|
+ if info.Config[logCreateGroupKey] != "" {
|
|
|
|
+ logCreateGroup, err = strconv.ParseBool(info.Config[logCreateGroupKey])
|
|
|
|
+ if err != nil {
|
|
|
|
+ return nil, err
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ logNonBlocking := info.Config["mode"] == "non-blocking"
|
|
|
|
+
|
|
|
|
+ forceFlushInterval := defaultForceFlushInterval
|
|
|
|
+ if info.Config[forceFlushIntervalKey] != "" {
|
|
|
|
+ forceFlushIntervalAsInt, err := strconv.Atoi(info.Config[forceFlushIntervalKey])
|
|
|
|
+ if err != nil {
|
|
|
|
+ return nil, err
|
|
|
|
+ }
|
|
|
|
+ forceFlushInterval = time.Duration(forceFlushIntervalAsInt) * time.Second
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ maxBufferedEvents := int(defaultMaxBufferedEvents)
|
|
|
|
+ if info.Config[maxBufferedEventsKey] != "" {
|
|
|
|
+ maxBufferedEvents, err = strconv.Atoi(info.Config[maxBufferedEventsKey])
|
|
|
|
+ if err != nil {
|
|
|
|
+ return nil, err
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if info.Config[logStreamKey] != "" {
|
|
|
|
+ logStreamName = info.Config[logStreamKey]
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ multilinePattern, err := parseMultilineOptions(info)
|
|
|
|
+ if err != nil {
|
|
|
|
+ return nil, err
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ containerStreamConfig := &logStreamConfig{
|
|
|
|
+ logStreamName: logStreamName,
|
|
|
|
+ logGroupName: logGroupName,
|
|
|
|
+ logCreateGroup: logCreateGroup,
|
|
|
|
+ logNonBlocking: logNonBlocking,
|
|
|
|
+ forceFlushInterval: forceFlushInterval,
|
|
|
|
+ maxBufferedEvents: maxBufferedEvents,
|
|
|
|
+ multilinePattern: multilinePattern,
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return containerStreamConfig, nil
|
|
|
|
+}
|
|
|
|
+
|
|
// Parses awslogs-multiline-pattern and awslogs-datetime-format options
|
|
// Parses awslogs-multiline-pattern and awslogs-datetime-format options
|
|
// If awslogs-datetime-format is present, convert the format from strftime
|
|
// If awslogs-datetime-format is present, convert the format from strftime
|
|
// to regexp and return.
|
|
// to regexp and return.
|
|
@@ -471,7 +524,11 @@ var newTicker = func(freq time.Duration) *time.Ticker {
|
|
func (l *logStream) collectBatch(created chan bool) {
|
|
func (l *logStream) collectBatch(created chan bool) {
|
|
// Wait for the logstream/group to be created
|
|
// Wait for the logstream/group to be created
|
|
<-created
|
|
<-created
|
|
- ticker := newTicker(batchPublishFrequency)
|
|
|
|
|
|
+ flushInterval := l.forceFlushInterval
|
|
|
|
+ if flushInterval <= 0 {
|
|
|
|
+ flushInterval = defaultForceFlushInterval
|
|
|
|
+ }
|
|
|
|
+ ticker := newTicker(flushInterval)
|
|
var eventBuffer []byte
|
|
var eventBuffer []byte
|
|
var eventBufferTimestamp int64
|
|
var eventBufferTimestamp int64
|
|
var batch = newEventBatch()
|
|
var batch = newEventBatch()
|
|
@@ -481,7 +538,7 @@ func (l *logStream) collectBatch(created chan bool) {
|
|
// If event buffer is older than batch publish frequency flush the event buffer
|
|
// If event buffer is older than batch publish frequency flush the event buffer
|
|
if eventBufferTimestamp > 0 && len(eventBuffer) > 0 {
|
|
if eventBufferTimestamp > 0 && len(eventBuffer) > 0 {
|
|
eventBufferAge := t.UnixNano()/int64(time.Millisecond) - eventBufferTimestamp
|
|
eventBufferAge := t.UnixNano()/int64(time.Millisecond) - eventBufferTimestamp
|
|
- eventBufferExpired := eventBufferAge >= int64(batchPublishFrequency)/int64(time.Millisecond)
|
|
|
|
|
|
+ eventBufferExpired := eventBufferAge >= int64(flushInterval)/int64(time.Millisecond)
|
|
eventBufferNegative := eventBufferAge < 0
|
|
eventBufferNegative := eventBufferAge < 0
|
|
if eventBufferExpired || eventBufferNegative {
|
|
if eventBufferExpired || eventBufferNegative {
|
|
l.processEvent(batch, eventBuffer, eventBufferTimestamp)
|
|
l.processEvent(batch, eventBuffer, eventBufferTimestamp)
|
|
@@ -672,6 +729,8 @@ func ValidateLogOpt(cfg map[string]string) error {
|
|
case datetimeFormatKey:
|
|
case datetimeFormatKey:
|
|
case multilinePatternKey:
|
|
case multilinePatternKey:
|
|
case credentialsEndpointKey:
|
|
case credentialsEndpointKey:
|
|
|
|
+ case forceFlushIntervalKey:
|
|
|
|
+ case maxBufferedEventsKey:
|
|
default:
|
|
default:
|
|
return fmt.Errorf("unknown log opt '%s' for %s log driver", key, name)
|
|
return fmt.Errorf("unknown log opt '%s' for %s log driver", key, name)
|
|
}
|
|
}
|
|
@@ -684,6 +743,16 @@ func ValidateLogOpt(cfg map[string]string) error {
|
|
return fmt.Errorf("must specify valid value for log opt '%s': %v", logCreateGroupKey, err)
|
|
return fmt.Errorf("must specify valid value for log opt '%s': %v", logCreateGroupKey, err)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
+ if cfg[forceFlushIntervalKey] != "" {
|
|
|
|
+ if value, err := strconv.Atoi(cfg[forceFlushIntervalKey]); err != nil || value <= 0 {
|
|
|
|
+ return fmt.Errorf("must specify a positive integer for log opt '%s': %v", forceFlushIntervalKey, cfg[forceFlushIntervalKey])
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ if cfg[maxBufferedEventsKey] != "" {
|
|
|
|
+ if value, err := strconv.Atoi(cfg[maxBufferedEventsKey]); err != nil || value <= 0 {
|
|
|
|
+ return fmt.Errorf("must specify a positive integer for log opt '%s': %v", maxBufferedEventsKey, cfg[maxBufferedEventsKey])
|
|
|
|
+ }
|
|
|
|
+ }
|
|
_, datetimeFormatKeyExists := cfg[datetimeFormatKey]
|
|
_, datetimeFormatKeyExists := cfg[datetimeFormatKey]
|
|
_, multilinePatternKeyExists := cfg[multilinePatternKey]
|
|
_, multilinePatternKeyExists := cfg[multilinePatternKey]
|
|
if datetimeFormatKeyExists && multilinePatternKeyExists {
|
|
if datetimeFormatKeyExists && multilinePatternKeyExists {
|