obsx

package
v0.17.3 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 31, 2026 License: Apache-2.0 Imports: 30 Imported by: 0

Documentation

Overview

Code generated by apic; DO NOT EDIT.

Package obsx is the observability layer for apic services, bundling structured logging, metrics, and tracing behind a small package-level API. It emits JSON request and audit logs through a buffered, periodically flushed stdout writer (with an optional slog-based hook and a value-redacting handler for sensitive fields), assigns request IDs, and records metrics via a swappable MetricsProvider whose default is expvar-backed (request counts, latency histograms, WebSocket frame counters, plus auth-attempt and rate-limit-rejection helpers). When an OpenTelemetry TelemetryProvider is configured it also creates OTEL instruments and a per-request tracing middleware with W3C trace-context propagation; with telemetry disabled that middleware is a zero-cost passthrough.

Two sub-packages extend this into a full OTEL/Prometheus deployment: otelx bootstraps the OpenTelemetry SDK from the standard OTEL_* env vars (or config-file / code overrides) and delivers traces, metrics, and logs over an in-repo OTLP/HTTP JSON exporter, while promx implements MetricsProvider on top of prometheus/client_golang — preserving labels (unlike the expvar default) behind a per-metric cardinality cap, and serving the scrape handler generated servers mount at the authenticated /metrics endpoint. See ../../docs/OBSERVABILITY.md for the operator guide. Code generated by apic; DO NOT EDIT.

Code generated by apic; DO NOT EDIT.

Code generated by apic; DO NOT EDIT.

Code generated by apic; DO NOT EDIT.

Code generated by apic; DO NOT EDIT.

Code generated by apic; DO NOT EDIT.

Code generated by apic; DO NOT EDIT.

Code generated by apic; DO NOT EDIT.

Code generated by apic; DO NOT EDIT.

Code generated by apic; DO NOT EDIT.

Code generated by apic; DO NOT EDIT.

Index

Constants

View Source
const DefaultCorrelationHeader = "X-Correlation-ID"

DefaultCorrelationHeader is the conventional end-to-end correlation header.

View Source
const RedactedValue = "[REDACTED]"

RedactedValue is the sentinel substituted for any map value whose key matched the sensitive-key list. Exported so tests and downstream tooling can compare against it without re-declaring the string.

Variables

View Source
var (

	// AppName is the application name included in logs.
	AppName string
	// AppVersion is the application version included in logs.
	AppVersion string
)
View Source
var DefaultSensitiveKeyFragments = []string{
	"api_key",
	"authorization",
	"authorization_code",
	"bearer",
	"ca_pem",
	"cert_pem",
	"cipher",
	"code_challenge",
	"code_verifier",
	"cookie",
	"csrf",
	"dsn",
	"encryption",
	"hash",
	"jwt",
	"key_pem",
	"passwd",
	"password",
	"pem",
	"private_key",
	"salt",
	"secret",
	"session",
	"token",
}

DefaultSensitiveKeyFragments is the case-insensitive substring list used by RedactSensitiveKeys and LogAudit to identify map keys whose values must be replaced with RedactedValue before emission. Generators and applications can extend this list at init time, or supply their own list to RedactSensitiveKeys directly.

The list is intentionally conservative: false positives (over-redaction) are far cheaper than false negatives (leaking a secret to an audit channel). All matches are case-insensitive substring matches: a key "user_password_hash" matches both "password" and "hash".

Adding to this slice from package code at init time is safe; mutating it concurrently with LogAudit / RedactSensitiveKeys calls is not. Audit-call convention: when emitting an OAuth/OIDC authorization code into an audit record, the canonical key is "authorization_code" — never the bare "code". The bare form would substring-collide with benign fields like "status_code" and "error_code" and force the redaction policy to either over-redact debugging signals or leak credentials. The explicit "authorization_code" fragment below is redundant with "authorization" (case-insensitive substring) but is recorded independently so the policy intent is unambiguous and so a future narrower matcher cannot silently drop the protection (A-NEW-9).

View Source
var (
	ErrInvalidConfig = errors.New("obsx: invalid configuration")
)

