Documentation
¶
Overview ¶
Package ctxscope carries attributes on a context.Context and stamps them onto every line logged under that context, so the values travel without anyone passing them around.
Two tiers, split by whether an attribute describes the PROCESS or the WORK:
SetGlobal commit, service, region never travels Set request_id, user_id travels, via ToJSON/FromJSON
Putting a process fact in Set's tier is a bug rather than a style slip: it would cross a hop and overwrite the receiving service's own value, whose logs would then name the wrong deploy.
There are two ways to get the attributes onto a line. Install NewHandler once at startup and plain slog.InfoContext(ctx, ...) carries them everywhere, including inside libraries that never heard of this package; or call GetLogger(ctx) at the log site. The handler is the one nobody can forget.
Index ¶
- func FromJSON(ctx context.Context, data []byte) (context.Context, error)
- func GetLogger(ctx context.Context) *slog.Logger
- func Remove(ctx context.Context, keys ...string) context.Context
- func RemoveGlobal(keys ...string)
- func Set(ctx context.Context, attrs ...Attribute) context.Context
- func SetGlobal(attrs ...Attribute)
- func ToJSON(ctx context.Context) ([]byte, error)
- type Attribute
- type Handler
- type Scope
- type Value
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func FromJSON ¶
FromJSON returns a new context carrying ctx's scope plus what ToJSON wrote on the other side of a hop. Incoming keys win.
Numbers come back as float64, JSON having one number type, so an int sent as 42 returns 42 but is no longer an int. Only matters if you type-assert.
func GetLogger ¶
GetLogger returns slog.Default() with both tiers applied, sorted by key, the context tier winning collisions. The context carries attributes, never a logger — where output goes is slog's business, configured once at startup.
Call it where you log rather than holding the result: a logger is a value, so one fetched before a Set or Remove keeps the attributes it was built with.
func Remove ¶
Remove returns a new context with the named keys dropped. Unset keys are ignored; no keys returns ctx unchanged.
func RemoveGlobal ¶
func RemoveGlobal(keys ...string)
RemoveGlobal drops the named keys from the process-wide scope. Unset keys are ignored.
func Set ¶
Set returns a new context carrying ctx's scope plus every attribute given. A key already set is replaced; no attributes returns ctx unchanged.
ctx = scope.Set(ctx,
scope.Attr("request_id", requestID),
scope.Attr("user_id", userID),
)
func SetGlobal ¶
func SetGlobal(attrs ...Attribute)
SetGlobal adds attributes to every line this process logs, under any context. Call it at startup, with the facts that describe the binary rather than the work — nothing set here is ever serialized by ToJSON.
scope.SetGlobal(
scope.Attr("commit", commitSHA),
scope.Attr("service", serviceName),
)
Types ¶
type Attribute ¶
type Attribute struct {
// contains filtered or unexported fields
}
Attribute is one key/value pair headed into a scope. The fields are unexported so Attr is the only way to build one, which is what keeps the Value constraint despite the field being an any — a variadic Set cannot hold mixed instantiations of a generic type, so the field has to be one.
type Handler ¶
type Handler struct {
// contains filtered or unexported fields
}
Handler stamps the scope carried by a context onto every record passing through it, then delegates to an inner handler.
Installing one at startup is what makes plain slog.InfoContext(ctx, ...) carry request_id — including from code that has never heard of this package, which GetLogger by definition cannot reach:
slog.SetDefault(slog.New(ctxscope.NewHandler(base)))
Handler and GetLogger are ALTERNATIVES, not layers. GetLogger applies the scope itself, so calling it under an installed Handler emits every attribute twice. Install the Handler and log with the Context-suffixed slog calls, or install nothing and go through GetLogger — one or the other, per project.
Only the Context-suffixed calls (InfoContext, ErrorContext, ...) carry a context to the handler. Plain slog.Info hands it a background context, so the line still gets the global tier — which never came from a context — but none of the per-context tier. That is slog's contract, not this package's choice.
func NewHandler ¶
NewHandler wraps inner so that records handled through it carry the scope of the context they were logged with.
func (*Handler) Enabled ¶
Enabled reports whether inner handles this level. Scope never changes the answer — it adds attributes, it does not gate them.
func (*Handler) Handle ¶
Handle merges both tiers onto the record's handler, the context tier winning collisions, and delegates. The merge happens per record because the scope is read from the context at THIS moment — a handler built earlier still sees attributes set later.