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")