apilogger.go 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143
  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. val := attr.Value.Any()
  62. if err, ok := val.(error); ok {
  63. extra[attr.Key] = err.Error()
  64. } else {
  65. extra[attr.Key] = val
  66. }
  67. }
  68. h.mu.Unlock()
  69. r.Attrs(func(attr slog.Attr) bool {
  70. val := attr.Value.Any()
  71. if err, ok := val.(error); ok {
  72. extra[attr.Key] = err.Error()
  73. } else {
  74. extra[attr.Key] = val
  75. }
  76. return true
  77. })
  78. params := opencode.AppLogParams{
  79. Service: opencode.F(h.service),
  80. Level: opencode.F(apiLevel),
  81. Message: opencode.F(r.Message),
  82. }
  83. if len(extra) > 0 {
  84. params.Extra = opencode.F(extra)
  85. }
  86. h.queue <- params
  87. return nil
  88. }
  89. // WithAttrs returns a new Handler whose attributes consist of
  90. // both the receiver's attributes and the arguments.
  91. func (h *APILogHandler) WithAttrs(attrs []slog.Attr) slog.Handler {
  92. h.mu.Lock()
  93. defer h.mu.Unlock()
  94. newHandler := &APILogHandler{
  95. client: h.client,
  96. service: h.service,
  97. level: h.level,
  98. attrs: make([]slog.Attr, len(h.attrs)+len(attrs)),
  99. groups: make([]string, len(h.groups)),
  100. }
  101. copy(newHandler.attrs, h.attrs)
  102. copy(newHandler.attrs[len(h.attrs):], attrs)
  103. copy(newHandler.groups, h.groups)
  104. return newHandler
  105. }
  106. // WithGroup returns a new Handler with the given group appended to
  107. // the receiver's existing groups.
  108. func (h *APILogHandler) WithGroup(name string) slog.Handler {
  109. h.mu.Lock()
  110. defer h.mu.Unlock()
  111. newHandler := &APILogHandler{
  112. client: h.client,
  113. service: h.service,
  114. level: h.level,
  115. attrs: make([]slog.Attr, len(h.attrs)),
  116. groups: make([]string, len(h.groups)+1),
  117. }
  118. copy(newHandler.attrs, h.attrs)
  119. copy(newHandler.groups, h.groups)
  120. newHandler.groups[len(h.groups)] = name
  121. return newHandler
  122. }