選択できるのは25トピックまでです。 トピックは、先頭が英数字で、英数字とダッシュ('-')を使用した35文字以内のものにしてください。

267 行
5.6KB

  1. package logging
  2. import (
  3. "context"
  4. "fmt"
  5. "io"
  6. "log/slog"
  7. "os"
  8. "strings"
  9. "sync"
  10. "time"
  11. )
  12. type Entry struct {
  13. Timestamp time.Time `json:"timestamp"`
  14. Level string `json:"level"`
  15. Message string `json:"message"`
  16. Fields map[string]any `json:"fields,omitempty"`
  17. }
  18. type RecentStore struct {
  19. mu sync.RWMutex
  20. entries []Entry
  21. next int
  22. size int
  23. capacity int
  24. }
  25. func NewRecentStore(capacity int) *RecentStore {
  26. if capacity <= 0 {
  27. capacity = 200
  28. }
  29. return &RecentStore{
  30. entries: make([]Entry, capacity),
  31. capacity: capacity,
  32. }
  33. }
  34. func (s *RecentStore) Add(entry Entry) {
  35. if s == nil {
  36. return
  37. }
  38. s.mu.Lock()
  39. defer s.mu.Unlock()
  40. s.entries[s.next] = entry
  41. s.next = (s.next + 1) % s.capacity
  42. if s.size < s.capacity {
  43. s.size++
  44. }
  45. }
  46. func (s *RecentStore) List(limit int) []Entry {
  47. if s == nil {
  48. return nil
  49. }
  50. s.mu.RLock()
  51. defer s.mu.RUnlock()
  52. if s.size == 0 {
  53. return nil
  54. }
  55. if limit <= 0 || limit > s.size {
  56. limit = s.size
  57. }
  58. out := make([]Entry, 0, limit)
  59. for i := 0; i < limit; i++ {
  60. idx := (s.next - 1 - i + s.capacity) % s.capacity
  61. out = append(out, s.entries[idx])
  62. }
  63. return out
  64. }
  65. type recentHandler struct {
  66. store *RecentStore
  67. level slog.Leveler
  68. attrs []slog.Attr
  69. group []string
  70. }
  71. func NewRecentHandler(store *RecentStore, level slog.Leveler) slog.Handler {
  72. if level == nil {
  73. level = slog.LevelInfo
  74. }
  75. return &recentHandler{store: store, level: level}
  76. }
  77. func (h *recentHandler) Enabled(_ context.Context, level slog.Level) bool {
  78. if h == nil || h.level == nil {
  79. return level >= slog.LevelInfo
  80. }
  81. return level >= h.level.Level()
  82. }
  83. func (h *recentHandler) Handle(_ context.Context, r slog.Record) error {
  84. if h == nil || h.store == nil {
  85. return nil
  86. }
  87. fields := map[string]any{}
  88. for _, attr := range h.attrs {
  89. addField(fields, h.group, attr)
  90. }
  91. r.Attrs(func(attr slog.Attr) bool {
  92. addField(fields, h.group, attr)
  93. return true
  94. })
  95. if len(fields) == 0 {
  96. fields = nil
  97. }
  98. h.store.Add(Entry{
  99. Timestamp: r.Time.UTC(),
  100. Level: r.Level.String(),
  101. Message: strings.TrimSpace(r.Message),
  102. Fields: fields,
  103. })
  104. return nil
  105. }
  106. func (h *recentHandler) WithAttrs(attrs []slog.Attr) slog.Handler {
  107. next := &recentHandler{
  108. store: h.store,
  109. level: h.level,
  110. group: append([]string(nil), h.group...),
  111. }
  112. next.attrs = append(append([]slog.Attr(nil), h.attrs...), attrs...)
  113. return next
  114. }
  115. func (h *recentHandler) WithGroup(name string) slog.Handler {
  116. next := &recentHandler{
  117. store: h.store,
  118. level: h.level,
  119. attrs: append([]slog.Attr(nil), h.attrs...),
  120. group: append([]string(nil), h.group...),
  121. }
  122. if trimmed := strings.TrimSpace(name); trimmed != "" {
  123. next.group = append(next.group, trimmed)
  124. }
  125. return next
  126. }
  127. func addField(fields map[string]any, groups []string, attr slog.Attr) {
  128. if attr.Equal(slog.Attr{}) {
  129. return
  130. }
  131. key := strings.TrimSpace(attr.Key)
  132. if key == "" {
  133. return
  134. }
  135. prefix := strings.Join(groups, ".")
  136. if prefix != "" {
  137. key = prefix + "." + key
  138. }
  139. val := attr.Value.Resolve()
  140. if val.Kind() == slog.KindGroup {
  141. for _, nested := range val.Group() {
  142. addField(fields, append(groups, strings.TrimSpace(attr.Key)), nested)
  143. }
  144. return
  145. }
  146. fields[key] = slogValueToAny(val)
  147. }
  148. func slogValueToAny(v slog.Value) any {
  149. switch v.Kind() {
  150. case slog.KindString:
  151. return v.String()
  152. case slog.KindBool:
  153. return v.Bool()
  154. case slog.KindInt64:
  155. return v.Int64()
  156. case slog.KindUint64:
  157. return v.Uint64()
  158. case slog.KindFloat64:
  159. return v.Float64()
  160. case slog.KindDuration:
  161. return v.Duration().Milliseconds()
  162. case slog.KindTime:
  163. return v.Time().UTC().Format(time.RFC3339Nano)
  164. case slog.KindAny:
  165. return v.Any()
  166. default:
  167. return fmt.Sprint(v.Any())
  168. }
  169. }
  170. type teeHandler struct {
  171. handlers []slog.Handler
  172. }
  173. func newTeeHandler(handlers ...slog.Handler) slog.Handler {
  174. nonNil := make([]slog.Handler, 0, len(handlers))
  175. for _, h := range handlers {
  176. if h != nil {
  177. nonNil = append(nonNil, h)
  178. }
  179. }
  180. return &teeHandler{handlers: nonNil}
  181. }
  182. func (h *teeHandler) Enabled(ctx context.Context, level slog.Level) bool {
  183. for _, handler := range h.handlers {
  184. if handler.Enabled(ctx, level) {
  185. return true
  186. }
  187. }
  188. return false
  189. }
  190. func (h *teeHandler) Handle(ctx context.Context, r slog.Record) error {
  191. for _, handler := range h.handlers {
  192. if !handler.Enabled(ctx, r.Level) {
  193. continue
  194. }
  195. if err := handler.Handle(ctx, r.Clone()); err != nil {
  196. return err
  197. }
  198. }
  199. return nil
  200. }
  201. func (h *teeHandler) WithAttrs(attrs []slog.Attr) slog.Handler {
  202. next := make([]slog.Handler, 0, len(h.handlers))
  203. for _, handler := range h.handlers {
  204. next = append(next, handler.WithAttrs(attrs))
  205. }
  206. return &teeHandler{handlers: next}
  207. }
  208. func (h *teeHandler) WithGroup(name string) slog.Handler {
  209. next := make([]slog.Handler, 0, len(h.handlers))
  210. for _, handler := range h.handlers {
  211. next = append(next, handler.WithGroup(name))
  212. }
  213. return &teeHandler{handlers: next}
  214. }
  215. type SetupResult struct {
  216. Logger *slog.Logger
  217. Recent *RecentStore
  218. Close func() error
  219. }
  220. func Setup() SetupResult {
  221. recent := NewRecentStore(400)
  222. stdoutHandler := slog.NewJSONHandler(os.Stdout, nil)
  223. handlers := []slog.Handler{stdoutHandler, NewRecentHandler(recent, slog.LevelInfo)}
  224. closers := make([]io.Closer, 0, 1)
  225. if path := strings.TrimSpace(os.Getenv("LOG_FILE")); path != "" {
  226. if file, err := os.OpenFile(path, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0o644); err == nil {
  227. handlers = append(handlers, slog.NewJSONHandler(file, nil))
  228. closers = append(closers, file)
  229. }
  230. }
  231. logger := slog.New(newTeeHandler(handlers...))
  232. return SetupResult{
  233. Logger: logger,
  234. Recent: recent,
  235. Close: func() error {
  236. for _, closer := range closers {
  237. _ = closer.Close()
  238. }
  239. return nil
  240. },
  241. }
  242. }