ErrInvalidConfig is returned when obsx receives an invalid configuration. Kept at package level per repo conventions; message uses a lowercase, package-prefixed, diagnostic form so log output reads naturally.

Functions

func ContextWithCorrelationID added in v0.15.1

func ContextWithCorrelationID(ctx context.Context, id string) context.Context

ContextWithCorrelationID attaches a correlation id (client-side use: set it on an outbound call's ctx so generated clients propagate it).

func ContextWithRequestID added in v0.17.0

func ContextWithRequestID(ctx context.Context, id string) context.Context

ContextWithRequestID attaches a request id to ctx so RequestIDFrom(r) returns it for the lifetime of the request, without re-consulting headers.

func CorrelationIDFrom added in v0.15.1

func CorrelationIDFrom(ctx context.Context) string

CorrelationIDFrom returns the request's correlation id, or "".

func CorrelationMiddleware added in v0.15.1

func CorrelationMiddleware(header string) func(http.Handler) http.Handler

CorrelationMiddleware honors a valid inbound correlation id, replaces an invalid/absent one with a fresh UUIDv4, echoes the normalized value on the response, rewrites the request header to the normalized value (so in-process readers never see the raw inbound), and stores it in ctx. header == "" uses DefaultCorrelationHeader. The correlation id is deliberately independent of the W3C traceparent: it is always present, never sampled away, and never parsed from trace headers.

func EmitAuthAttempt

func EmitAuthAttempt(ctx context.Context, method string, ok bool)

EmitAuthAttempt records an auth attempt via MetricsProvider and OTEL.

A-P1.3: the default ExpvarMetrics provider IGNORES the labels map (`pkg/obsx/metrics.go:52` — labels are passed and discarded). Pre-fix every authed request built a fresh `map[string]string{"method":..., "status":...}` even when the operator never wired a label-aware provider. Pass nil for the default path; the OTel branch below builds attributes directly via `attribute.String` and never needed the intermediate map.

func EmitGQLError

func EmitGQLError(ctx context.Context, errorType string)

EmitGQLError records a GraphQL error by type.

func EmitGQLQuery

func EmitGQLQuery(ctx context.Context, operationName string, durationMs float64, ok bool)

EmitGQLQuery records a GraphQL query execution. The operation name is supplied by the (possibly untrusted) GraphQL client and is sanitized by safeOperationLabel before being used as a metric label to bound cardinality. APPSEC-12.

func EmitGQLSubActive

func EmitGQLSubActive(ctx context.Context, delta int64)

EmitGQLSubActive adjusts the active GraphQL subscription gauge.

func EmitMCPRequest

func EmitMCPRequest(ctx context.Context, method string)

EmitMCPRequest records an MCP JSON-RPC request.

func EmitMCPToolCall

func EmitMCPToolCall(ctx context.Context, tool string, durationMs float64, ok bool)

EmitMCPToolCall records an MCP tool invocation.

func EmitPanic

func EmitPanic(ctx context.Context)

EmitPanic records a panic recovery event.

func EmitRateLimitReject

func EmitRateLimitReject(ctx context.Context, route string)

EmitRateLimitReject records a rate limit rejection.

A-P1.3: same rationale as EmitAuthAttempt — the default Expvar provider drops labels; pass nil to skip the per-request map alloc.

func EmitWSConnect

func EmitWSConnect(ctx context.Context, path string)

EmitWSConnect records a new WebSocket connection.

func EmitWSDisconnect

func EmitWSDisconnect(ctx context.Context, path string, durationMs float64)

EmitWSDisconnect records a WebSocket disconnection with duration.

func EmitWSFrame

func EmitWSFrame(ctx context.Context, path, direction string, n int)

EmitWSFrame records a WebSocket frame via MetricsProvider and OTEL.

func EmitWSUpgradeRejected

func EmitWSUpgradeRejected(ctx context.Context, path, reason string)

EmitWSUpgradeRejected records a WebSocket upgrade rejection from the apic-emitted pre-handshake gate (GAP-0075). The reason label distinguishes the wsx.Limiter sentinel that fired so operators can attribute rejections without spelunking through logs.

Reason values are short, stable identifiers expected by dashboards: "total_cap", "per_ip_cap", "rate", or an operator-defined string for caller-extension scenarios. Path is the WebSocket route as configured (e.g. "/ws/feed").

func FlushLogs

func FlushLogs()

FlushLogs flushes any buffered JSON log records to os.Stdout. Consumers MUST defer FlushLogs() in main() (and call it immediately before any os.Exit on a fatal path) so log lines buffered by the obsx logger are not lost on shutdown. It is safe to call concurrently and repeatedly.

func IsSensitiveKey

func IsSensitiveKey(key string, fragments []string) bool

IsSensitiveKey reports whether the lower-cased form of key contains any substring from fragments. fragments must already be lower-cased; this is the same convention DefaultSensitiveKeyFragments enforces.

func LogAudit

func LogAudit(event string, fields map[string]any)

LogAudit emits a JSON audit log entry. The audit event identifier is written under the "audit_event" key, and the rest of fields is emitted alongside. Before emission, every value whose key (case-insensitive) contains a substring from DefaultSensitiveKeyFragments is replaced with RedactedValue. This guards against accidental secret leaks from call sites that forget to scrub credentials, JWTs, TLS keys, etc. before logging.

Callers that need a custom redaction list (e.g. to add app-specific secret-key prefixes) should call RedactSensitiveKeys explicitly and then call LogAudit with the redacted map; LogAudit always re-applies the default list on top, so an app-specific list is a strict superset of the defaults.

NP-09: the level check below runs BEFORE any of the caller's fields map is touched. Without it, a record destined to be dropped by the configured slog level (e.g. LevelError in production) still paid for the full redaction walk (a pre-scan of every key plus a second map allocation) and the []any boxing in slogLogAt — indistinguishable in cost from a record that is actually emitted. logFn is always slogLog once the package's init() has run (see log_hooks.go), so gating on the installed slog.Logger's Enabled() here reflects exactly what logFn would decide downstream, just before doing any work.

func LogAuditAttrs added in v0.17.0

func LogAuditAttrs(event string, attrs ...slog.Attr)

LogAuditAttrs is a NP-09 fast path for callers whose field set is fixed and known at the call site to contain no sensitive-key fragments — generated REST/WS/GraphQL/MCP handlers logging their standard path/id/reason-shaped audit events, for example. It skips LogAudit's redaction walk (the pre-scan of every key plus the second map allocation) entirely and writes straight through slog.Logger.LogAttrs, avoiding the map[string]any -> []any boxing slogLogAt performs for the map-based path.

Trust contract: callers MUST NOT pass attrs whose VALUE may itself contain a secret in a form that DefaultSensitiveKeyFragments would otherwise have caught by KEY (e.g. do not name an attr "token" or "password" and expect this path to redact it — it does not). This function is for the generator's own fixed, reviewed key set, not a general replacement for LogAudit(map[string]any); application callers with caller-controlled or dynamic keys must keep using LogAudit so the CWE-532 redaction contract still applies.

The wire shape matches LogAudit exactly: message "log", with the audit event name carried as the "audit_event" attribute (plus app_name / app_version when configured) — see slogLogAt's identical convention for the map-based path.

func LogMCP

func LogMCP(event string, fields map[string]any)

LogMCP emits a JSON log entry for MCP events. Fields get the same redaction treatment as LogWS / LogAudit (N-09).

func LogWS

func LogWS(event string, fields map[string]any)

LogWS emits a JSON log entry for WebSocket events. Fields are routed through the same redaction walk as LogAudit (N-09): every value whose key (case-insensitive) contains a substring from DefaultSensitiveKeyFragments is replaced with RedactedValue before emission, so a forgetful caller cannot leak credentials into the WS event log that the audit channel would have scrubbed.

func Logger added in v0.15.1

func Logger() *slog.Logger

Logger returns the currently-installed package logger — the redaction-wrapped fanout installed by SetLogger (so its output reaches every delivered sink and honours field redaction). Consumers that emit their own structured events, such as the generated health manager, should log through this rather than re-resolving a separate logger, so their events land in the same delivered/redacted stream as request logs.

func Meter

func Meter(name string) metric.Meter

Meter returns a named meter. Returns a cached no-op meter when telemetry is disabled. Same rationale as Tracer above.

func Middleware

func Middleware(next http.Handler) http.Handler

Middleware adds a request id, emits JSON logs on start/end, exposes basic counters and latency histograms, and recovers handler panics into a structured 500 (F1). Without this recovery a panic hits net/http's per-connection recover (aborted connection, no structured body, no metric, no request id).

func NewRequestID

func NewRequestID() string

NewRequestID creates a new request id string. The encoding is "<base36-counter>-<base36-unix-nanos>". Implemented with stack-allocated scratch buffers so the only heap allocation is the returned string itself (a single allocation per call, vs. the prior format/concat path which allocated three).

func NewRotatingHandler added in v0.15.1

func NewRotatingHandler(cfg LogFileConfig) (slog.Handler, io.Closer, error)

NewRotatingHandler builds a JSON slog.Handler that writes newline-delimited records to a size-rotated file via lumberjack, returning the handler, an io.Closer that flushes and closes the underlying file, and an error.

It is fail-closed: an empty path or an unwritable target (parent cannot be created / opened) surfaces as an error at construction rather than silently dropping logs at runtime. The probe uses a zero-length write, which forces lumberjack to create the directory and open the active file up front.

func ObserveWSFrame

func ObserveWSFrame(direction string, opcode byte, n int)

ObserveWSFrame records a WS message/frame size.

func RedactSensitiveKeys

func RedactSensitiveKeys(fields map[string]any, fragments []string) map[string]any

RedactSensitiveKeys returns a copy of fields with any value whose key (case-insensitive) contains a substring in fragments replaced by RedactedValue. When fragments is nil the function uses DefaultSensitiveKeyFragments. When fields is nil the function returns nil. The returned map is always a fresh allocation -- callers can safely store it without aliasing the caller's map.

This is the single centralised redaction helper used by LogAudit and by application code (see geode-ui internal/server/audit.go). Keeping it in one place ensures every channel that emits audit-style records uses the same redaction rules.

func RequestIDFrom

func RequestIDFrom(r *http.Request) string

RequestIDFrom returns the request id for r: first the id Middleware already minted/propagated on the request context (the common case for any handler running behind Middleware), then the X-Request-ID header (for callers invoked outside Middleware, e.g. direct unit tests), and only mints a new one if neither is present.

func ResolveSensitivePath added in v0.15.1

func ResolveSensitivePath(path string) (string, error)

ResolveSensitivePath canonicalizes path via filepath.EvalSymlinks and confirms the final target is a regular file. It is the single source of truth for the "an operator-supplied sensitive path must resolve to a regular file, not a directory/device/FIFO/dangling link" guard across the observability surface (N-P3-3): the OTLP TLS-material loader (pkg/obsx/otelx), the rotating on-disk log sink (NewRotatingHandler), and the generated server's/cmd/server's TLS/secret loaders (loadCertPool, readSecretFile) all call it directly.

Symlinks ARE followed by this function: a symlinked PARENT directory, or a symlinked FINAL component that itself resolves to a regular file, is fine as far as ResolveSensitivePath alone is concerned. The FINAL target must be a regular file, so a symlink resolving to a directory, device, FIFO, or socket is rejected, and a dangling symlink (or any path that does not resolve) is an error — fail closed.

Symlink-following here is INTENTIONAL for READ material: Kubernetes secret volumes, Let's Encrypt, and Docker/compose secrets all deliver TLS certificates and CA bundles through symlinks, and every pure-READ caller of this function (OTLP client TLS material, generated-server and cmd/server TLS cert/key/CA-bundle/webhook-secret loaders) relies on that. Reading the wrong file through a malicious symlink merely fails certificate parsing or signature verification — there is no echo/leak of the file's contents back to any caller, so following the link is an acceptable trade for supporting the standard secret-delivery mechanisms.

The on-disk log WRITE sink (NewRotatingHandler) is different and is guarded MORE strictly, one layer up, precisely BECAUSE unconditional symlink-following would be unsafe there: a log file is always application-created and never legitimately delivered via a secret-volume symlink, so a pre-placed symlink at a log path is always the arbitrary-write / write-redirect vector, not a legitimate delivery mechanism. NewRotatingHandler therefore Lstats the configured path itself first and unconditionally rejects a symlinked FINAL component — regardless of what it resolves to — before ever reaching this function for that path. Only once that stricter check has passed (nothing on disk yet, or the final component is a real, non-symlink file/dir/etc., possibly reached through a symlinked PARENT directory) does NewRotatingHandler fall through and call THIS function too, for the same EvalSymlinks+IsRegular canonicalization every other caller gets. A caller that must tolerate a not-yet-created path (e.g. a log file lumberjack will create fresh on first write) must Lstat first and skip this call when the path does not exist; see NewRotatingHandler.

func ResolveTLSCertKeyPaths added in v0.15.1

func ResolveTLSCertKeyPaths(cert, key string) (certResolved, keyResolved string, err error)

ResolveTLSCertKeyPaths canonicalizes an operator-supplied TLS certificate/key path PAIR via ResolveSensitivePath (EvalSymlinks + IsRegular) before they are handed to tls.LoadX509KeyPair (N-P3-3). It is the single source of truth for that pairing: every hand-written TLS/mTLS listener-construction call site in the repo (api/server.go's serverTLSConfig / serverTLSConfigWithMTLS, pkg/htpx/server.go's newListener) calls this one helper instead of each re-implementing the same two-call sequence, so a symlink cannot redirect either load to a non-regular file no matter which call site constructed the listener.

Fail closed: an unresolvable cert OR key path returns a non-nil error and BOTH resolved strings come back empty — there is no partial success where one leg resolves and the other doesn't. This is a boot-time error for the framework listeners that call it, not a silent skip. Symlinks themselves ARE followed (Kubernetes secret volumes and Let's Encrypt deliver certs this way); see ResolveSensitivePath's doc comment for the full read-path-vs-write-path rationale.

