From d45d8b1fb006fcc2f5304809b3addb49cd09a3ce Mon Sep 17 00:00:00 2001
From: Travis Ralston <travpc@gmail.com>
Date: Thu, 15 Apr 2021 22:17:35 -0600
Subject: [PATCH] Add structured logging support

---
 CHANGELOG.md                          |  4 ++++
 cmd/export_synapse_for_import/main.go |  2 +-
 cmd/gdpr_export/main.go               |  2 +-
 cmd/gdpr_import/main.go               |  2 +-
 cmd/import_synapse/main.go            |  2 +-
 cmd/media_repo/main.go                |  2 +-
 common/config/conf_main.go            |  1 +
 common/config/models_main.go          |  1 +
 common/logging/logger.go              | 15 +++++++++++----
 config.sample.yaml                    |  4 ++++
 10 files changed, 26 insertions(+), 9 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index bfbde2f2..c7691656 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,6 +7,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
 
 ## [Unreleased]
 
+### Added
+
+* Added support for structured logging (JSON).
+
 ### Changed
 
 * Turned color-coded logs off by default. This can be changed in the config.
diff --git a/cmd/export_synapse_for_import/main.go b/cmd/export_synapse_for_import/main.go
index 6c8f5ff0..84a86203 100644
--- a/cmd/export_synapse_for_import/main.go
+++ b/cmd/export_synapse_for_import/main.go
@@ -58,7 +58,7 @@ func main() {
 		realPsqlPassword = *postgresPassword
 	}
 
-	err := logging.Setup(config.Get().General.LogDirectory)
+	err := logging.Setup(config.Get().General.LogDirectory, config.Get().General.LogColors, config.Get().General.JsonLogs)
 	if err != nil {
 		panic(err)
 	}
diff --git a/cmd/gdpr_export/main.go b/cmd/gdpr_export/main.go
index c3213de6..06d31222 100644
--- a/cmd/gdpr_export/main.go
+++ b/cmd/gdpr_export/main.go
@@ -45,7 +45,7 @@ func main() {
 	assets.SetupTemplates(*templatesPath)
 
 	var err error
-	err = logging.Setup(config.Get().General.LogDirectory)
+	err = logging.Setup(config.Get().General.LogDirectory, config.Get().General.LogColors, config.Get().General.JsonLogs)
 	if err != nil {
 		panic(err)
 	}
diff --git a/cmd/gdpr_import/main.go b/cmd/gdpr_import/main.go
index 76f6adc0..c69035a0 100644
--- a/cmd/gdpr_import/main.go
+++ b/cmd/gdpr_import/main.go
@@ -35,7 +35,7 @@ func main() {
 	assets.SetupMigrations(*migrationsPath)
 
 	var err error
-	err = logging.Setup(config.Get().General.LogDirectory)
+	err = logging.Setup(config.Get().General.LogDirectory, config.Get().General.LogColors, config.Get().General.JsonLogs)
 	if err != nil {
 		panic(err)
 	}
diff --git a/cmd/import_synapse/main.go b/cmd/import_synapse/main.go
index 958c8b9a..db184c78 100644
--- a/cmd/import_synapse/main.go
+++ b/cmd/import_synapse/main.go
@@ -72,7 +72,7 @@ func main() {
 		realPsqlPassword = *postgresPassword
 	}
 
-	err := logging.Setup(config.Get().General.LogDirectory)
+	err := logging.Setup(config.Get().General.LogDirectory, config.Get().General.LogColors, config.Get().General.JsonLogs)
 	if err != nil {
 		panic(err)
 	}
diff --git a/cmd/media_repo/main.go b/cmd/media_repo/main.go
index 2433e3c3..06179992 100644
--- a/cmd/media_repo/main.go
+++ b/cmd/media_repo/main.go
@@ -59,7 +59,7 @@ func main() {
 	assets.SetupTemplates(*templatesPath)
 	assets.SetupAssets(*assetsPath)
 
-	err := logging.Setup(config.Get().General.LogDirectory, config.Get().General.LogColors)
+	err := logging.Setup(config.Get().General.LogDirectory, config.Get().General.LogColors, config.Get().General.JsonLogs)
 	if err != nil {
 		panic(err)
 	}
diff --git a/common/config/conf_main.go b/common/config/conf_main.go
index e5537bc0..d6b5dc4f 100644
--- a/common/config/conf_main.go
+++ b/common/config/conf_main.go
@@ -25,6 +25,7 @@ func NewDefaultMainConfig() MainRepoConfig {
 			Port:             8000,
 			LogDirectory:     "logs",
 			LogColors:        false,
+			JsonLogs:         false,
 			TrustAnyForward:  false,
 			UseForwardedHost: true,
 		},
diff --git a/common/config/models_main.go b/common/config/models_main.go
index 0690d1aa..a331754b 100644
--- a/common/config/models_main.go
+++ b/common/config/models_main.go
@@ -5,6 +5,7 @@ type GeneralConfig struct {
 	Port             int    `yaml:"port"`
 	LogDirectory     string `yaml:"logDirectory"`
 	LogColors        bool   `yaml:"logColors"`
+	JsonLogs         bool   `yaml:"jsonLogs"`
 	TrustAnyForward  bool   `yaml:"trustAnyForwardedAddress"`
 	UseForwardedHost bool   `yaml:"useForwardedHost"`
 }
diff --git a/common/logging/logger.go b/common/logging/logger.go
index bbd95334..86cba363 100644
--- a/common/logging/logger.go
+++ b/common/logging/logger.go
@@ -19,17 +19,24 @@ func (f utcFormatter) Format(entry *logrus.Entry) ([]byte, error) {
 	return f.Formatter.Format(entry)
 }
 
-func Setup(dir string, colors bool) error {
-	formatter := &utcFormatter{
-		&logrus.TextFormatter{
+func Setup(dir string, colors bool, json bool) error {
+	var lineFormatter logrus.Formatter
+	if json {
+		lineFormatter = &logrus.JSONFormatter{
+			TimestampFormat:  "2006-01-02 15:04:05.000 Z07:00",
+			DisableTimestamp: false,
+		}
+	} else {
+		lineFormatter = &logrus.TextFormatter{
 			TimestampFormat:  "2006-01-02 15:04:05.000 Z07:00",
 			FullTimestamp:    true,
 			ForceColors:      colors,
 			DisableColors:    !colors,
 			DisableTimestamp: false,
 			QuoteEmptyFields: true,
-		},
+		}
 	}
+	formatter := &utcFormatter{lineFormatter}
 	logrus.SetFormatter(formatter)
 	logrus.SetOutput(os.Stdout)
 
diff --git a/config.sample.yaml b/config.sample.yaml
index 69d5bb21..7c9150c1 100644
--- a/config.sample.yaml
+++ b/config.sample.yaml
@@ -15,6 +15,10 @@ repo:
   # appear in logs which render them unreadable, which is why colors are disabled by default.
   logColors: false
 
+  # Set to true to enable JSON logging for consumption by things like logstash. Note that this is
+  # incompatible with the log color option and will always render without colors.
+  jsonLogs: false
+
   # If true, the media repo will accept any X-Forwarded-For header without validation. In most cases
   # this option should be left as "false". Note that the media repo already expects an X-Forwarded-For
   # header, but validates it to ensure the IP being given makes sense.
-- 
GitLab