From 05df642f3e60ccd35d788a3511d645f86e01f7fe Mon Sep 17 00:00:00 2001 From: Amir Zarrinkafsh Date: Mon, 31 May 2021 14:02:54 +1000 Subject: [PATCH] feat: add option to keep stdout logging with log_file_path (#2037) Currently if a `log_file_path` is defined Authelia will redirect all logging from standard output to said defined location. This change allows users to keep standard output logging along with a defined `log_file_path`. --- cmd/authelia/main.go | 2 +- config.template.yml | 3 +++ docs/configuration/miscellaneous.md | 17 ++++++++++++ internal/configuration/config.template.yml | 3 +++ .../configuration/schema/configuration.go | 1 + internal/logging/logger.go | 10 +++++-- internal/logging/logger_test.go | 27 +++++++++++++++++-- 7 files changed, 58 insertions(+), 5 deletions(-) diff --git a/cmd/authelia/main.go b/cmd/authelia/main.go index a23688fb3..7422bf872 100644 --- a/cmd/authelia/main.go +++ b/cmd/authelia/main.go @@ -52,7 +52,7 @@ func startServer() { } } - if err := logging.InitializeLogger(config.LogFormat, config.LogFilePath); err != nil { + if err := logging.InitializeLogger(config.LogFormat, config.LogFilePath, config.LogKeepStdout); err != nil { logger.Fatalf("Cannot initialize logger: %v", err) } diff --git a/config.template.yml b/config.template.yml index b279bb11a..5843bad02 100644 --- a/config.template.yml +++ b/config.template.yml @@ -43,6 +43,9 @@ log_level: debug ## File path where the logs will be written. If not set logs are written to stdout. # log_file_path: /config/authelia.log +## Whether to also log to stdout when a log_file_path is defined. +# log_keep_stdout: false + ## The secret used to generate JWT tokens when validating user identity by email confirmation. JWT Secret can also be ## set using a secret: https://www.authelia.com/docs/configuration/secrets.html jwt_secret: a_very_important_secret diff --git a/docs/configuration/miscellaneous.md b/docs/configuration/miscellaneous.md index 82abaeaec..f3c823935 100644 --- a/docs/configuration/miscellaneous.md +++ b/docs/configuration/miscellaneous.md @@ -158,6 +158,23 @@ they rotate and/or truncate the logs over time to prevent significant long-term log_file_path: /config/authelia.log ``` +### log_keep_stdout +
+type: boolean +{: .label .label-config .label-purple } +default: false +{: .label .label-config .label-blue } +required: no +{: .label .label-config .label-green } +
+ +Overrides the behaviour to redirect logging only to the `log_file_path`. If set to `true` logs will be written to both +standard output, and the defined logging location. + +```yaml +log_keep_stdout: true +``` + ## jwt_secret
type: string diff --git a/internal/configuration/config.template.yml b/internal/configuration/config.template.yml index b279bb11a..5843bad02 100644 --- a/internal/configuration/config.template.yml +++ b/internal/configuration/config.template.yml @@ -43,6 +43,9 @@ log_level: debug ## File path where the logs will be written. If not set logs are written to stdout. # log_file_path: /config/authelia.log +## Whether to also log to stdout when a log_file_path is defined. +# log_keep_stdout: false + ## The secret used to generate JWT tokens when validating user identity by email confirmation. JWT Secret can also be ## set using a secret: https://www.authelia.com/docs/configuration/secrets.html jwt_secret: a_very_important_secret diff --git a/internal/configuration/schema/configuration.go b/internal/configuration/schema/configuration.go index 89434d351..d5390e44e 100644 --- a/internal/configuration/schema/configuration.go +++ b/internal/configuration/schema/configuration.go @@ -11,6 +11,7 @@ type Configuration struct { LogLevel string `mapstructure:"log_level"` LogFormat string `mapstructure:"log_format"` LogFilePath string `mapstructure:"log_file_path"` + LogKeepStdout bool `mapstructure:"log_keep_stdout"` JWTSecret string `mapstructure:"jwt_secret"` DefaultRedirectionURL string `mapstructure:"default_redirection_url"` diff --git a/internal/logging/logger.go b/internal/logging/logger.go index d0b1e8ae5..b4114ef78 100644 --- a/internal/logging/logger.go +++ b/internal/logging/logger.go @@ -1,6 +1,7 @@ package logging import ( + "io" "os" logrus_stack "github.com/Gurpartap/logrus-stack" @@ -18,7 +19,7 @@ func SetLevel(level logrus.Level) { } // InitializeLogger initialize logger. -func InitializeLogger(format, filename string) error { +func InitializeLogger(format, filename string, stdout bool) error { callerLevels := []logrus.Level{} stackLevels := []logrus.Level{logrus.PanicLevel, logrus.FatalLevel, logrus.ErrorLevel} logrus.AddHook(logrus_stack.NewHook(callerLevels, stackLevels)) @@ -43,7 +44,12 @@ func InitializeLogger(format, filename string) error { }) } - logrus.SetOutput(f) + if stdout { + logLocations := io.MultiWriter(os.Stdout, f) + logrus.SetOutput(logLocations) + } else { + logrus.SetOutput(f) + } } return nil diff --git a/internal/logging/logger_test.go b/internal/logging/logger_test.go index 7f785759c..a68e0824f 100644 --- a/internal/logging/logger_test.go +++ b/internal/logging/logger_test.go @@ -20,7 +20,30 @@ func TestShouldWriteLogsToFile(t *testing.T) { defer os.RemoveAll(dir) path := fmt.Sprintf("%s/authelia.log", dir) - err = InitializeLogger("text", path) + err = InitializeLogger("text", path, false) + require.NoError(t, err) + + Logger().Info("This is a test") + + f, err := os.OpenFile(path, os.O_RDONLY, 0) + require.NoError(t, err) + + b, err := ioutil.ReadAll(f) + require.NoError(t, err) + + assert.Contains(t, string(b), "level=info msg=\"This is a test\"\n") +} + +func TestShouldWriteLogsToFileAndStdout(t *testing.T) { + dir, err := ioutil.TempDir("/tmp", "logs-dir") + if err != nil { + log.Fatal(err) + } + + defer os.RemoveAll(dir) + + path := fmt.Sprintf("%s/authelia.log", dir) + err = InitializeLogger("text", path, true) require.NoError(t, err) Logger().Info("This is a test") @@ -43,7 +66,7 @@ func TestShouldFormatLogsAsJSON(t *testing.T) { defer os.RemoveAll(dir) path := fmt.Sprintf("%s/authelia.log", dir) - err = InitializeLogger("json", path) + err = InitializeLogger("json", path, false) require.NoError(t, err) Logger().Info("This is a test")