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
‹ swipe to step through ›
Walkthrough
Space play
←→ step
click any line
Three takeaways
- 1Buffering writes and flushing on a timer trades a little latency for far fewer syscalls.
- 2A mutex shared between the write path and the flush path keeps concurrent access to the buffer safe.
- 3Pairing a context cancel with a done channel lets Close block until the flusher has fully drained and exited.
Related explainers
go
package streaming import ( "bufio"
Streaming NDJSON logs over HTTP in Go
http-streaming
channels
select
Advanced
10 steps
go
package api import ( "crypto/sha256"
ETag conditional requests in Gin
http-caching
etag
conditional-requests
Intermediate
6 steps
go
func (w *Watcher) resetDebounce(d time.Duration) { if !w.timer.Stop() { select { case <-w.timer.C:
Debouncing a stream of events in Go
debounce
timers
channels
Advanced
7 steps
go
package logging import ( "context"
Deduplicating log attributes in Go's slog
decorator-pattern
structured-logging
immutability
Intermediate
8 steps
rust
use std::collections::VecDeque; use std::sync::{Arc, Condvar, Mutex}; use std::time::{Duration, Instant};
Building a counting semaphore in Rust
concurrency
synchronization
condition-variable
Advanced
9 steps
go
package middleware import ( "net/http"
Per-plan export limits in Gin middleware
middleware
rate-limiting
authorization
Intermediate
7 steps
Share this explainer
Here's the card — post it anywhere.
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code
Embed this explainer
Drop the interactive walkthrough into a blog or docs. Views never cost a credit.
<iframe src="https://highlit.co/explainers/a-buffered-logger-with-background-flushing-in-go-explained-go-08fe/embed?autoplay=1" width="100%" height="520" loading="lazy" style="border:0"></iframe>
Autoplay is on by default — add ?autoplay=0 to start paused.