mirror of
https://github.com/hanzoai/traces.git
synced 2026-10-30 18:20:41 +00:00
160 lines
4.2 KiB
Go
160 lines
4.2 KiB
Go
// Provenance-includes-location: https://github.com/grafana/loki/blob/7c78d7ea44afb420847255f9f5a4f677ad0f47bf/pkg/util/log/line_buffer.go
|
|
// Provenance-includes-location: https://github.com/grafana/mimir/blob/c8b24a462f7e224950409e7e0a4e0a58f3a79599/pkg/util/log/line_buffer.go
|
|
// Provenance-includes-copyright: Grafana Labs
|
|
package log
|
|
|
|
import (
|
|
"bytes"
|
|
"io"
|
|
"sync"
|
|
"time"
|
|
|
|
"go.uber.org/atomic"
|
|
)
|
|
|
|
// BufferedLogger buffers log lines to be flushed periodically. Without a line buffer, Log() will call the write
|
|
// syscall for every log line which is expensive if logging thousands of lines per second.
|
|
type BufferedLogger struct {
|
|
buf *threadsafeBuffer
|
|
entries atomic.Uint32
|
|
cap uint32
|
|
w io.Writer
|
|
|
|
onFlush func(entries uint32)
|
|
}
|
|
|
|
// Size returns the number of entries in the buffer.
|
|
func (l *BufferedLogger) Size() uint32 {
|
|
return l.entries.Load()
|
|
}
|
|
|
|
// Write writes the given bytes to the line buffer, and increments the entries counter.
|
|
// If the buffer is full (entries == cap), it will be flushed, and the entries counter reset.
|
|
func (l *BufferedLogger) Write(p []byte) (n int, err error) {
|
|
// when we've filled the buffer, flush it
|
|
if l.Size() >= l.cap {
|
|
// Flush resets the size to 0
|
|
if err := l.Flush(); err != nil {
|
|
l.buf.Reset()
|
|
return 0, err
|
|
}
|
|
}
|
|
|
|
l.entries.Inc()
|
|
|
|
return l.buf.Write(p)
|
|
}
|
|
|
|
// Flush forces the buffer to be written to the underlying writer.
|
|
func (l *BufferedLogger) Flush() error {
|
|
// reset the counter
|
|
sz := l.entries.Swap(0)
|
|
if sz <= 0 {
|
|
return nil
|
|
}
|
|
|
|
// WriteTo() calls Reset() on the underlying buffer, so it's not needed here
|
|
_, err := l.buf.WriteTo(l.w)
|
|
|
|
// only call OnFlush callback if write was successful
|
|
if err == nil && l.onFlush != nil {
|
|
l.onFlush(sz)
|
|
}
|
|
|
|
return err
|
|
}
|
|
|
|
type BufferedLoggerOption func(*BufferedLogger)
|
|
|
|
// WithFlushPeriod creates a new BufferedLoggerOption that sets the flush period for the BufferedLogger.
|
|
func WithFlushPeriod(d time.Duration) BufferedLoggerOption {
|
|
return func(l *BufferedLogger) {
|
|
go func() {
|
|
tick := time.NewTicker(d)
|
|
defer tick.Stop()
|
|
|
|
for range tick.C {
|
|
l.Flush()
|
|
}
|
|
}()
|
|
}
|
|
}
|
|
|
|
// WithFlushCallback allows for a callback function to be executed when Flush() is called.
|
|
// The length of the buffer at the time of the Flush() will be passed to the function.
|
|
func WithFlushCallback(fn func(entries uint32)) BufferedLoggerOption {
|
|
return func(l *BufferedLogger) {
|
|
l.onFlush = fn
|
|
}
|
|
}
|
|
|
|
// WithPrellocatedBuffer preallocates a buffer to reduce GC cycles and slice resizing.
|
|
func WithPrellocatedBuffer(size uint32) BufferedLoggerOption {
|
|
return func(l *BufferedLogger) {
|
|
l.buf = newThreadsafeBuffer(bytes.NewBuffer(make([]byte, 0, size)))
|
|
}
|
|
}
|
|
|
|
// NewBufferedLogger creates a new BufferedLogger with a configured capacity.
|
|
// Lines are flushed when the context is done, the buffer is full, or the flush period is reached.
|
|
func NewBufferedLogger(w io.Writer, cap uint32, opts ...BufferedLoggerOption) *BufferedLogger {
|
|
l := &BufferedLogger{
|
|
w: w,
|
|
buf: newThreadsafeBuffer(bytes.NewBuffer([]byte{})),
|
|
cap: cap,
|
|
}
|
|
|
|
for _, opt := range opts {
|
|
opt(l)
|
|
}
|
|
|
|
return l
|
|
}
|
|
|
|
// threadsafeBuffer wraps the non-threadsafe bytes.Buffer.
|
|
type threadsafeBuffer struct {
|
|
mx sync.Mutex
|
|
buf *bytes.Buffer
|
|
}
|
|
|
|
// Read reads up to len(p) bytes into p. It returns the number of bytes read (0 <= n <= len(p)) and any error encountered.
|
|
func (t *threadsafeBuffer) Read(p []byte) (n int, err error) {
|
|
t.mx.Lock()
|
|
defer t.mx.Unlock()
|
|
|
|
return t.buf.Read(p)
|
|
}
|
|
|
|
// Write writes the given bytes to the underlying writer.
|
|
func (t *threadsafeBuffer) Write(p []byte) (n int, err error) {
|
|
t.mx.Lock()
|
|
defer t.mx.Unlock()
|
|
|
|
return t.buf.Write(p)
|
|
}
|
|
|
|
// WriteTo writes the buffered lines to the given writer.
|
|
func (t *threadsafeBuffer) WriteTo(w io.Writer) (n int64, err error) {
|
|
t.mx.Lock()
|
|
defer t.mx.Unlock()
|
|
|
|
return t.buf.WriteTo(w)
|
|
}
|
|
|
|
// Reset resets the buffer to be empty,
|
|
// but it retains the underlying storage for use by future writes.
|
|
// Reset is the same as Truncate(0).
|
|
func (t *threadsafeBuffer) Reset() {
|
|
t.mx.Lock()
|
|
defer t.mx.Unlock()
|
|
|
|
t.buf.Reset()
|
|
}
|
|
|
|
// newThreadsafeBuffer returns a new threadsafeBuffer wrapping the given bytes.Buffer.
|
|
func newThreadsafeBuffer(buf *bytes.Buffer) *threadsafeBuffer {
|
|
return &threadsafeBuffer{
|
|
buf: buf,
|
|
}
|
|
}
|