66 lines
2.7 KiB
Go
66 lines
2.7 KiB
Go
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[:])
|
|
}
|