go 75 lines · 10 steps

How a tail -f follower works in Go

Streaming new lines from a growing file by seeking to the end and polling for fresh data, with context-aware cancellation.

Explained by highlit
1package logtail
2 
3import (
4 "bufio"
5 "context"
6 "errors"
7 "io"
8 "os"
9 "time"
10)
11 
12type Line struct {
13 Offset int64
14 Text string
15}
16 
17func Tail(ctx context.Context, path string, out chan<- Line) error {
18 f, err := os.Open(path)
19 if err != nil {
20 return err
21 }
22 defer f.Close()
23 
24 if _, err := f.Seek(0, io.SeekEnd); err != nil {
25 return err
26 }
27 
28 reader := bufio.NewReader(f)
29 ticker := time.NewTicker(200 * time.Millisecond)
30 defer ticker.Stop()
31 
32 var pending []byte
33 for {
34 line, err := reader.ReadBytes('\n')
35 switch {
36 case err == nil:
37 offset, _ := f.Seek(0, io.SeekCurrent)
38 select {
39 case out <- Line{Offset: offset, Text: string(pending) + string(line[:len(line)-1])}:
40 case <-ctx.Done():
41 return ctx.Err()
42 }
43 pending = pending[:0]
44 case errors.Is(err, io.EOF):
45 pending = append(pending, line...)
46 if err := waitForData(ctx, f, ticker.C); err != nil {
47 return err
48 }
49 default:
50 return err
51 }
52 }
53}
54 
55func waitForData(ctx context.Context, f *os.File, tick <-chan time.Time) error {
56 for {
57 select {
58 case <-ctx.Done():
59 return ctx.Err()
60 case <-tick:
61 info, err := f.Stat()
62 if err != nil {
63 return err
64 }
65 pos, _ := f.Seek(0, io.SeekCurrent)
66 if info.Size() > pos {
67 return nil
68 }
69 if info.Size() < pos {
70 f.Seek(0, io.SeekStart)
71 return nil
72 }
73 }
74 }
75}
01 / 01
STEP 01

Walkthrough

Space play step click any line
Three takeaways
  1. 1Seeking to the end before reading lets you stream only new content, mimicking tail -f.
  2. 2Partial reads at EOF must be buffered and prepended once the rest of the line arrives.
  3. 3Polling with a ticker and watching file size handles growth and truncation without busy-looping.

Related explainers

Share this explainer

Here's the card — post it anywhere.

How a tail -f follower works in Go — share card
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code