logs_windows.go 2.8 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586
  1. package main
  2. import (
  3. "context"
  4. "log/slog"
  5. "strings"
  6. "sync"
  7. "github.com/slackhq/nebula/logging"
  8. )
  9. // newPlatformLogger returns a *slog.Logger that routes every log record
  10. // through the Windows service logger so records end up in the Windows
  11. // Event Log. All the heavy lifting (level management, format swap,
  12. // timestamp toggle, WithAttrs/WithGroup) comes from logging.NewHandler;
  13. // this file only contributes:
  14. //
  15. // - an io.Writer that forwards each formatted line to the service
  16. // logger at the current record's Event Log severity, and
  17. // - a thin severityTag that embeds *logging.Handler and overrides
  18. // only Handle / WithAttrs / WithGroup, so Event Viewer's severity
  19. // column and severity-based filters keep working the way they did
  20. // before the slog migration.
  21. //
  22. // Format (text vs json) is carried by the embedded *logging.Handler, so
  23. // logging.format: json in config still produces JSON lines in Event
  24. // Viewer, same as the pre-slog logrus setup.
  25. func newPlatformLogger() *slog.Logger {
  26. w := &eventLogWriter{}
  27. return slog.New(&severityTag{Handler: logging.NewHandler(w), w: w})
  28. }
  29. // eventLogWriter forwards slog-formatted lines to the Windows service
  30. // logger at the severity most recently stashed by severityTag.Handle.
  31. // The mutex serializes the stash + inner.Handle + Write cycle per record
  32. // across all concurrent goroutines; slog's builtin text/json handlers
  33. // each hold their own mutex around Write, but that only protects the
  34. // Write call itself, not our stash-then-handle sequence.
  35. type eventLogWriter struct {
  36. mu sync.Mutex
  37. level slog.Level
  38. }
  39. func (w *eventLogWriter) Write(p []byte) (int, error) {
  40. line := strings.TrimRight(string(p), "\n")
  41. switch {
  42. case w.level >= slog.LevelError:
  43. return len(p), logger.Error(line)
  44. case w.level >= slog.LevelWarn:
  45. return len(p), logger.Warning(line)
  46. default:
  47. return len(p), logger.Info(line)
  48. }
  49. }
  50. // severityTag embeds *logging.Handler to pick up everything it does for
  51. // free (Enabled, SetLevel, GetLevel, SetFormat, GetFormat,
  52. // SetDisableTimestamp) and overrides only Handle / WithAttrs / WithGroup
  53. // so each record's slog.Level is stashed on the writer before formatting
  54. // and so derived handlers stay wrapped as severityTag rather than
  55. // downgrading to bare *logging.Handler.
  56. type severityTag struct {
  57. *logging.Handler
  58. w *eventLogWriter
  59. }
  60. func (s *severityTag) Handle(ctx context.Context, r slog.Record) error {
  61. s.w.mu.Lock()
  62. defer s.w.mu.Unlock()
  63. s.w.level = r.Level
  64. return s.Handler.Handle(ctx, r)
  65. }
  66. func (s *severityTag) WithAttrs(attrs []slog.Attr) slog.Handler {
  67. if len(attrs) == 0 {
  68. return s
  69. }
  70. return &severityTag{Handler: s.Handler.WithAttrs(attrs).(*logging.Handler), w: s.w}
  71. }
  72. func (s *severityTag) WithGroup(name string) slog.Handler {
  73. if name == "" {
  74. return s
  75. }
  76. return &severityTag{Handler: s.Handler.WithGroup(name).(*logging.Handler), w: s.w}
  77. }