func SetAppInfo

func SetAppInfo(name, version string)

SetAppInfo configures the application identity for logging.

func SetLogTraceIDs added in v0.15.1

func SetLogTraceIDs(on bool)

SetLogTraceIDs turns trace_id/span_id enrichment of request logs on or off. Default true (on). This only gates the span-context fields (trace_id/span_id); correlation_id is always emitted when present, independent of this setting.

func SetLogger

func SetLogger(l *slog.Logger)

SetLogger configures the package-level logger. If l is nil, the call is a no-op.

func SetMetrics

func SetMetrics(m MetricsProvider)

SetMetrics configures the package-level metrics provider. If m is nil, the call is a no-op.

func SetPropagationOnly added in v0.15.1

func SetPropagationOnly(on bool)

SetPropagationOnly toggles headers-only trace-context propagation.

func SetTelemetry

func SetTelemetry(tp *TelemetryProvider)

SetTelemetry configures OTEL telemetry. Pass nil to disable. When non-nil, OTEL instruments are created eagerly.

func TelemetryEnabled

func TelemetryEnabled() bool

TelemetryEnabled returns true when a TelemetryProvider is configured.

func Tracer

func Tracer(name string) trace.Tracer

Tracer returns a named tracer. Returns a cached no-op tracer when telemetry is disabled.

