69 lines
2.4 KiB
Go
69 lines
2.4 KiB
Go
// 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[:])
|
||
}
|