go 64 lines · 8 steps

A buffered logger with background flushing in Go

A logger batches writes into a buffer and a background goroutine flushes it on a timer, with clean shutdown via context.

Explained by highlit
1package logbuffer
2 
3import (
4 "bufio"
5 "context"
6 "fmt"
7 "io"
8 "sync"
9 "time"
10)
11 
12type Logger struct {
13 mu sync.Mutex
14 buf *bufio.Writer
15 cancel context.CancelFunc
16 done chan struct{}
17}
18 
19func New(w io.Writer, flushEvery time.Duration) *Logger {
20 ctx, cancel := context.WithCancel(context.Background())
21 l := &Logger{
22 buf: bufio.NewWriterSize(w, 32*1024),
23 cancel: cancel,
24 done: make(chan struct{}),
25 }
26 go l.flushLoop(ctx, flushEvery)
27 return l
28}
29 
30func (l *Logger) Write(level, msg string) error {
31 l.mu.Lock()
32 defer l.mu.Unlock()
33 line := fmt.Sprintf("%s [%s] %s\n", time.Now().UTC().Format(time.RFC3339), level, msg)
34 _, err := l.buf.WriteString(line)
35 return err
36}
37 
38func (l *Logger) flushLoop(ctx context.Context, every time.Duration) {
39 defer close(l.done)
40 ticker := time.NewTicker(every)
41 defer ticker.Stop()
42 for {
43 select {
44 case <-ticker.C:
45 l.Flush()
46 case <-ctx.Done():
47 l.Flush()
48 return
49 }
50 }
51}
52 
53func (l *Logger) Flush() {
54 l.mu.Lock()
55 defer l.mu.Unlock()
56 if err := l.buf.Flush(); err != nil {
57 fmt.Printf("logbuffer: flush failed: %v\n", err)
58 }
59}
60 
61func (l *Logger) Close() {
62 l.cancel()
63 <-l.done
64}
01 / 01
STEP 01

Walkthrough

Space play step click any line
Three takeaways
  1. 1Buffering writes and flushing on a timer trades a little latency for far fewer syscalls.
  2. 2A mutex shared between the write path and the flush path keeps concurrent access to the buffer safe.
  3. 3Pairing a context cancel with a done channel lets Close block until the flusher has fully drained and exited.

Related explainers

Share this explainer

Here's the card — post it anywhere.

A buffered logger with background flushing in Go — share card
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code