A-P1.1 (2026-05-28): the fallback path used to call `tracenoop.NewTracerProvider().Tracer(name)` on every request, allocating a provider + tracer pair before returning. With telemetry off (the default deployment), the no-op tracer is the same object on every call — cache it with sync.Once and reuse. The `name` argument is intentionally ignored for the no-op tracer: tracenoop's tracer ignores name internally, so returning a single shared tracer is observationally identical.

NP-01 (2026-07): the ENABLED path (tp.TracerProvider != nil) used to call tp.TracerProvider.Tracer(name) on every request. In the OTel SDK that takes the TracerProvider's internal mutex plus a scope-map lookup on every call — fully serialized under concurrent load (measured: throughput FALLS as parallelism increases). Named tracers are cheap to keep forever (there are only a handful of fixed names — "apic.api", "apic.gql", "apic.ws", "apic.mcp" — one per generated template call site), so resolve each name once per TelemetryProvider generation and reuse it.

Do NOT hoist this resolution further, e.g. to a package-level var in the generated templates: handler registration can run before OTEL bootstrap, so a package-level capture there would permanently pin the noop tracer even after SetTelemetry configures a real provider — the same "cache the resolution, break the reconfiguration" mistake the round-3 TLS-cert-path cache made (and was reverted for). Caching HERE, inside obsx, re-checks telemetryPtr on every call and rebuilds whenever SetTelemetry installs a new *TelemetryProvider, so a later (re)configuration still takes effect immediately.

