obs

package
v0.2.0 Latest Latest
Warning

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

Go to latest
Published: Aug 7, 2026 License: MIT Imports: 15 Imported by: 0

Documentation

Overview

Package obs is the instrumentation tier: ECS-shaped structured logging and a standard-library metrics registry rendered in Prometheus text format.

Nothing here changes what the service does. Logging goes to stdout as JSON so a log shipper (Filebeat / Elastic Agent) can pick it straight off the container, with a text mode for local runs — see docs/OBSERVABILITY.md.

Index

Constants

This section is empty.

Variables

View Source
var (
	// NATSUp separates "the distribution plane is down" from "the process is
	// down". A NATS outage no longer stops the service booting, so without a
	// gauge the degraded start is visible only in a log line at startup.
	NATSUp = NewGauge("centralconfig_nats_up",
		"1 when the KV buckets are provisioned and NATS is connected, 0 otherwise.")
	KVPublishAttempts = NewCounter("centralconfig_kv_publish_attempts_total",
		"KV publish attempts, by bucket.")
	KVPublishSuccess = NewCounter("centralconfig_kv_publish_success_total",
		"KV publishes that reached JetStream, by bucket.")
	KVPublishFailures = NewCounter("centralconfig_kv_publish_failures_total",
		"KV publishes that failed and were left to the reconciler, by bucket.")
	KVPublishSkipped = NewCounter("centralconfig_kv_publish_skipped_total",
		"KV publishes skipped because the stored value was already identical, by bucket.")
	KVDeleteFailures = NewCounter("centralconfig_kv_delete_failures_total",
		"KV key deletions that failed, by bucket.")
)

KV distribution. A publish failure is the drift the reconciler exists to heal; it is silent from the caller's side, so it has to be countable.

View Source
var (
	ReconcileCycles = NewCounter("centralconfig_reconcile_cycles_total",
		"Reconcile cycles completed, by sweep (full/incremental) and result.")
	ReconcileDuration = NewHistogram("centralconfig_reconcile_duration_seconds",
		"Wall time of a reconcile cycle, by sweep.")
	ReconcileKeysRepublished = NewCounter("centralconfig_reconcile_keys_republished_total",
		"Keys republished from the database to KV, by source.")
	ReconcileKeysPruned = NewCounter("centralconfig_reconcile_keys_pruned_total",
		"KV keys deleted because their database row is gone, by bucket.")
	// ReconcilePruneRefused fires when a sweep proposed deleting more of a
	// bucket than the ceiling allows. It means KV and the database disagree
	// wholesale, which is worth an alert on the first occurrence.
	ReconcilePruneRefused = NewCounter("centralconfig_reconcile_prune_refused_total",
		"Prune passes refused because they would have deleted too much of a bucket, by bucket.")
	ReconcileSourceFailures = NewCounter("centralconfig_reconcile_source_failures_total",
		"Reconcile sources that failed to resync, by source.")
	ReconcileLastSuccess = NewGauge("centralconfig_reconcile_last_success_timestamp_seconds",
		"Unix time of the last reconcile cycle in which every source succeeded.")
)

Reconciler. Its whole job is to converge KV on the database, so what matters is that cycles keep completing and how much drift each one had to repair.

View Source
var (
	HTTPRequests = NewCounter("centralconfig_http_requests_total",
		"HTTP requests, by method, matched route and status code.")
	HTTPDuration = NewHistogram("centralconfig_http_request_duration_seconds",
		"HTTP request duration, by method and matched route.")
	HTTPPanics = NewCounter("centralconfig_http_panics_total",
		"Requests whose handler panicked.")
)

HTTP admin API.

View Source
var (
	DBUp = NewGauge("centralconfig_db_up",
		"1 when the last health-check ping succeeded, 0 otherwise.")
	DBPingFailures = NewCounter("centralconfig_db_ping_failures_total",
		"Health-check database pings that failed.")
)

Database — PostgreSQL in production, SQLite in the local stack.

Functions

func Err

func Err(err error) slog.Attr

Err renders an error as ECS fields. Attach it rather than formatting the error into the message, so failures can be aggregated by type in Kibana.

func FromContext

func FromContext(ctx context.Context, component string) *slog.Logger

FromContext returns the request's logger, or the component logger when the call did not come from a request (the reconciler, startup).

func Handler

func Handler() http.Handler

Handler serves the registry in Prometheus text exposition format.

func Logger

func Logger(component string) *slog.Logger

Logger returns the logger for a component, tagged with its event.dataset. It is safe to call from a package-level var: the returned logger follows Setup.

func RequestLogger

func RequestLogger(requestID string) *slog.Logger

RequestLogger returns a logger carrying the request correlation id and nothing else: the component's event.dataset is added by FromContext at the log site, so it appears exactly once.

func Setup

func Setup(o LogOptions)

Setup installs the configured handler and makes it the slog default. It is called once, from the app layer, before anything else is wired.

func Stack

func Stack(buf []byte) slog.Attr

Stack carries a captured goroutine stack. Only the panic handler has one.

func StdLogger

func StdLogger(component string, level slog.Level) *log.Logger

StdLogger adapts the active handler to *log.Logger, for the standard-library APIs that still insist on one (http.Server.ErrorLog).

func WithLogger

func WithLogger(ctx context.Context, l *slog.Logger) context.Context

WithLogger returns a context carrying the request's logger. The access-log middleware puts one in; handlers and services read it back with FromContext so their lines carry the same http.request.id.

Types

type Counter

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

Counter is a monotonic counter, optionally broken down by labels. Values live in an expvar.Map keyed by the rendered label set, which keeps the increment path lock-free per series.

func NewCounter

func NewCounter(name, help string) *Counter

func (*Counter) Add

func (c *Counter) Add(n int64, labels ...string)

Add increments the series identified by the given label key/value pairs.

func (*Counter) Inc

func (c *Counter) Inc(labels ...string)

Inc adds one.

func (*Counter) Value

func (c *Counter) Value(labels ...string) int64

Value reports a series' current total. Only tests need it.

type Gauge

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

Gauge is a single value that goes up and down (database reachable, time of the last successful reconcile).

func NewGauge

func NewGauge(name, help string) *Gauge

func (*Gauge) Set

func (g *Gauge) Set(f float64)

func (*Gauge) SetBool

func (g *Gauge) SetBool(ok bool)

SetBool records a state flag as the 1/0 an alert expression can compare.

func (*Gauge) Value

func (g *Gauge) Value() float64

Value reports the current value. Only tests need it.

type Histogram

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

Histogram is a Prometheus histogram: bucket counts plus sum and count, so quantiles can be computed at query time.

func NewHistogram

func NewHistogram(name, help string) *Histogram

func (*Histogram) Observe

func (h *Histogram) Observe(seconds float64, labels ...string)

Observe records one measurement, in seconds.

type LogOptions

type LogOptions struct {
	Level   string    // debug | info | warn | error (default info)
	Format  string    // json (ECS) | text (local development)
	Service string    // service.name
	Version string    // service.version
	Output  io.Writer // defaults to os.Stdout
}

LogOptions is the logging configuration, read from the environment by the app layer. Zero values fall back to the ECS/JSON production shape.

Jump to

Keyboard shortcuts

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