From 5926ea2476460273fa17dc4264de83af3fbc38f4 Mon Sep 17 00:00:00 2001 From: yiyuanzzz Date: Tue, 20 Feb 2024 16:10:55 -0800 Subject: [PATCH] making the ECS init log level configurable --- README.md | 1 - ecs-init/config/common.go | 2 +- ecs-init/config/logger.go | 46 -------------- ecs-init/ecs-init.go | 11 +--- ecs-init/logger/log.go | 119 +++++++++++++++++++++++++++++++++++ ecs-init/logger/log_test.go | 96 ++++++++++++++++++++++++++++ scripts/bundle_log_config.sh | 41 ------------ seelog.xml | 2 +- 8 files changed, 220 insertions(+), 98 deletions(-) delete mode 100644 ecs-init/config/logger.go create mode 100644 ecs-init/logger/log.go create mode 100644 ecs-init/logger/log_test.go delete mode 100755 scripts/bundle_log_config.sh diff --git a/README.md b/README.md index 0f5cd1bd403..4083dc2ccfd 100644 --- a/README.md +++ b/README.md @@ -270,7 +270,6 @@ Additionally, the following environment variable(s) can be used to configure the | `ECS_AGENT_APPARMOR_PROFILE` | `unconfined` | Specifies the name of the AppArmor profile to run the ecs-agent container under. This only applies to AppArmor-enabled systems, such as Ubuntu, Debian, and SUSE. If unset, defaults to the profile written out by ecs-init (ecs-agent-default). | `ecs-agent-default` | - ### Persistence When you run the Amazon ECS Container Agent in production, its `datadir` should be persisted between runs of the Docker diff --git a/ecs-init/config/common.go b/ecs-init/config/common.go index 2bd9c7518e9..6661def724b 100644 --- a/ecs-init/config/common.go +++ b/ecs-init/config/common.go @@ -173,7 +173,7 @@ func LogDirectory() string { return directoryPrefix + "/var/log/ecs" } -func initLogFile() string { +func InitLogFile() string { return LogDirectory() + "/ecs-init.log" } diff --git a/ecs-init/config/logger.go b/ecs-init/config/logger.go deleted file mode 100644 index 20699f3c885..00000000000 --- a/ecs-init/config/logger.go +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright 2015 Amazon.com, Inc. or its affiliates. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"). You may -// not use this file except in compliance with the License. A copy of the -// License is located at -// -// http://aws.amazon.com/apache2.0/ -// -// or in the "license" file accompanying this file. This file is distributed -// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either -// express or implied. See the License for the specific language governing -// permissions and limitations under the License. - -// GENERATED FILE, DO NOT MODIFY BY HAND -//go:generate bundle_log_config.sh - -package config - -// Logger holds the bundled log configuration for seelog -func Logger() string { - return ` - - - - - - - - - - - -` -} diff --git a/ecs-init/ecs-init.go b/ecs-init/ecs-init.go index ecf0c7c56e6..93e1104cadc 100644 --- a/ecs-init/ecs-init.go +++ b/ecs-init/ecs-init.go @@ -18,8 +18,8 @@ import ( "fmt" "os" - "github.com/aws/amazon-ecs-agent/ecs-init/config" "github.com/aws/amazon-ecs-agent/ecs-init/engine" + "github.com/aws/amazon-ecs-agent/ecs-init/logger" "github.com/aws/amazon-ecs-agent/ecs-init/version" log "github.com/cihub/seelog" @@ -37,7 +37,6 @@ const ( ) func main() { - defer log.Flush() flag.Parse() args := flag.Args() @@ -46,12 +45,8 @@ func main() { os.Exit(1) } - logger, err := log.LoggerFromConfigAsString(config.Logger()) - if err != nil { - die(err, engine.DefaultInitErrorExitCode) - } - log.ReplaceLogger(logger) - + logger.Setup() + defer log.Flush() if args[0] == VERSION { err := version.PrintVersion() if err != nil { diff --git a/ecs-init/logger/log.go b/ecs-init/logger/log.go new file mode 100644 index 00000000000..73443fc19ca --- /dev/null +++ b/ecs-init/logger/log.go @@ -0,0 +1,119 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"). You may +// not use this file except in compliance with the License. A copy of the +// License is located at +// +// http://aws.amazon.com/apache2.0/ +// +// or in the "license" file accompanying this file. This file is distributed +// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either +// express or implied. See the License for the specific language governing +// permissions and limitations under the License. + +package logger + +import ( + "fmt" + "os" + "strconv" + "strings" + "sync" + "time" + + conf "github.com/aws/amazon-ecs-agent/ecs-init/config" + "github.com/cihub/seelog" +) + +const ( + LOGLEVEL_ENV_VAR = "ECS_LOGLEVEL" + defaultLogLevel = "info" + outputFmt = "logfmt" + rollCount = 24 +) + +// logLevels is the mapping from ECS_INIT_LOGLEVEL to Seelog provided levels. +var logLevels = map[string]string{ + "debug": "debug", + "info": "info", + "warn": "warn", + "error": "error", + "crit": "critical", + "none": "off", +} + +type logConfig struct { + level string + outputFormat string + maxRollCount int + lock sync.Mutex +} + +// config contains config for seelog logger +var config *logConfig + +func init() { + config = &logConfig{ + level: defaultLogLevel, + outputFormat: outputFmt, + maxRollCount: rollCount, + } +} + +// Setup sets the custom logging config +func Setup() { + if logLevel := os.Getenv(LOGLEVEL_ENV_VAR); logLevel != "" { + SetLogLevel(logLevel) + } + if err := seelog.RegisterCustomFormatter("InitLogfmt", logfmtFormatter); err != nil { + seelog.Error(err) + } + reloadConfig() +} + +func logfmtFormatter(params string) seelog.FormatterFunc { + return func(message string, level seelog.LogLevel, context seelog.LogContextInterface) interface{} { + return fmt.Sprintf(`level=%s time=%s msg=%q +`, level.String(), context.CallTime().UTC().Format(time.RFC3339), message) + } +} + +func SetLogLevel(logLevel string) { + parsedLevel, ok := logLevels[strings.ToLower(logLevel)] + if ok { + config.lock.Lock() + defer config.lock.Unlock() + config.level = parsedLevel + reloadConfig() + } else { + seelog.Error("log level mapping not found") + } +} + +func reloadConfig() { + logger, err := seelog.LoggerFromConfigAsString(seelogConfig()) + if err == nil { + seelog.ReplaceLogger(logger) + } else { + seelog.Error(err) + } +} + +func seelogConfig() string { + c := ` + + + ` + if conf.InitLogFile() != "" { + c += ` + ` + } + c += ` + + + + +` + return c +} diff --git a/ecs-init/logger/log_test.go b/ecs-init/logger/log_test.go new file mode 100644 index 00000000000..c64292db7a5 --- /dev/null +++ b/ecs-init/logger/log_test.go @@ -0,0 +1,96 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"). You may +// not use this file except in compliance with the License. A copy of the +// License is located at +// +// http://aws.amazon.com/apache2.0/ +// +// or in the "license" file accompanying this file. This file is distributed +// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either +// express or implied. See the License for the specific language governing +// permissions and limitations under the License. + +package logger + +import ( + "testing" + "time" + + conf "github.com/aws/amazon-ecs-agent/ecs-init/config" + "github.com/cihub/seelog" + "github.com/stretchr/testify/assert" +) + +func TestLogfmtFormat(t *testing.T) { + logfmt := logfmtFormatter("") + out := logfmt("This is my log message", seelog.InfoLvl, &LogContextMock{}) + s, ok := out.(string) + assert.True(t, ok) + assert.Equal(t, `level=info time=2018-10-01T01:02:03Z msg="This is my log message" +`, s) +} + +func TestSeelogConfig(t *testing.T) { + config = &logConfig{ + level: defaultLogLevel, + outputFormat: outputFmt, + maxRollCount: 24, + } + c := seelogConfig() + assert.Equal(t, ` + + + + + + + + +`, c) +} + +type LogContextMock struct{} + +// Caller's function name. +func (l *LogContextMock) Func() string { + return "" +} + +// Caller's line number. +func (l *LogContextMock) Line() int { + return 0 +} + +// Caller's file short path (in slashed form). +func (l *LogContextMock) ShortPath() string { + return "" +} + +// Caller's file full path (in slashed form). +func (l *LogContextMock) FullPath() string { + return "" +} + +// True if the context is correct and may be used. +// If false, then an error in context evaluation occurred and +// all its other data may be corrupted. +func (l *LogContextMock) IsValid() bool { + return true +} + +// Time when log function was called. +func (l *LogContextMock) CallTime() time.Time { + return time.Date(2018, time.October, 1, 1, 2, 3, 0, time.UTC) +} + +// Custom context that can be set by calling logger.SetContext +func (l *LogContextMock) CustomContext() interface{} { + return map[string]string{} +} + +// Caller's file name (without path). +func (l *LogContextMock) FileName() string { + return "mytestmodule.go" +} diff --git a/scripts/bundle_log_config.sh b/scripts/bundle_log_config.sh deleted file mode 100755 index dcd35d9a9b1..00000000000 --- a/scripts/bundle_log_config.sh +++ /dev/null @@ -1,41 +0,0 @@ -#!/bin/bash -# Copyright 2015 Amazon.com, Inc. or its affiliates. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the -# "License"). You may not use this file except in compliance -# with the License. A copy of the License is located at -# -# http://aws.amazon.com/apache2.0/ -# -# or in the "license" file accompanying this file. This file is -# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR -# CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and -# limitations under the License. - -cat > $(pwd)/logger.go < -