func TracingMiddleware

func TracingMiddleware(next http.Handler) http.Handler

TracingMiddleware creates an OTEL span per HTTP request with W3C trace context propagation. When telemetry is disabled (nil provider), it is a zero-cost passthrough.

func ValidCorrelationHeaderName added in v0.15.1

func ValidCorrelationHeaderName(s string) bool

ValidCorrelationHeaderName enforces the HTTP header-name grammar ^[A-Za-z][A-Za-z0-9-]{0,63}$ (an ASCII letter, then up to 63 more letters/digits/hyphens) and rejects the reserved names in reservedCorrelationHeaders. It is the single source of truth for correlation-header-name validation shared by cmd/apic's config validator, the api package's WithCorrelation option, and every generated server's Serve-time header resolution — a header name failing this check must never be trusted as a correlation-id header.

func ValidCorrelationID added in v0.15.1

func ValidCorrelationID(v string) bool

ValidCorrelationID enforces ^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$ without regexp allocation on the hot path. Inbound header values are attacker-controlled: anything failing this is replaced, never echoed, so header-splitting / log-forging payloads die here.

func VarsHandler

func VarsHandler(auth func(*http.Request) bool) http.Handler

VarsHandler returns /debug/vars gated by an auth function. If auth returns false, 401.

Types

