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