// Package obs is the observability seam: trace ids threaded through context, // the slog handler that stamps them on every log line, gated LLM-exchange // logging, and recovered goroutines. Порт vojo trace.go/logging.go + // вынесенный из bot.go safego; ось запроса заменена с (sender, verbose) на // (book, chapter, chunk, stage, role) — домен перевода. package obs import ( "context" "crypto/rand" "encoding/hex" ) // trace.go threads a per-run correlation id and the small request facts the // logger needs through context — the userver / OpenTelemetry idiom: mint once // at the top of a unit of work, 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. // // The id is 16 random bytes rendered as 32 hex chars — the W3C Trace-Context / // OTel trace-id shape — so the field maps straight onto an OTel exporter later. type ctxKey int const reqInfoKey ctxKey = iota // ReqInfo is the per-call data carried in context. Zero-value fields are // simply omitted from logs. type ReqInfo struct { TraceID string Book string Chapter int Chunk int Stage string // pipeline stage: draft | edit | ... Role string // agent role: translator | editor | ... // LogBodies gates raw LLM request/response body logging (privacy by // default; decided once at admission so the transport just reads the flag). LogBodies bool } // WithReqInfo stamps the call's info onto ctx. WithTimeout/WithCancel preserve // values, so it flows down into the model transport. func WithReqInfo(ctx context.Context, ri ReqInfo) context.Context { return context.WithValue(ctx, reqInfoKey, ri) } // ReqInfoFromContext returns the call info, if any. func ReqInfoFromContext(ctx context.Context) (ReqInfo, bool) { ri, ok := ctx.Value(reqInfoKey).(ReqInfo) return ri, ok } // TraceFromContext returns the trace id, or "" when ctx carries none (startup // paths) — 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 on modern Go (an entropy // failure crashes the process rather than returning a short read). func NewTraceID() string { var b [16]byte _, _ = rand.Read(b[:]) return hex.EncodeToString(b[:]) }