logging.go 3.8 KB

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