logger/awslogs/logger.go (140 lines of code) (raw):

// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 // Package awslogs provides functionalities for integrating the awslogs logging driver // with shim-loggers-for-containerd. package awslogs import ( "context" "fmt" "github.com/aws/shim-loggers-for-containerd/debug" "github.com/aws/shim-loggers-for-containerd/logger" "github.com/containerd/containerd/runtime/v2/logging" dockerawslogs "github.com/docker/docker/daemon/logger/awslogs" ) const ( // awslogs driver options. // RegionKey specifies the AWS logging region. RegionKey = "awslogs-region" // GroupKey denotes the AWS logging group name. GroupKey = "awslogs-group" // CreateGroupKey is a flag to create a new AWS logging group. CreateGroupKey = "awslogs-create-group" // StreamKey specifies the AWS logging stream name. StreamKey = "awslogs-stream" // CreateStreamKey is a flag to create a new log stream. CreateStreamKey = "awslogs-create-stream" // MultilinePatternKey defines the pattern for multiline logs. MultilinePatternKey = "awslogs-multiline-pattern" // DatetimeFormatKey specifies the datetime format of the logs. DatetimeFormatKey = "awslogs-datetime-format" // CredentialsEndpointKey denotes the AWS credentials endpoint (not actual credentials). CredentialsEndpointKey = "awslogs-credentials-endpoint" //nolint:gosec // not credentials // EndpointKey is the AWS logging endpoint. EndpointKey = "awslogs-endpoint" // LogFormatKey is used to explicitly set EMF header. LogFormatKey = "awslogs-format" // JSONEmfLogFormat currently only 'json/emf' is supported. // See: https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/CloudWatch_Embedded_Metric_Format_Specification.html JSONEmfLogFormat = "json/emf" // There are 26 bytes additional bytes for each log event: // See more details in: http://docs.aws.amazon.com/AmazonCloudWatchLogs/latest/APIReference/API_PutLogEvents.html perEventBytes = 26 // The value of maximumBytesPerEvent is adopted from Docker. Reference: // https://github.com/moby/moby/blob/19.03/daemon/logger/awslogs/cloudwatchlogs.go#L58 maximumBytesPerEvent = 262144 - perEventBytes // The max size of CloudWatch events is 256kb. defaultAwsBufSizeInBytes = 256 * 1024 ) // Args represents AWSlogs driver arguments. type Args struct { // Required arguments. Group string Region string Stream string CredentialsEndpoint string // Optional arguments. CreateGroup string CreateStream string MultilinePattern string DatetimeFormat string Endpoint string LogsFormatHeader string } // LoggerArgs stores global logger args and awslogs specific args. type LoggerArgs struct { globalArgs *logger.GlobalArgs args *Args } // InitLogger initialize the input arguments. func InitLogger(globalArgs *logger.GlobalArgs, awslogsArgs *Args) *LoggerArgs { return &LoggerArgs{ globalArgs: globalArgs, args: awslogsArgs, } } // RunLogDriver initiates an awslogs driver and starts driving container logs to cloudwatch. func (la *LoggerArgs) RunLogDriver(ctx context.Context, config *logging.Config, ready func() error) error { defer debug.DeferFuncForRunLogDriver() loggerConfig := getAWSLogsConfig(la.args) if err := validateLogOptCompatability(loggerConfig); err != nil { debug.ErrLogger = fmt.Errorf("incompatible logger options: %w", err) return debug.ErrLogger } info := logger.NewInfo( la.globalArgs.ContainerID, la.globalArgs.ContainerName, logger.WithConfig(loggerConfig), ) stream, err := dockerawslogs.New(*info) if err != nil { debug.ErrLogger = fmt.Errorf("unable to create stream: %w", err) return debug.ErrLogger } l, err := logger.NewLogger( logger.WithStdout(config.Stdout), logger.WithStderr(config.Stderr), logger.WithInfo(info), logger.WithStream(stream), logger.WithBufferSizeInBytes(maximumBytesPerEvent), ) if err != nil { debug.ErrLogger = fmt.Errorf("unable to create awslogs driver: %w", err) return debug.ErrLogger } if la.globalArgs.Mode == logger.NonBlockingMode { debug.SendEventsToLog(logger.DaemonName, "Starting log streaming for non-blocking mode awslogs driver", debug.INFO, 0) l = logger.NewBufferedLogger(l, defaultAwsBufSizeInBytes, la.globalArgs.MaxBufferSize, la.globalArgs.ContainerID) } // Start awslogs driver debug.SendEventsToLog(logger.DaemonName, "Starting log streaming for awslogs driver", debug.INFO, 0) err = l.Start(ctx, la.globalArgs.CleanupTime, ready) if err != nil { debug.ErrLogger = fmt.Errorf("failed to run awslogs driver: %w", err) // Do not return error if log driver has issue sending logs to destination, because if error // returned here, containerd will identify this error and kill shim process, which will kill // the container process accordingly. // Note: the container will continue to run if shim logger exits after. // Reference: https://github.com/containerd/containerd/blob/release/1.3/runtime/v2/logging/logging.go return nil } debug.SendEventsToLog(logger.DaemonName, "Logging finished", debug.INFO, 1) return nil } // getAWSLogsConfig sets values for awslogs config. func getAWSLogsConfig(args *Args) map[string]string { config := make(map[string]string) // Required arguments config[GroupKey] = args.Group config[RegionKey] = args.Region config[StreamKey] = args.Stream config[CredentialsEndpointKey] = args.CredentialsEndpoint // Optional arguments createGroup := args.CreateGroup if createGroup != "" { config[CreateGroupKey] = createGroup } createStream := args.CreateStream if createStream != "" { config[CreateStreamKey] = createStream } multilinePattern := args.MultilinePattern if multilinePattern != "" { config[MultilinePatternKey] = multilinePattern } datetimeFormat := args.DatetimeFormat if datetimeFormat != "" { config[DatetimeFormatKey] = datetimeFormat } endpoint := args.Endpoint if endpoint != "" { config[EndpointKey] = endpoint } logsFormatHeader := args.LogsFormatHeader if logsFormatHeader != "" { config[LogFormatKey] = logsFormatHeader } return config } func validateLogOptCompatability(cfg map[string]string) error { _, datetimeFormatKeyExists := cfg[DatetimeFormatKey] _, multilinePatternKeyExists := cfg[MultilinePatternKey] if cfg[LogFormatKey] != "" { // Only json/emf is supported at the moment. if cfg[LogFormatKey] != JSONEmfLogFormat { return fmt.Errorf("unsupported log format '%s'", cfg[LogFormatKey]) } if datetimeFormatKeyExists || multilinePatternKeyExists { return fmt.Errorf( "you cannot configure log opt '%s' or '%s' when log opt '%s' is set to '%s'", DatetimeFormatKey, MultilinePatternKey, LogFormatKey, JSONEmfLogFormat, ) } } return nil }