trace

package
v0.8.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 24, 2026 License: MIT Imports: 9 Imported by: 0

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

Constants

This section is empty.

Variables

This section is empty.

Functions

func HTTPClient added in v0.8.0

func HTTPClient(inner *http.Client) *http.Client

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

func In(ctx context.Context, name string, attrs ...Attr) func(*error)

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

func InjectHeader(ctx context.Context, h http.Header)

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

func Record(c *gin.Context, name string, start time.Time, err error)

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

type Attr struct {
	Key string
	Val any
}

Attr is a key/value annotation attached to a span. Surfaced in the dashboard's trace waterfall (attrs panel per span).

func Any added in v0.8.0

func Any(k string, v any) Attr

func Bool added in v0.8.0

func Bool(k string, v bool) Attr

func Int added in v0.8.0

func Int(k string, v int64) Attr

func Str added in v0.8.0

func Str(k, v string) Attr

Str / Int / Bool / Any are constructor sugar for the common attr types. They exist so call sites stay short:

trace.StartSpan(ctx, "db.users.get", trace.Str("id", userID))

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 BusFrom

func BusFrom(c *gin.Context) (*Bus, bool)

func BusFromCtx

func BusFromCtx(ctx context.Context) (*Bus, bool)

BusFromCtx returns the Bus from context, if the trace middleware stashed one.

func NewBus

func NewBus(capacity int) *Bus

func (*Bus) Publish

func (b *Bus) Publish(e Event)

func (*Bus) SnapshotByTrace added in v0.8.0

func (b *Bus) SnapshotByTrace(traceID string) []Event

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.

func (*Bus) Subscribe

func (b *Bus) Subscribe(sinceID int64, buffer int) ([]Event, <-chan Event, func())

Subscribe atomically captures the backlog (events with ID > sinceID) and registers a channel for future events. Doing both under one lock guarantees a reconnecting client sees every event exactly once — no gap, no duplicate.

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"`
	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 SpanFrom

func SpanFrom(c *gin.Context) (*Span, bool)

func SpanFromCtx

func SpanFromCtx(ctx context.Context) (*Span, bool)

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

func StartSpan(ctx context.Context, name string, attrs ...Attr) (context.Context, *Span)

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

func (s *Span) End(err error)

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.

func (*Span) Set added in v0.8.0

func (s *Span) Set(key string, val any)

Set records one attribute on the span. Safe for concurrent use.

func (*Span) SetAttrs added in v0.8.0

func (s *Span) SetAttrs(attrs ...Attr)

SetAttrs applies a batch of attributes.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL