buffer.go raw
1 package logbuffer
2
3 import (
4 "sync"
5 "time"
6 )
7
8 // LogEntry represents a single log entry
9 type LogEntry struct {
10 ID int64 `json:"id"`
11 Timestamp time.Time `json:"timestamp"`
12 Level string `json:"level"`
13 Message string `json:"message"`
14 File string `json:"file,omitempty"`
15 Line int `json:"line,omitempty"`
16 }
17
18 // Buffer is a thread-safe ring buffer for log entries
19 type Buffer struct {
20 entries []LogEntry
21 size int
22 head int // next write position
23 count int // number of entries
24 nextID int64 // monotonic ID counter
25 mu sync.RWMutex
26 }
27
28 // NewBuffer creates a new ring buffer with the specified size
29 func NewBuffer(size int) *Buffer {
30 if size <= 0 {
31 size = 10000
32 }
33 return &Buffer{
34 entries: make([]LogEntry, size),
35 size: size,
36 }
37 }
38
39 // Add adds a log entry to the buffer
40 func (b *Buffer) Add(entry LogEntry) {
41 b.mu.Lock()
42 defer b.mu.Unlock()
43
44 b.nextID++
45 entry.ID = b.nextID
46
47 b.entries[b.head] = entry
48 b.head = (b.head + 1) % b.size
49
50 if b.count < b.size {
51 b.count++
52 }
53 }
54
55 // Get returns log entries, newest first
56 // offset is the number of entries to skip from the newest
57 // limit is the maximum number of entries to return
58 func (b *Buffer) Get(offset, limit int) []LogEntry {
59 b.mu.RLock()
60 defer b.mu.RUnlock()
61
62 if b.count == 0 || offset >= b.count {
63 return []LogEntry{}
64 }
65
66 if limit <= 0 {
67 limit = 100
68 }
69
70 available := b.count - offset
71 if limit > available {
72 limit = available
73 }
74
75 result := make([]LogEntry, limit)
76
77 // Start from the newest entry (head - 1) and go backwards
78 for i := 0; i < limit; i++ {
79 // Calculate index: newest is at (head - 1), skip offset entries
80 idx := (b.head - 1 - offset - i + b.size*2) % b.size
81 result[i] = b.entries[idx]
82 }
83
84 return result
85 }
86
87 // Clear removes all entries from the buffer
88 func (b *Buffer) Clear() {
89 b.mu.Lock()
90 defer b.mu.Unlock()
91
92 b.head = 0
93 b.count = 0
94 // Note: we don't reset nextID to maintain monotonic IDs
95 }
96
97 // Count returns the number of entries in the buffer
98 func (b *Buffer) Count() int {
99 b.mu.RLock()
100 defer b.mu.RUnlock()
101 return b.count
102 }
103
104 // Global buffer instance
105 var GlobalBuffer *Buffer
106
107 // Init initializes the global log buffer
108 func Init(size int) {
109 GlobalBuffer = NewBuffer(size)
110 }
111