about summary refs log tree commit diff
diff options
context:
space:
mode:
authorVincent Ambo <tazjin@google.com>2019-10-05T13·55+0100
committerVincent Ambo <github@tazj.in>2019-10-06T22·05+0100
commit95abb1bcde75253aa35669eed26f734d02c6a870 (patch)
tree2c5efe518637693124c748fa3233dc17d44d6c51
parent0642f7044dea2127b1c7dab1d88d90638536183a (diff)
feat(server): Initial Stackdriver-compatible log formatter
This formatter has basic support for the Stackdriver Error Reporting
format, but several things are still lacking:

* the service version (preferably git commit?) needs to be included in
  the server somehow
* log streams should be split between stdout/stderr as that is how
  AppEngine (and several other GCP services?) seemingly differentiate
  between info/error logs
-rw-r--r--tools/nixery/server/logs.go68
1 files changed, 68 insertions, 0 deletions
diff --git a/tools/nixery/server/logs.go b/tools/nixery/server/logs.go
new file mode 100644
index 000000000000..55e0a13a03ea
--- /dev/null
+++ b/tools/nixery/server/logs.go
@@ -0,0 +1,68 @@
+package main
+
+// This file configures different log formatters via logrus. The
+// standard formatter uses a structured JSON format that is compatible
+// with Stackdriver Error Reporting.
+//
+// https://cloud.google.com/error-reporting/docs/formatting-error-messages
+
+import (
+	"bytes"
+	"encoding/json"
+	log "github.com/sirupsen/logrus"
+)
+
+type stackdriverFormatter struct{}
+
+type serviceContext struct {
+	Service string `json:"service"`
+	Version string `json:"version"`
+}
+
+type reportLocation struct {
+	FilePath     string `json:"filePath"`
+	LineNumber   int    `json:"lineNumber"`
+	FunctionName string `json:"functionName"`
+}
+
+var nixeryContext = serviceContext{
+	Service: "nixery",
+	Version: "TODO(tazjin)", // angry?
+}
+
+// isError determines whether an entry should be logged as an error
+// (i.e. with attached `context`).
+//
+// This requires the caller information to be present on the log
+// entry, as stacktraces are not available currently.
+func isError(e *log.Entry) bool {
+	l := e.Level
+	return (l == log.ErrorLevel || l == log.FatalLevel || l == log.PanicLevel) &&
+		e.HasCaller()
+}
+
+func (f stackdriverFormatter) Format(e *log.Entry) ([]byte, error) {
+	msg := e.Data
+	msg["serviceContext"] = &nixeryContext
+	msg["message"] = &e.Message
+	msg["eventTime"] = &e.Time
+
+	if isError(e) {
+		loc := reportLocation{
+			FilePath: e.Caller.File,
+			LineNumber: e.Caller.Line,
+			FunctionName: e.Caller.Function,
+		}
+		msg["context"] = &loc
+	}
+
+	b := new(bytes.Buffer)
+	err := json.NewEncoder(b).Encode(&msg)
+
+	return b.Bytes(), err
+}
+
+func init() {
+	log.SetReportCaller(true)
+	log.SetFormatter(stackdriverFormatter{})
+}