From a29eeb52b6046ce5a8ca7a8ee138c5e69493cd0e Mon Sep 17 00:00:00 2001 From: Amir Zarrinkafsh Date: Wed, 25 Nov 2020 10:46:41 +1100 Subject: [PATCH] [FEATURE] Add JSON log formatting option (#1488) This change adds the ability to format Authelia's log output as JSON. Example below: ``` {"level":"info","msg":"Logging severity set to info","time":"2020-01-01T00:00:00+11:00"} {"level":"info","msg":"Authelia is listening for non-TLS connections on 0.0.0.0:9091","time":"2020-01-01T00:00:00+11:00"} ``` --- cmd/authelia/main.go | 2 +- config.template.yml | 4 ++- docs/configuration/miscellaneous.md | 23 ++++++++++++++++- .../configuration/schema/configuration.go | 1 + .../validator/configuration_test.go | 1 + internal/configuration/validator/const.go | 1 + internal/logging/logger.go | 8 +++++- internal/logging/logger_test.go | 25 ++++++++++++++++++- 8 files changed, 60 insertions(+), 5 deletions(-) diff --git a/cmd/authelia/main.go b/cmd/authelia/main.go index 3a99fba9f..1c8fa18be 100644 --- a/cmd/authelia/main.go +++ b/cmd/authelia/main.go @@ -35,7 +35,7 @@ func startServer() { os.Exit(1) } - if err := logging.InitializeLogger(config.LogFilePath); err != nil { + if err := logging.InitializeLogger(config.LogFormat, config.LogFilePath); err != nil { logging.Logger().Fatalf("Cannot initialize logger: %v", err) } diff --git a/config.template.yml b/config.template.yml index b4ac2a304..305317c51 100644 --- a/config.template.yml +++ b/config.template.yml @@ -21,7 +21,9 @@ server: # Level of verbosity for logs: info, debug, trace log_level: debug -## File path where the logs will be written. If not set logs are written to stdout. +# Format the logs are written as: json, text +# log_format: json +# File path where the logs will be written. If not set logs are written to stdout. # log_file_path: /config/authelia.log # The secret used to generate JWT tokens when validating user identity by diff --git a/docs/configuration/miscellaneous.md b/docs/configuration/miscellaneous.md index 45798e2e4..cf378b6d5 100644 --- a/docs/configuration/miscellaneous.md +++ b/docs/configuration/miscellaneous.md @@ -47,6 +47,28 @@ generate a large amount of log entries and expose the `/debug/vars` and log_level: debug ``` +### Log format + +`optional: true` + +Defines the format of the logs written by Authelia. +This format can be set to `json` or `text`. + +```yaml +log_format: json +``` + +#### JSON format +``` +{"level":"info","msg":"Logging severity set to info","time":"2020-01-01T00:00:00+11:00"} +{"level":"info","msg":"Authelia is listening for non-TLS connections on 0.0.0.0:9091","time":"2020-01-01T00:00:00+11:00"} +``` +#### Text format +``` +time="2020-01-01T00:00:00+11:00" level=info msg="Logging severity set to info" +time="2020-01-01T00:00:00+11:00" level=info msg="Authelia is listening for non-TLS connections on 0.0.0.0:9091" +``` + ### Log file path `optional: true` @@ -61,7 +83,6 @@ logs over time to prevent significant long-term disk usage. log_file_path: /config/authelia.log ``` - ## JWT Secret `optional: false` diff --git a/internal/configuration/schema/configuration.go b/internal/configuration/schema/configuration.go index 8adad2e34..eae5a1fa2 100644 --- a/internal/configuration/schema/configuration.go +++ b/internal/configuration/schema/configuration.go @@ -7,6 +7,7 @@ type Configuration struct { TLSCert string `mapstructure:"tls_cert"` TLSKey string `mapstructure:"tls_key"` LogLevel string `mapstructure:"log_level"` + LogFormat string `mapstructure:"log_format"` LogFilePath string `mapstructure:"log_file_path"` JWTSecret string `mapstructure:"jwt_secret"` DefaultRedirectionURL string `mapstructure:"default_redirection_url"` diff --git a/internal/configuration/validator/configuration_test.go b/internal/configuration/validator/configuration_test.go index 3d3dc9af4..6811030cd 100644 --- a/internal/configuration/validator/configuration_test.go +++ b/internal/configuration/validator/configuration_test.go @@ -14,6 +14,7 @@ func newDefaultConfig() schema.Configuration { config.Host = "127.0.0.1" config.Port = 9090 config.LogLevel = "info" + config.LogFormat = "text" config.JWTSecret = testJWTSecret config.AuthenticationBackend.File = new(schema.FileAuthenticationBackendConfiguration) config.AuthenticationBackend.File.Path = "/a/path" diff --git a/internal/configuration/validator/const.go b/internal/configuration/validator/const.go index 3a42f9862..461e0c710 100644 --- a/internal/configuration/validator/const.go +++ b/internal/configuration/validator/const.go @@ -5,6 +5,7 @@ var validKeys = []string{ "host", "port", "log_level", + "log_format", "log_file_path", "default_redirection_url", "jwt_secret", diff --git a/internal/logging/logger.go b/internal/logging/logger.go index c8c9b2553..b81ddd927 100644 --- a/internal/logging/logger.go +++ b/internal/logging/logger.go @@ -18,11 +18,17 @@ func SetLevel(level logrus.Level) { } // InitializeLogger initialize logger. -func InitializeLogger(filename string) error { +func InitializeLogger(format, filename string) error { callerLevels := []logrus.Level{} stackLevels := []logrus.Level{logrus.PanicLevel, logrus.FatalLevel, logrus.ErrorLevel} logrus.AddHook(logrus_stack.NewHook(callerLevels, stackLevels)) + if format == "json" { + logrus.SetFormatter(&logrus.JSONFormatter{}) + } else { + logrus.SetFormatter(&logrus.TextFormatter{}) + } + if filename != "" { f, err := os.OpenFile(filename, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0600) diff --git a/internal/logging/logger_test.go b/internal/logging/logger_test.go index 24e3e052d..7f785759c 100644 --- a/internal/logging/logger_test.go +++ b/internal/logging/logger_test.go @@ -20,7 +20,7 @@ func TestShouldWriteLogsToFile(t *testing.T) { defer os.RemoveAll(dir) path := fmt.Sprintf("%s/authelia.log", dir) - err = InitializeLogger(path) + err = InitializeLogger("text", path) require.NoError(t, err) Logger().Info("This is a test") @@ -33,3 +33,26 @@ func TestShouldWriteLogsToFile(t *testing.T) { assert.Contains(t, string(b), "level=info msg=\"This is a test\"\n") } + +func TestShouldFormatLogsAsJSON(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("json", path) + 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\",") +}