diff --git a/cmd/payment-api/main.go b/cmd/payment-api/main.go index 4739089..bb23040 100644 --- a/cmd/payment-api/main.go +++ b/cmd/payment-api/main.go @@ -38,18 +38,19 @@ func main() { migratecmd.MustRegister(app, app.RootCmd, migratecmd.Config{Automigrate: false}) zeroLogger := zerolog.New(zerolog.ConsoleWriter{Out: os.Stderr, TimeFormat: "15:04:05"}).With().Timestamp().Logger() + gmessagesLogger := gmessages.ProductionLogger(zeroLogger) stdLogger := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelInfo})) webhookService := webhooks.NewService(app, cfg) webhookService.Logger = stdLogger paymentService := payments.NewService(app, cfg, webhookService) smsService := sms.NewService(app, paymentService) - gmessagesManager := gmessages.NewManager(cfg, zeroLogger, func(input sms.Input) error { + gmessagesManager := gmessages.NewManager(cfg, gmessagesLogger, func(input sms.Input) error { _, err := smsService.Ingest(input) return err }) api.New(cfg, paymentService, smsService, gmessagesManager).Register(app) - registerPairCommand(app, cfg, zeroLogger) + registerPairCommand(app, cfg, gmessagesLogger) registerHealthcheckCommand(app) rootCtx, rootCancel := context.WithCancel(context.Background()) diff --git a/internal/gmessages/logging.go b/internal/gmessages/logging.go new file mode 100644 index 0000000..6ccb54d --- /dev/null +++ b/internal/gmessages/logging.go @@ -0,0 +1,11 @@ +package gmessages + +import "github.com/rs/zerolog" + +// ProductionLogger suppresses Trace/Debug output before a logger is handed to +// libgm. Upstream uses those levels for encoded HTTP/protobuf response bodies, +// which can contain private Google Messages data and must not be retained in +// production logs. Info and above keep lifecycle and actionable failures visible. +func ProductionLogger(logger zerolog.Logger) zerolog.Logger { + return logger.Level(zerolog.InfoLevel) +} diff --git a/internal/gmessages/logging_test.go b/internal/gmessages/logging_test.go new file mode 100644 index 0000000..1e67016 --- /dev/null +++ b/internal/gmessages/logging_test.go @@ -0,0 +1,32 @@ +package gmessages + +import ( + "bytes" + "strings" + "testing" + + "github.com/rs/zerolog" +) + +func TestProductionLoggerSuppressesTraceAndDebug(t *testing.T) { + var output bytes.Buffer + base := zerolog.New(&output).Level(zerolog.TraceLevel) + logger := ProductionLogger(base).With().Str("component", "libgm").Logger() + + logger.Trace().Str("response_body", "sensitive-trace").Msg("trace message") + logger.Debug().Str("response_body", "sensitive-debug").Msg("debug message") + logger.Info().Msg("connection lifecycle") + logger.Warn().Msg("actionable warning") + + got := output.String() + for _, forbidden := range []string{"sensitive-trace", "sensitive-debug", "trace message", "debug message"} { + if strings.Contains(got, forbidden) { + t.Fatalf("production logger emitted suppressed content %q: %s", forbidden, got) + } + } + for _, wanted := range []string{"connection lifecycle", "actionable warning", `"component":"libgm"`} { + if !strings.Contains(got, wanted) { + t.Fatalf("production logger missing %q: %s", wanted, got) + } + } +}