Repository: authelia/authelia (go)

Issue:
Log format json
Hello,

Is there a way to format logs in json ? (I'm using it in a docker-compose file along with traefik so far)

Could not find anything on the docs.
if not, it would be great to have this option

Thank you

Pull request description:
[FEATURE] Add JSON log formatting option
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"}
```

Closes #1456.

Patch:
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/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)
 


Test patch:
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/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\",")
+}