Documentation
¶
Overview ¶
Package flightrecorder provides Go runtime execution trace recording for cmdguard CLIs via the flight recorder pattern (Go 1.25+ runtime/trace).
The flight recorder continuously buffers the last few seconds of execution trace in memory. When something goes wrong — a slow command, an error, or a panic — you can snapshot the buffer to capture exactly the problematic window of time. The resulting .trace file can be analyzed with `go tool trace`.
This is an optional module — it has zero external dependencies (uses only the Go standard library runtime/trace package). Import it only when you need execution trace diagnostics.
Quick start ¶
Wire it as a single CLIOption and every command that exceeds the slow threshold or returns an error will automatically produce a .trace file:
import (
v4 "github.com/larsartmann/cmdguard/v4/pkg/cmdguard/v4"
"github.com/larsartmann/cmdguard/flightrecorder"
)
cli, _ := v4.NewCLI[Config]("app", "My app", Config{},
flightrecorder.WithFlightRecorder[Config](flightrecorder.Config{
CaptureOnSlow: true,
SlowThreshold: 200 * time.Millisecond,
CaptureOnError: true,
}),
)
Manual control ¶
For advanced use cases (custom capture triggers, shared recorder across multiple CLIs), use the Recorder and Middleware types directly:
rec := flightrecorder.New(flightrecorder.DefaultConfig())
if err := rec.Start(); err != nil {
log.Printf("flight recorder: %v", err)
}
defer rec.Stop()
cli, _ := v4.NewCLI[Config]("app", "My app", Config{},
v4.WithMiddleware(flightrecorder.Middleware[Config](rec)),
)
Analyzing traces ¶
After a snapshot is captured, analyze it with:
go tool trace /path/to/snapshot.trace
This launches a local web server with an interactive trace viewer.
Index ¶
- Variables
- func Middleware[T any](rec *Recorder) v4.Middleware[T]
- func WithFlightRecorder[T any](cfg Config) v4.CLIOption
- func WithFlightRecorderRecorder[T any](rec *Recorder) v4.CLIOption
- type CaptureReason
- type Config
- type Recorder
- func (rec *Recorder) Capture(ctx context.Context, commandName string, reason CaptureReason) (string, error)
- func (rec *Recorder) CaptureToWriter(ctx context.Context, writer io.Writer, commandName string, ...) (int64, error)
- func (rec *Recorder) Config() Config
- func (rec *Recorder) Enabled() bool
- func (rec *Recorder) Start() error
- func (rec *Recorder) Stop()
- func (rec *Recorder) WriteTo(writer io.Writer) (int64, error)
Examples ¶
Constants ¶
This section is empty.
Variables ¶
var ( // ErrAlreadyStarted is returned when Start is called on an already-running recorder. ErrAlreadyStarted = errors.New("flightrecorder: recorder already started") // ErrNotEnabled is returned when an operation requires an active recorder but none is running. ErrNotEnabled = errors.New("flightrecorder: recorder not enabled") )
Sentinel errors for Recorder operations.
Functions ¶
func Middleware ¶
func Middleware[T any](rec *Recorder) v4.Middleware[T]
Middleware returns a cmdguard middleware that manages the flight recorder lifecycle and automatically captures trace snapshots when configured conditions are met.
The recorder is lazily started on the first command invocation. Snapshots are only captured during the run phase to avoid duplicate captures from pre-run/post-run phases.
The middleware never blocks command execution on recorder operations: if Start fails (e.g. another recorder is already active), the command proceeds without tracing. Snapshot capture runs in a background goroutine so the command response is not delayed.
The returned error from next is always passed through unchanged — the middleware only adds observability, it never changes program behavior.
func WithFlightRecorder ¶
WithFlightRecorder is a convenience CLIOption that creates a Recorder from the given Config and registers it as middleware via v4.WithMiddleware.
The recorder is lazily started on first command execution. For explicit lifecycle control (Start/Stop), use New + Middleware instead.
Example:
cli, _ := v4.NewCLI[Config]("app", "My app", Config{},
flightrecorder.WithFlightRecorder[Config](flightrecorder.Config{
CaptureOnSlow: true,
SlowThreshold: 200 * time.Millisecond,
CaptureOnError: true,
}),
)
func WithFlightRecorderRecorder ¶
WithFlightRecorderRecorder is a CLIOption that registers a pre-created Recorder as middleware. Unlike WithFlightRecorder (which creates a new Recorder from Config), this variant gives the caller explicit lifecycle control — useful when multiple CLIs share a single recorder or when Start/Stop must be called at specific times.
Example:
rec := flightrecorder.New(flightrecorder.DefaultConfig())
defer rec.Stop()
cli, _ := v4.NewCLI[Config]("app", "My app", Config{},
flightrecorder.WithFlightRecorderRecorder[Config](rec),
)
Types ¶
type CaptureReason ¶
type CaptureReason string
CaptureReason describes why a snapshot was taken.
Example ¶
package main
import (
"fmt"
"github.com/larsartmann/cmdguard/flightrecorder"
)
func main() {
fmt.Println(flightrecorder.CaptureReasonSlow)
fmt.Println(flightrecorder.CaptureReasonError)
}
Output: slow error
const ( // CaptureReasonSlow indicates the command exceeded the configured slow threshold. CaptureReasonSlow CaptureReason = "slow" // CaptureReasonError indicates the command returned an error. CaptureReasonError CaptureReason = "error" )
type Config ¶
type Config struct {
// MinAge is the duration for which trace data is reliably retained in the
// in-memory buffer. Older data may be evicted. Suggested: ~2x the time
// window of the event you are debugging (e.g. if debugging a 5-second
// timeout, set to 10 seconds).
//
// Default: 5 * time.Second
MinAge time.Duration
// MaxBytes limits the in-memory buffer size to prevent unbounded memory
// growth. Expect roughly 10 MB/s of trace data for a busy service.
//
// Default: 10 MiB (10 << 20)
MaxBytes uint64
// CaptureOnSlow controls whether a snapshot is automatically captured when
// a command's run phase exceeds SlowThreshold.
//
// Default: true
CaptureOnSlow bool
// SlowThreshold is the duration above which a command is considered slow
// and triggers a snapshot (when CaptureOnSlow is true).
//
// Default: 100 * time.Millisecond
SlowThreshold time.Duration
// CaptureOnError controls whether a snapshot is automatically captured when
// a command returns a non-nil error from its run phase.
//
// Default: false
CaptureOnError bool
// OutputDir is the directory where snapshot .trace files are written.
// The directory is created if it does not exist. If empty, uses os.TempDir().
//
// Default: "" (resolves to os.TempDir())
OutputDir string
// FilenamePrefix is prepended to snapshot filenames. Snapshot files are
// named: {prefix}-{command}-{reason}-{timestamp}.trace
//
// Default: "cmdguard"
FilenamePrefix string
// Log receives diagnostic messages (e.g. snapshot captured, start failed).
// If nil, messages are written to os.Stderr.
//
// Default: nil (os.Stderr)
Log func(format string, args ...any)
}
Config configures the flight recorder behavior.
func DefaultConfig ¶
func DefaultConfig() Config
DefaultConfig returns a Config with sensible defaults for typical CLI usage.
Example ¶
package main
import (
"fmt"
"github.com/larsartmann/cmdguard/flightrecorder"
)
func main() {
cfg := flightrecorder.DefaultConfig()
fmt.Println("capture on slow:", cfg.CaptureOnSlow)
fmt.Println("slow threshold:", cfg.SlowThreshold)
fmt.Println("capture on error:", cfg.CaptureOnError)
fmt.Println("filename prefix:", cfg.FilenamePrefix)
}
Output: capture on slow: true slow threshold: 100ms capture on error: false filename prefix: cmdguard
type Recorder ¶
type Recorder struct {
// contains filtered or unexported fields
}
Recorder wraps runtime/trace.FlightRecorder with snapshot management. It is safe for concurrent use.
At most one flight recorder may be active per process at any given time (runtime/trace limitation). If you create multiple Recorders, only the first Start will succeed; subsequent starts return an error.
func New ¶
New creates a Recorder from the given Config. The recorder is NOT started — call Start to begin recording. Zero-valued Config fields are replaced with defaults.
Example ¶
package main
import (
"fmt"
"time"
"github.com/larsartmann/cmdguard/flightrecorder"
)
func main() {
rec := flightrecorder.New(flightrecorder.Config{
CaptureOnSlow: true,
SlowThreshold: 500 * time.Millisecond,
CaptureOnError: true,
OutputDir: "/tmp/myapp-traces",
})
fmt.Println("enabled before start:", rec.Enabled())
if err := rec.Start(); err != nil {
fmt.Println("failed to start:", err)
return
}
defer rec.Stop()
fmt.Println("enabled after start:", rec.Enabled())
}
Output: enabled before start: false enabled after start: true
func (*Recorder) Capture ¶
func (rec *Recorder) Capture(ctx context.Context, commandName string, reason CaptureReason) (string, error)
Capture writes a trace snapshot to a file in OutputDir. The filename includes the command name, capture reason, and timestamp. Returns the path to the written file.
If the recorder is not enabled, Capture returns an error without writing.
func (*Recorder) CaptureToWriter ¶
func (rec *Recorder) CaptureToWriter( ctx context.Context, writer io.Writer, commandName string, reason CaptureReason, ) (int64, error)
CaptureToWriter writes a trace snapshot to the given writer instead of a file. This decouples snapshot writing from the filesystem, useful for piping to stdout, network storage, or testing. Returns the number of bytes written.
If the recorder is not enabled, CaptureToWriter returns an error without writing.
func (*Recorder) Start ¶
Start begins recording execution traces into the in-memory buffer. Returns an error if the recorder is already started or if the runtime rejects the start (e.g. another flight recorder is already active).
func (*Recorder) Stop ¶
func (rec *Recorder) Stop()
Stop stops recording. Safe to call multiple times. After stopping, the recorder can be restarted with Start.
Stop waits for any in-flight snapshot capture (WriteTo/Capture) to complete before stopping the underlying recorder, preventing races between snapshot writes and recorder shutdown.