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
ruby
class ThumbnailPool def initialize(worker_count: 4, capacity: 100) @queue = SizedQueue.new(capacity) @running = true
A thread pool for thumbnail jobs in Ruby
concurrency
thread-pool
bounded-queue
Advanced
7 steps
go
package handler import ( "net/http"
Custom time validation in Gin
validation
request-binding
struct-tags
Intermediate
8 steps
java
public class RequestCoalescer<K, V> { private final ConcurrentHashMap<K, CompletableFuture<V>> inFlight = new ConcurrentHashMap<>(); private final Function<K, V> loader;
Coalescing duplicate requests in Java
concurrency
caching
completablefuture
Advanced
6 steps
go
package middleware import ( "net/http"
Localized validation errors in Gin
middleware
internationalization
validation
Intermediate
8 steps
javascript
async function uploadInBatches(records, uploadFn, { batchSize = 100, concurrency = 3 } = {}) { const batches = []; for (let i = 0; i < records.length; i += batchSize) { batches.push(records.slice(i, i + batchSize));
Uploading records with bounded concurrency
concurrency
worker-pool
async-await
Advanced
8 steps
go
func ListProducts(c *gin.Context) { allowed := map[string]string{ "category": "category", "brand": "brand",
Safe query filtering in a Gin handler
input-validation
whitelisting
query-building
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.