type ExpvarMetrics

type ExpvarMetrics struct {
	// contains filtered or unexported fields
}

ExpvarMetrics implements MetricsProvider using expvar.

Per-name *expvar.Int counters are cached in a sync.Map so steady-state IncCounter / ObserveHistogram calls hit the hot path without taking any locks. The mutex is only used for the first-time publish race.

func (*ExpvarMetrics) IncCounter

func (e *ExpvarMetrics) IncCounter(name string, _ map[string]string)

func (*ExpvarMetrics) ObserveHistogram

func (e *ExpvarMetrics) ObserveHistogram(name string, value float64, _ map[string]string)

func (*ExpvarMetrics) WantsLabels added in v0.15.1

func (e *ExpvarMetrics) WantsLabels() bool

WantsLabels reports that ExpvarMetrics discards labels, so callers should not bother building a per-call label map for it.

type LogFileConfig added in v0.15.1

type LogFileConfig struct {
	// Path is the active log file path. Required. lumberjack creates the
	// parent directory and rotated siblings (<base>-<timestamp><ext>).
	Path string
	// MaxSizeMB is the size threshold (megabytes) that triggers rotation.
	MaxSizeMB int
	// MaxBackups is the number of rotated files retained.
	MaxBackups int
	// MaxAgeDays is the maximum age (days) of a retained rotated file.
	MaxAgeDays int
	// Compress gzips rotated backups when true.
	Compress bool
}

LogFileConfig configures a rotating on-disk JSON log sink backed by lumberjack. It is the file-delivery counterpart to the always-on stdout logs and the optional OTLP log export: when configured, the generated server (or an api.WithLogFile caller) fans structured log records into a size-rotated file IN ADDITION to stdout, INSIDE the same redaction wrapper so on-disk lines are redacted like every other sink.

