Operations 12 min read

How Go 1.25’s Trace Flight Recorder Enables Low‑Overhead Production Debugging

Go 1.25 introduces Trace Flight Recorder, a lightweight circular‑buffer tracing tool that lets developers capture recent execution data in production with minimal overhead, and the article walks through its concepts, configuration, code demos, analysis workflow, and practical use cases.

BirdNest Tech Talk
BirdNest Tech Talk
BirdNest Tech Talk
How Go 1.25’s Trace Flight Recorder Enables Low‑Overhead Production Debugging

Tracing vs Flight Recording

Traditional tracing records every event for the entire program lifetime, producing large trace files and incurring high overhead. Flight Recording keeps only the most recent events in a circular buffer, discarding older data automatically. This bounded buffer makes it suitable for continuous production use.

Configuration

The core struct is trace.FlightRecorderConfig with two fields: MinAge – minimum duration an event must stay in the buffer before it can be dropped (e.g., 5 * time.Second keeps data for at least five seconds). MaxBytes – maximum size of the circular buffer (e.g., 3 * 1024 * 1024 limits the buffer to 3 MB).

These are advisory limits; the runtime may keep slightly more data.

Step‑by‑step demonstration

1. Simple HTTP server with a heavy endpoint

package main

import (
    "log"
    "net/http"
    "math/big"
    "crypto/sha256"
    "strconv"
    "runtime"
)

func pow(targetBits int) [32]byte {
    target := big.NewInt(1)
    target.Lsh(target, uint(256-targetBits))
    var hashInt big.Int
    var hash [32]byte
    nonce := 0
    for {
        data := "hello world " + strconv.Itoa(nonce)
        hash = sha256.Sum256([]byte(data))
        hashInt.SetBytes(hash[:])
        if hashInt.Cmp(target) == -1 {
            break
        }
        nonce++
        if nonce%100 == 0 {
            runtime.Gosched()
        }
    }
    return hash
}

func handler(w http.ResponseWriter, r *http.Request) {
    if r.URL.Path == "/heavy" {
        _ = pow(20) // simulate CPU load
    }
    w.Write([]byte("Hello, world!"))
}

func main() {
    http.HandleFunc("/", handler)
    log.Fatal(http.ListenAndServe(":8080", nil))
}

2. Start a Flight Recorder

package main

import (
    "log"
    "net/http"
    "time"
    "runtime/trace"
)

var recorder *trace.FlightRecorder

func main() {
    cfg := trace.FlightRecorderConfig{MinAge: 5 * time.Second, MaxBytes: 3 * 1024 * 1024}
    recorder = trace.StartFlightRecorder(cfg)
    defer recorder.Stop()

    http.HandleFunc("/", handler)
    log.Fatal(http.ListenAndServe(":8080", nil))
}

3. Add a performance trigger that writes the latest buffer to a file

package main

import (
    "log"
    "net/http"
    "time"
    "runtime/trace"
    "os"
    "strings"
    "math/rand"
)

func handlerWithTrigger(w http.ResponseWriter, r *http.Request) {
    // Random difficulty between 10 and 30 bits
    hash := pow(rand.Intn(20) + 10)
    // Trigger when the first six hex characters are zero
    if strings.HasPrefix(fmt.Sprintf("%x", hash[:]), "000000") {
        f, err := os.Create("trace.out")
        if err != nil {
            log.Println("failed to create trace file:", err)
            return
        }
        defer f.Close()
        if _, err := recorder.WriteTo(f); err != nil {
            log.Println("failed to write trace data:", err)
        }
    }
    w.Write([]byte(fmt.Sprintf("%x", hash[:])))
}

func main() {
    cfg := trace.FlightRecorderConfig{MinAge: 5 * time.Second, MaxBytes: 3 * 1024 * 1024}
    recorder = trace.NewFlightRecorder(cfg)
    if err := recorder.Start(); err != nil {
        log.Fatalf("failed to start FlightRecorder: %v", err)
    }
    defer recorder.Stop()

    http.HandleFunc("/", handlerWithTrigger)
    log.Fatal(http.ListenAndServe(":8080", nil))
}

After a trigger fires, the file trace.out can be inspected with: go tool trace trace.out The tool opens a browser visualising goroutine lifecycles, CPU utilisation per logical processor, and heap allocation/GC patterns.

Analyzing trace data

Goroutine activity – creation, execution, and blocking.

CPU usage – per‑processor utilisation over time.

Heap usage – allocation rates and garbage‑collection behaviour.

Core API

All functionality lives in the runtime/trace package.

type FlightRecorder struct { /* internal fields */ }

func NewFlightRecorder() *FlightRecorder
func (fr *FlightRecorder) SetMinAge(d time.Duration)
func (fr *FlightRecorder) SetMaxBytes(b uint64)
func (fr *FlightRecorder) Start() error
func (fr *FlightRecorder) Stop() error
func (fr *FlightRecorder) Enabled() bool
func (fr *FlightRecorder) WriteTo(w io.Writer) (int64, error)

The recorder hooks into the runtime’s tracing system (e.g., the ProcSteal event) to capture scheduler‑level events such as P‑stealing, including the stolen M’s ID, the event sequence number, and the previous M’s ID.

Design background

The feature originated from Go issue #63185 (https://github.com/golang/go/issues/63185). It mirrors Java Flight Recorder’s circular‑buffer approach, allowing a program to retain only the most recent seconds of execution. The implementation became stable in Go 1.22 (issue #60773: https://github.com/golang/go/issues/60773) after Go 1.21 reduced tracing overhead, making continuous recording feasible in production clusters.

Design documentation resides at https://go.googlesource.com/proposal/+/ac09a140c3d26f8bb62cbad8969c8b154f93ead6/design/60773-execution-tracer-overhaul.md, which describes the partitioned trace format introduced in Go 1.22 and extended in Go 1.25.

Typical use cases

Production debugging – capture context around rare, hard‑to‑reproduce errors without the cost of a full trace.

Performance monitoring – diagnose intermittent latency spikes that only appear under specific conditions.

Memory‑constrained environments – low overhead and bounded buffer size suit devices with limited resources.

Trace Flight Recorder diagram
Trace Flight Recorder diagram
Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

GoRuntimePerformance MonitoringTracingProduction Debuggingflight-recorder
BirdNest Tech Talk
Written by

BirdNest Tech Talk

Author of the rpcx microservice framework, original book author, and chair of Baidu's Go CMC committee.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.