Documentation
¶
Overview ¶
Package trace captures request-lifecycle events (start, end, downstream calls, logs) into an in-memory ring buffer and fans them out to subscribers such as the dashboard. It is the data plane under /__nexus/events.
Index ¶
- func CleanStack(raw string) string
- func HTTPClient(inner *http.Client) *http.Client
- func In(ctx context.Context, name string, attrs ...Attr) func(*error)
- func InjectHeader(ctx context.Context, h http.Header)
- func Middleware(bus *Bus, service, endpoint, transport string) gin.HandlerFunc
- func NewTraceID() string
- func Record(c *gin.Context, name string, start time.Time, err error)
- func StackOf(err error) string
- type Attr
- type Bus
- type Event
- type Kind
- type Span
- type StackError
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func CleanStack ¶ added in v0.21.28
CleanStack trims the runtime + recovery noise from a debug.Stack() output so the user-code frame ends up at the top.
Raw debug.Stack() looks like:
goroutine 25 [running]:
runtime/debug.Stack()
.../runtime/debug/stack.go:26 +0x64
github.com/.../metrics.ginRecorder.func2.1()
.../metrics/middleware.go:62 +0x148
panic({0x...})
.../runtime/panic.go:860 +0x12c
github.com/.../users.NewBoom(...) ← actual panic site
.../examples/microsplit/users/handlers.go:66 +0x28
...
The first three frames (Stack itself, the deferred recover, panic) are framework noise — they shift on every Go release and tell the user nothing about their bug. We strip everything up to and including the panic frame so the cleaned stack starts at the user code. Returns the input unchanged when no `panic(` line is found (defensive — keeps the dashboard usable on edge-case inputs).
func HTTPClient ¶ added in v0.8.0
HTTPClient wraps an *http.Client so every outbound request auto-injects a traceparent header derived from the ctx on the request. Use it to stitch a trace across service boundaries:
client := trace.HTTPClient(nil) // or trace.HTTPClient(&myClient) req, _ := http.NewRequestWithContext(ctx, "GET", url, nil) resp, err := client.Do(req)
If the request already has a traceparent set (e.g. because the caller injected one manually), it's left alone.
func In ¶ added in v0.8.0
In is the common-case sugar for a one-call span. It starts the span and returns a closer that ends it when invoked:
func NewGetUser(...) (u *User, err error) {
defer trace.In(ctx, "db.users.get")(&err)
u, err = db.GetUser(ctx, id)
return
}
Pass nil if the caller has no error to report.
func InjectHeader ¶ added in v0.8.0
InjectHeader writes a W3C traceparent into h using the current span from ctx. No-op if ctx carries no span, or the span's IDs are the wrong length (defensive — our minting always produces the right shape).
The sampled flag is always set ("01") — nexus samples every request.
func Middleware ¶
func Middleware(bus *Bus, service, endpoint, transport string) gin.HandlerFunc
Middleware emits request.start and request.end events bracketing the handler chain, and stashes a *Span and the bus in gin.Context so child spans and Record() attach to the same trace.
If the inbound request carries a valid W3C traceparent header, the root span reuses its TraceID (and records ParentID from the upstream span) so the trace is stitched across services. Otherwise a fresh TraceID is minted.
func NewTraceID ¶ added in v0.3.0
func NewTraceID() string
NewTraceID mints the next trace ID in the same format the request middleware uses. Exposed for code paths that emit events outside the HTTP request lifecycle (e.g. the cron scheduler) so trace IDs stay uniform across the dashboard.
func Record ¶
Record attaches a timed sub-operation (DB call, MQ publish, external HTTP) to the current request's trace as a leaf span. It emits a span.start / span.end pair using the caller-supplied start time so the waterfall places the bar at the right offset.
start := time.Now() err := db.Query(...) trace.Record(c, "db.users.get", start, err)
Prefer trace.In / trace.StartSpan for new code — they compose with ctx instead of needing *gin.Context. Record remains for handlers already written against gin.
Types ¶
type Attr ¶ added in v0.8.0
Attr is a key/value annotation attached to a span. Surfaced in the dashboard's trace waterfall (attrs panel per span).
type Bus ¶
type Bus struct {
// contains filtered or unexported fields
}
Bus is a bounded ring buffer + pub/sub. Slow subscribers drop events rather than block producers — the request path must never be held up by a stalled UI.
func BusFromCtx ¶
BusFromCtx returns the Bus from context, if the trace middleware stashed one.
func (*Bus) SnapshotByTrace ¶ added in v0.8.0
SnapshotByTrace returns every event currently in the ring whose TraceID matches, in publish order. Backs the dashboard's per-trace waterfall view. Returns nil when no events match.
type Event ¶
type Event struct {
ID int64 `json:"id"`
TraceID string `json:"traceId"`
SpanID string `json:"spanId,omitempty"`
ParentID string `json:"parentId,omitempty"`
Name string `json:"name,omitempty"`
Kind Kind `json:"kind"`
Service string `json:"service,omitempty"`
Endpoint string `json:"endpoint,omitempty"`
Transport string `json:"transport,omitempty"`
Method string `json:"method,omitempty"`
Path string `json:"path,omitempty"`
Status int `json:"status,omitempty"`
DurationMs int64 `json:"durationMs,omitempty"`
Message string `json:"message,omitempty"`
Error string `json:"error,omitempty"`
// Stack carries the captured Go runtime stack trace for events
// that originated from a panic. The framework's recovery
// middleware wraps the panic value in a *StackError; metrics +
// trace publishers extract the stack via StackOf and attach it
// here so the dashboard's error views can show "where did this
// come from?" without operators tailing logs.
Stack string `json:"stack,omitempty"`
Remote bool `json:"remote,omitempty"`
Timestamp time.Time `json:"timestamp"`
Meta map[string]any `json:"meta,omitempty"`
}
type Kind ¶
type Kind string
const ( KindRequestStart Kind = "request.start" KindRequestEnd Kind = "request.end" // KindRequestOp is emitted by the metrics middleware AFTER the // handler returns, carrying the specific op name in Endpoint so // UI consumers can drive per-endpoint visualisations (packet // animations, per-op trace rows). request.start from the trace // middleware uses the HTTP path — too coarse to identify a // GraphQL operation; request.op fills that gap. KindRequestOp Kind = "request.op" // KindDownstream is an untimed "X happened during this request" // marker (e.g. resource.using lookup). For timed sub-operations use // KindSpanStart / KindSpanEnd so they render as bars in the // waterfall. KindDownstream Kind = "downstream" KindLog Kind = "log" // KindSpanStart / KindSpanEnd bracket a child span. Emitted by // StartSpan / (*Span).End and by trace.Record. The ParentID field // links children to their parent (the root request or an enclosing // span); the dashboard reconstructs the tree from it. KindSpanStart Kind = "span.start" KindSpanEnd Kind = "span.end" )
type Span ¶
type Span struct {
TraceID string
SpanID string
ParentID string
Name string
Service string
Endpoint string
Start time.Time
// Remote is true when this span's TraceID came from an inbound
// W3C traceparent header (continuing an upstream trace) rather
// than being minted fresh at ingress.
Remote bool
// contains filtered or unexported fields
}
Span represents one unit of work inside a trace. The root span is minted by the request Middleware (HTTP ingress); child spans are pushed with StartSpan and closed with End.
Span carries its bus pointer so End can publish without re-reading ctx — a handler may call End after the request's ctx has expired (deferred cleanup).
func SpanFromCtx ¶
SpanFromCtx reads the current span from any context.Context — root or child, whichever was pushed last. Use this from code paths that don't have a *gin.Context (GraphQL resolvers via p.Context, GORM callbacks, worker code called from a request).
func StartSpan ¶ added in v0.8.0
StartSpan pushes a child span under whatever span is currently in ctx and returns a derived ctx carrying the child. If ctx has no parent span or no bus, the returned span is a no-op stub — End is still safe to call.
Typical use:
ctx, span := trace.StartSpan(ctx, "rabbit.publish", trace.Str("topic", t))
err := ch.Publish(ctx, payload)
span.End(err)
func (*Span) End ¶ added in v0.8.0
End closes the span and publishes a span.end event. Safe to call more than once — subsequent calls are no-ops. Pass nil for success; pass the handler's error for failure so it surfaces on the waterfall.
type StackError ¶ added in v0.21.28
StackError is an error that carries a captured Go stack trace. Used by the framework's panic-recovery middleware to thread both the panic value AND the stack through gin's c.Error/c.Errors path, so downstream middleware (metrics, trace) can surface the stack on the dashboard via StackOf.
Direct use:
&trace.StackError{Err: fmt.Errorf("..."), Stack: string(debug.Stack())}
Errors that don't originate from a panic don't get a stack automatically; user code can opt in by wrapping at the source.
func (*StackError) Error ¶ added in v0.21.28
func (e *StackError) Error() string
func (*StackError) Unwrap ¶ added in v0.21.28
func (e *StackError) Unwrap() error
Unwrap lets errors.Is / errors.As / errors.Unwrap walk past the stack-carrier to the underlying error value.