logger.go 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137
  1. package middleware
  2. import (
  3. "context"
  4. "fmt"
  5. "log/slog"
  6. "os"
  7. "strings"
  8. "github.com/mk6i/open-oscar-server/config"
  9. "github.com/mk6i/open-oscar-server/wire"
  10. )
  11. const (
  12. LevelTrace = slog.Level(-8)
  13. )
  14. var levelNames = map[slog.Leveler]string{
  15. LevelTrace: "TRACE",
  16. }
  17. func NewLogger(cfg config.Config) *slog.Logger {
  18. var level slog.Level
  19. switch strings.ToLower(cfg.LogLevel) {
  20. case "trace":
  21. level = LevelTrace
  22. case "debug":
  23. level = slog.LevelDebug
  24. case "warn":
  25. level = slog.LevelWarn
  26. case "error":
  27. level = slog.LevelError
  28. case "info":
  29. fallthrough
  30. default:
  31. level = slog.LevelInfo
  32. }
  33. opts := &slog.HandlerOptions{
  34. Level: level,
  35. ReplaceAttr: func(groups []string, a slog.Attr) slog.Attr {
  36. if a.Key == slog.LevelKey {
  37. level := a.Value.Any().(slog.Level)
  38. levelLabel, exists := levelNames[level]
  39. if !exists {
  40. levelLabel = level.String()
  41. }
  42. a.Value = slog.StringValue(levelLabel)
  43. }
  44. return a
  45. },
  46. }
  47. return slog.New(handler{slog.NewTextHandler(os.Stdout, opts)})
  48. }
  49. type handler struct {
  50. slog.Handler
  51. }
  52. func (h handler) Handle(ctx context.Context, r slog.Record) error {
  53. if sn := ctx.Value("screenName"); sn != nil {
  54. r.AddAttrs(slog.Attr{Key: "screenName", Value: slog.StringValue(sn.(fmt.Stringer).String())})
  55. }
  56. if ip := ctx.Value("ip"); ip != nil {
  57. r.AddAttrs(slog.Attr{Key: "ip", Value: slog.StringValue(ip.(string))})
  58. }
  59. return h.Handler.Handle(ctx, r)
  60. }
  61. func (h handler) WithAttrs(attrs []slog.Attr) slog.Handler {
  62. return handler{h.Handler.WithAttrs(attrs)}
  63. }
  64. func (h handler) WithGroup(name string) slog.Handler {
  65. return h.Handler.WithGroup(name)
  66. }
  67. type RouteLogger struct {
  68. Logger *slog.Logger
  69. }
  70. func (rt RouteLogger) LogRequestAndResponse(ctx context.Context, inFrame wire.SNACFrame, inSNAC any, outFrame wire.SNACFrame, outSNAC any) {
  71. msg := "client request -> server response"
  72. switch {
  73. case rt.Logger.Enabled(ctx, LevelTrace):
  74. rt.Logger.LogAttrs(ctx, LevelTrace, msg, snacLogGroupWithPayload("request", inFrame, inSNAC),
  75. snacLogGroupWithPayload("response", outFrame, outSNAC))
  76. case rt.Logger.Enabled(ctx, slog.LevelDebug):
  77. rt.Logger.LogAttrs(ctx, slog.LevelDebug, msg, snacLogGroup("request", inFrame),
  78. snacLogGroup("response", outFrame))
  79. }
  80. }
  81. func (rt RouteLogger) LogRequestError(ctx context.Context, inFrame wire.SNACFrame, err error) {
  82. LogRequestError(ctx, rt.Logger, inFrame, err)
  83. }
  84. func LogRequestError(ctx context.Context, logger *slog.Logger, inFrame wire.SNACFrame, err error) {
  85. logger.LogAttrs(ctx, slog.LevelError, "client request error",
  86. slog.Group("request",
  87. slog.String("food_group", wire.FoodGroupName(inFrame.FoodGroup)),
  88. slog.String("sub_group", wire.SubGroupName(inFrame.FoodGroup, inFrame.SubGroup)),
  89. ),
  90. slog.String("err", err.Error()),
  91. )
  92. }
  93. func (rt RouteLogger) LogRequest(ctx context.Context, inFrame wire.SNACFrame, inSNAC any) {
  94. LogRequest(ctx, rt.Logger, inFrame, inSNAC)
  95. }
  96. func LogRequest(ctx context.Context, logger *slog.Logger, inFrame wire.SNACFrame, inSNAC any) {
  97. const msg = "client request"
  98. switch {
  99. case logger.Enabled(ctx, LevelTrace):
  100. logger.LogAttrs(ctx, LevelTrace, msg, snacLogGroupWithPayload("request", inFrame, inSNAC))
  101. case logger.Enabled(ctx, slog.LevelDebug):
  102. logger.LogAttrs(ctx, slog.LevelDebug, msg, snacLogGroup("request", inFrame))
  103. }
  104. }
  105. func snacLogGroup(key string, outFrame wire.SNACFrame) slog.Attr {
  106. return slog.Group(key,
  107. slog.String("food_group", wire.FoodGroupName(outFrame.FoodGroup)),
  108. slog.String("sub_group", wire.SubGroupName(outFrame.FoodGroup, outFrame.SubGroup)),
  109. )
  110. }
  111. func snacLogGroupWithPayload(key string, outFrame wire.SNACFrame, outSNAC any) slog.Attr {
  112. return slog.Group(key,
  113. slog.String("food_group", wire.FoodGroupName(outFrame.FoodGroup)),
  114. slog.String("sub_group", wire.SubGroupName(outFrame.FoodGroup, outFrame.SubGroup)),
  115. slog.Any("snac_frame", outFrame),
  116. slog.Any("snac_payload", outSNAC),
  117. )
  118. }