// Package flog is a structured logger formatted for GCP Cloud Logging. // // The API mirrors log/slog: a message string followed by alternating key/value // pairs. Each kv pair becomes a JSON field in the emitted record, which Cloud // Logging promotes to a queryable jsonPayload field. // // flog.Info("particle processed", "particleID", id, "elapsed", dt) package flog import ( "os" "github.com/sirupsen/logrus" ) // Prepares global logger for GCP Cloud Logging's quirks var log = func() *logrus.Logger { logger := logrus.New() logger.SetFormatter(&logrus.JSONFormatter{ FieldMap: logrus.FieldMap{ logrus.FieldKeyLevel: "severity", logrus.FieldKeyMsg: "message", logrus.FieldKeyTime: "timestamp", }, }) logger.SetOutput(os.Stderr) logger.SetLevel(logrus.DebugLevel) return logger }() // fields converts alternating key/value args into a logrus.Fields map. A // trailing odd arg or non-string key is stored under "!BADKEY" to match // slog's behavior. func fields(args []any) logrus.Fields { if len(args) == 0 { return nil } f := make(logrus.Fields, len(args)/2+1) for i := 0; i < len(args); i += 2 { if i+1 >= len(args) { f["!BADKEY"] = args[i] break } key, ok := args[i].(string) if !ok { f["!BADKEY"] = args[i] continue } f[key] = args[i+1] } return f } func Debug(msg string, args ...any) { log.WithFields(fields(args)).Debug(msg) } func Info(msg string, args ...any) { log.WithFields(fields(args)).Info(msg) } func Warn(msg string, args ...any) { log.WithFields(fields(args)).Warn(msg) } func Error(msg string, args ...any) { log.WithFields(fields(args)).Error(msg) } func Fatal(msg string, args ...any) { log.WithFields(fields(args)).Fatal(msg) }