package obs
import (
"context"
"strconv"
"sync/atomic"
)
type ctxRequest struct{}
var requestSeq atomic.Uint64
// WithRequest tags a context with a fresh request id and returns both.
//
// The id is what lets a store call name the page that caused it. Before
// it, the subprocess panel and the render panel were two independent
// lists: "GET /search took 1132ms" and "zit artifact ran 499 times"
// could not be connected even when one caused the other, so the page
// could find a slow route and never say why.
//
// A counter rather than a UUID: this identifies a request WITHIN one
// process's memory for as long as the rings hold it, and nothing
// compares ids across processes. A cheap monotonic value is the whole
// requirement.
func WithRequest(ctx context.Context) (context.Context, string) {
id := strconv.FormatUint(requestSeq.Add(1), 36)
return context.WithValue(ctx, ctxRequest{}, id), id
}
// RequestID is the tag on this context, empty when there is none —
// which is the ordinary case for work with no request behind it: ingest
// rounds, warm passes, the doc indexer. Callers record the empty string
// rather than guessing, because "background" is a true answer and a
// wrong attribution is not.
func RequestID(ctx context.Context) string {
if ctx == nil {
return ""
}
id, _ := ctx.Value(ctxRequest{}).(string)
return id
}
// ctxReason tags a context with WHY the work beneath it is happening,
// for calls that have no request behind them.
type ctxReason struct{}
// The reasons quarry names. A closed set on purpose: an operator scans
// this column, and a free-text field would grow one spelling per call
// site until the column could not be grouped.
const (
ReasonSync = "sync" // pulling from an upstream mirror
ReasonIngest = "ingest" // folding new artifacts into the index
ReasonWarm = "warm" // building a cache before anybody asks
ReasonDocIndex = "doc index" // the documentation sweep
ReasonCI = "ci" // cloning or driving a CI run
)
// WithReason names the background activity a subprocess serves. The
// request path needs none of this — RequestID already says which render
// caused the call, and the route is a better answer than any word here.
// This is for the work that answers to nobody: without it every ingest
// round, warm pass and doc sweep records the same empty string and the
// panel can only say "background".
func WithReason(ctx context.Context, reason string) context.Context {
if ctx == nil {
ctx = context.Background()
}
return context.WithValue(ctx, ctxReason{}, reason)
}
// Reason is why this context's work is happening, empty when a request
// is behind it or when nobody said.
func Reason(ctx context.Context) string {
if ctx == nil {
return ""
}
r, _ := ctx.Value(ctxReason{}).(string)
return r
}