Defaults (ApplyDefaults) mirror pkg/obsx/auditx.FileSink exactly: 100 MiB per file, 7 backups, 365-day retention. Compress is passed through unchanged (off unless the caller opts in).

func (*LogFileConfig) ApplyDefaults added in v0.15.1

func (c *LogFileConfig) ApplyDefaults()

ApplyDefaults fills non-positive size/backup/age members with the auditx FileSink defaults (100 MiB / 7 backups / 365 days). Explicit positive values are preserved; Compress is left untouched.

type MetricsProvider

type MetricsProvider interface {
	IncCounter(name string, labels map[string]string)
	ObserveHistogram(name string, value float64, labels map[string]string)
}

MetricsProvider abstracts counter and histogram metrics.

func Metrics

func Metrics() MetricsProvider

Metrics returns the current MetricsProvider.

type RedactingHandler

type RedactingHandler struct {
	// contains filtered or unexported fields
}

RedactingHandler is a slog.Handler middleware that replaces the value of any record attribute whose key matches the sensitive-key allowlist with RedactedValue, before delegating to the wrapped handler (F5). It applies the same policy as RedactSensitiveKeys / LogAudit so every log channel redacts consistently — not just LogAudit callers. slog.LogValuer values are resolved first, then group-qualified attributes and nested map values are walked recursively. Resolving at this single chokepoint — in front of the fanout — makes redaction authoritative for every downstream sink (stdout, file, and the network-egress OTLP sink), instead of leaving LogValuer resolution to each handler (the OTLP handler did not resolve, egressing secret:true struct fields in cleartext — appsec F-1 / N-P3-2).

Redaction here is key-name based: an arbitrary non-LogValuer Go struct carried as a KindAny value is NOT walked (its fields are opaque to the key matcher); rely on the type implementing slog.LogValuer (as generated secret:true structs do) to self-redact or resolve into a walkable group.

func NewRedactingHandler

func NewRedactingHandler(next slog.Handler, fragments []string) *RedactingHandler

NewRedactingHandler wraps next so attribute values under sensitive keys are redacted. When fragments is nil the DefaultSensitiveKeyFragments allowlist is used; a non-nil list REPLACES the defaults (callers wanting both should pass append(append([]string(nil), DefaultSensitiveKeyFragments...), extra...)).

func (*RedactingHandler) Enabled

func (h *RedactingHandler) Enabled(ctx context.Context, l slog.Level) bool

Enabled delegates to the wrapped handler.

func (*RedactingHandler) Handle

func (h *RedactingHandler) Handle(ctx context.Context, r slog.Record) error

Handle redacts the record's attributes then delegates.

func (*RedactingHandler) WithAttrs

func (h *RedactingHandler) WithAttrs(attrs []slog.Attr) slog.Handler

WithAttrs redacts the bound attrs then delegates so persisted attrs are also covered.

func (*RedactingHandler) WithGroup

func (h *RedactingHandler) WithGroup(name string) slog.Handler

WithGroup delegates; group qualification does not change key matching.

type TelemetryProvider

type TelemetryProvider struct {
	TracerProvider trace.TracerProvider
	MeterProvider  metric.MeterProvider
}

TelemetryProvider wraps OpenTelemetry's TracerProvider and MeterProvider. Deployers configure SDK exporters externally; apic uses the OTEL API only.

func Telemetry

func Telemetry() *TelemetryProvider

Telemetry returns the current TelemetryProvider, or nil if disabled.

Directories

Path Synopsis
Code generated by apic; DO NOT EDIT.
Code generated by apic; DO NOT EDIT.
Code generated by apic; DO NOT EDIT.
Code generated by apic; DO NOT EDIT.
Package promx implements obsx.MetricsProvider on top of prometheus/client_golang, preserving labels (the expvar default discards them) with a per-metric series-cardinality cap, and exposes the scrape handler mounted by generated servers at the authenticated /metrics endpoint.
Package promx implements obsx.MetricsProvider on top of prometheus/client_golang, preserving labels (the expvar default discards them) with a per-metric series-cardinality cap, and exposes the scrape handler mounted by generated servers at the authenticated /metrics endpoint.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL