apilogger.go 3.2 KB

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