runtime/trace is the first GOWDK Trace runtime slice. It is dependency-free
and can be used from normal Go programs without generated app wiring.
Use Observability for generated app instrumentation, the local viewer, and optional OTLP export.
package main
import (
"context"
"os"
gowdktrace "github.com/cssbruno/gowdk/runtime/trace"
)
func main() {
tracer := gowdktrace.NewTracer(
gowdktrace.WithSink(gowdktrace.NewJSONLSink(os.Stdout)),
)
ctx, span := tracer.Start(context.Background(), "GET /patients",
gowdktrace.WithSurface(gowdktrace.SurfaceBackend),
gowdktrace.WithLane(gowdktrace.LaneRoute),
gowdktrace.WithAttributes(map[string]any{
gowdktrace.AttrHTTPRoute: "/patients",
}),
)
defer span.End()
_ = ctx
span.Event("info", "loaded patients", nil)
span.SetStatus(gowdktrace.StatusOK, "")
}Start returns nil for the span when sampling is disabled. Span methods are
nil-safe, so defer span.End() remains valid.
Use Inject and Extract with carriers such as http.Header:
headers := http.Header{}
gowdktrace.Inject(ctx, headers)
ctx = gowdktrace.Extract(context.Background(), headers)The wire format is W3C traceparent:
00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01
Current sinks:
NewConsoleSink(io.Writer): one readable line per completed span.NewJSONLSink(io.Writer): one JSON span per line.NewRingSink(limit): bounded in-memory recent spans, dropping oldest on overflow.MultiSink(...): sends spans to multiple sinks in order.ExporterSink(exporter): adapts an OTLP-like exporter interface.NewCollector(limit): sink plus local JSON/SSE HTTP handler and browser span ingest.
collector := gowdktrace.NewCollector(256)
tracer := gowdktrace.NewTracer(gowdktrace.WithSink(collector))
http.Handle("/_gowdk/traces", collector.Handler())GET /_gowdk/traces returns:
{
"spans": [],
"dropped": 0
}GET /_gowdk/traces/events or a request with Accept: text/event-stream
streams event: gowdk-trace messages for existing and future spans. The viewer
handler adds GET / for the self-contained UI and POST /browser for generated
browser spans.
AlwaysOn()AlwaysOff()RatioSampler(0.10)
The disabled AlwaysOff path with no start options is allocation-free.
- Collector storage is in-memory and process-local.
- Generated app instrumentation is opt-in through
addons/observabilityand debug builds. - Concrete OTLP export lives in the nested
runtime/trace/otelmodule so the root module does not depend on OpenTelemetry.