apilogger.go 2.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133
  1. package util
  2. import (
  3. "context"
  4. "log/slog"
  5. "sync"
  6. opencode "github.com/sst/opencode-sdk-go"
  7. )
  8. type APILogHandler struct {
  9. client *opencode.Client
  10. service string
  11. level slog.Level
  12. attrs []slog.Attr
  13. groups []string
  14. mu sync.Mutex
  15. queue chan opencode.AppLogParams
  16. }
  17. func NewAPILogHandler(ctx context.Context, client *opencode.Client, service string, level slog.Level) *APILogHandler {
  18. result := &APILogHandler{
  19. client: client,
  20. service: service,
  21. level: level,
  22. attrs: make([]slog.Attr, 0),
  23. groups: make([]string, 0),
  24. queue: make(chan opencode.AppLogParams, 100_000),
  25. }
  26. go func() {
  27. for {
  28. select {
  29. case <-ctx.Done():
  30. return
  31. case params := <-result.queue:
  32. _, err := client.App.Log(context.Background(), params)
  33. if err != nil {
  34. slog.Error("Failed to log to API", "error", err)
  35. }
  36. }
  37. }
  38. }()
  39. return result
  40. }
  41. func (h *APILogHandler) Enabled(_ context.Context, level slog.Level) bool {
  42. return level >= h.level
  43. }
  44. func (h *APILogHandler) Handle(ctx context.Context, r slog.Record) error {
  45. var apiLevel opencode.AppLogParamsLevel
  46. switch r.Level {
  47. case slog.LevelDebug:
  48. apiLevel = opencode.AppLogParamsLevelDebug
  49. case slog.LevelInfo:
  50. apiLevel = opencode.AppLogParamsLevelInfo
  51. case slog.LevelWarn:
  52. apiLevel = opencode.AppLogParamsLevelWarn
  53. case slog.LevelError:
  54. apiLevel = opencode.AppLogParamsLevelError
  55. default:
  56. apiLevel = opencode.AppLogParamsLevelInfo
  57. }
  58. extra := make(map[string]any)
  59. h.mu.Lock()
  60. for _, attr := range h.attrs {
  61. extra[attr.Key] = attr.Value.Any()
  62. }
  63. h.mu.Unlock()
  64. r.Attrs(func(attr slog.Attr) bool {
  65. extra[attr.Key] = attr.Value.Any()
  66. return true
  67. })
  68. params := opencode.AppLogParams{
  69. Service: opencode.F(h.service),
  70. Level: opencode.F(apiLevel),
  71. Message: opencode.F(r.Message),
  72. }
  73. if len(extra) > 0 {
  74. params.Extra = opencode.F(extra)
  75. }
  76. h.queue <- params
  77. return nil
  78. }
  79. // WithAttrs returns a new Handler whose attributes consist of
  80. // both the receiver's attributes and the arguments.
  81. func (h *APILogHandler) WithAttrs(attrs []slog.Attr) slog.Handler {
  82. h.mu.Lock()
  83. defer h.mu.Unlock()
  84. newHandler := &APILogHandler{
  85. client: h.client,
  86. service: h.service,
  87. level: h.level,
  88. attrs: make([]slog.Attr, len(h.attrs)+len(attrs)),
  89. groups: make([]string, len(h.groups)),
  90. }
  91. copy(newHandler.attrs, h.attrs)
  92. copy(newHandler.attrs[len(h.attrs):], attrs)
  93. copy(newHandler.groups, h.groups)
  94. return newHandler
  95. }
  96. // WithGroup returns a new Handler with the given group appended to
  97. // the receiver's existing groups.
  98. func (h *APILogHandler) WithGroup(name string) slog.Handler {
  99. h.mu.Lock()
  100. defer h.mu.Unlock()
  101. newHandler := &APILogHandler{
  102. client: h.client,
  103. service: h.service,
  104. level: h.level,
  105. attrs: make([]slog.Attr, len(h.attrs)),
  106. groups: make([]string, len(h.groups)+1),
  107. }
  108. copy(newHandler.attrs, h.attrs)
  109. copy(newHandler.groups, h.groups)
  110. newHandler.groups[len(h.groups)] = name
  111. return newHandler
  112. }