package main import ( "context" "crypto/rand" "encoding/hex" ) // trace.go threads a per-request correlation id (and the small request facts the logger // and the body-logging gate need) through context — the userver / OpenTelemetry idiom: // mint once at the top of a request, and every log line below it (down to the HTTP call // to the model) carries the same trace_id without passing a logger by hand. ctx is // already plumbed through the whole request path (handleEvent → respond → generate → // LLMClient.Complete → the transport), so a value placed here surfaces everywhere. // // The id is 16 random bytes rendered as 32 hex chars — the W3C Trace-Context / OTel // trace-id shape — so the trace_id field maps straight onto an OpenTelemetry trace id if // an exporter is added later (no log/field rename). Today this is a correlation key, not // a full SpanContext: real distributed tracing would still add a span_id and traceparent // propagation across services. type ctxKey int const reqInfoKey ctxKey = iota // reqInfo is the per-request data carried in context: the trace id stamped on every log // line, the sender (so the body-log lines stay filterable by user), and verbose — // whether this sender is on the LOG_BODIES_USERS allowlist. verbose is decided once, at // admission, so the deep transport never re-checks the allowlist; it just reads the flag. type reqInfo struct { traceID string sender string verbose bool } // withRequestTrace stamps the request's trace id + sender + body-logging decision onto // ctx. Call it once per handled event; the value flows down through the per-room // goroutine, the per-request deadline ctx (WithTimeout preserves values), and into the // model transport. func withRequestTrace(ctx context.Context, traceID, sender string, verbose bool) context.Context { return context.WithValue(ctx, reqInfoKey, reqInfo{traceID: traceID, sender: sender, verbose: verbose}) } func reqInfoFromContext(ctx context.Context) (reqInfo, bool) { ri, ok := ctx.Value(reqInfoKey).(reqInfo) return ri, ok } // traceFromContext returns the request trace id, or "" when ctx carries none (startup, // the appservice transaction handler) — the slog handler then simply omits trace_id. func traceFromContext(ctx context.Context) string { if ri, ok := reqInfoFromContext(ctx); ok { return ri.traceID } return "" } // newTraceID mints a random 16-byte id as 32 hex chars (the OTel trace-id shape). // crypto/rand.Read never returns an error and always fills the buffer (Go 1.24+: on an // entropy failure it crashes the process rather than returning a short read), so ignoring // the error is safe — the id is always fully random. func newTraceID() string { var b [16]byte _, _ = rand.Read(b[:]) return hex.EncodeToString(b[:]) }