Documentation
¶
Overview ¶
Package observe provides health, readiness and build-info endpoints for Go services, standard library only.
It owns the operational-status model so every service exposes the same /healthz, /readyz and build-info shape without copy-pasted handlers. It is deliberately not a metrics/tracing platform: no Prometheus client, no OpenTelemetry SDK.
Health vs readiness ¶
Health is process-level liveness: it answers "is the process up" and is always ok. Readiness runs the registered dependency checks (database, cache, queue, ...) and fails when any dependency is unusable, so a load balancer can stop routing traffic.
obs := observe.New(
observe.WithBuildInfo(observe.BuildInfo{Service: "api", Version: "v1.2.3"}),
// Error details are redacted by default; opt in with
// observe.WithErrorRedaction(false) for local debugging.
)
obs.Check("postgres", func(ctx context.Context) error { return pool.Ping(ctx) })
obs.Check("redis", func(ctx context.Context) error { return rdb.Ping(ctx) })
mux.Handle("/healthz", obs.HealthHandler()) // 200 while the process lives
mux.Handle("/readyz", obs.ReadyHandler()) // 503 if a dependency is down
mux.Handle("/buildz", obs.BuildHandler())
Decoupling ¶
observe never imports the lifecycle kernel. To surface application lifecycle state, register it as an ordinary check over the kernel's status snapshot:
obs.Check("app", func(context.Context) error {
if !a.Status().Healthy() {
return errors.New("a component failed")
}
return nil
})
See DOC.md (English) and DOC.UK.md (Ukrainian) for the full reference.
Index ¶
- type BuildInfo
- type Check
- type CheckResult
- type Option
- type Registry
- func (r *Registry) Build() BuildInfo
- func (r *Registry) BuildHandler() http.Handler
- func (r *Registry) Check(name string, fn Check)
- func (r *Registry) Health(context.Context) Status
- func (r *Registry) HealthHandler() http.Handler
- func (r *Registry) Ready(ctx context.Context) Status
- func (r *Registry) ReadyHandler() http.Handler
- type Status
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type BuildInfo ¶
type BuildInfo struct {
Service string `json:"service,omitempty"`
Version string `json:"version,omitempty"`
Commit string `json:"commit,omitempty"`
BuiltAt string `json:"built_at,omitempty"`
}
BuildInfo identifies the running build. It is surfaced in health and readiness responses and by BuildHandler. Populate it at build time with -ldflags, or from runtime/debug.ReadBuildInfo.
type Check ¶
Check reports whether a dependency is usable. It should be fast and honour the context deadline. A nil error means healthy.
type CheckResult ¶
type CheckResult struct {
Status string `json:"status"`
LatencyMS int64 `json:"latency_ms"`
Error string `json:"error,omitempty"`
}
CheckResult is the outcome of a single dependency check.
type Option ¶
type Option func(*Registry)
Option configures a Registry at construction time.
func WithBuildInfo ¶
WithBuildInfo sets the build metadata. If the service name is not set separately, it is taken from the build info.
func WithCheckTimeout ¶
WithCheckTimeout sets the per-check deadline used by Ready. A non-positive value disables the per-check timeout (the request context still applies).
func WithErrorRedaction ¶
WithErrorRedaction controls whether check error details appear in responses; the failing status is always reported either way. Redaction is ON by default so internals never leak by accident. Pass false to expose details for local debugging - not in production.
func WithService ¶
WithService sets the service name reported in responses.
type Registry ¶
type Registry struct {
// contains filtered or unexported fields
}
Registry holds health/readiness checks and build info. It owns the health model: liveness (Health) is process-level and always ok; readiness (Ready) runs the registered dependency checks. It is safe for concurrent use.
Example ¶
ExampleRegistry wires health, readiness and build endpoints onto a mux.
package main
import (
"context"
"fmt"
"net/http"
"github.com/goloop/observe"
)
func main() {
obs := observe.New(
observe.WithBuildInfo(observe.BuildInfo{Service: "api", Version: "v1.2.3"}),
observe.WithErrorRedaction(true),
)
// Register dependency checks (database, cache, ...).
obs.Check("cache", func(context.Context) error { return nil })
mux := http.NewServeMux()
mux.Handle("/healthz", obs.HealthHandler())
mux.Handle("/readyz", obs.ReadyHandler())
mux.Handle("/buildz", obs.BuildHandler())
st := obs.Ready(context.Background())
fmt.Println(st.Status, st.Service, st.Version)
}
Output: ok api v1.2.3
Example (LifecycleBridge) ¶
ExampleRegistry_lifecycleBridge shows registering an external lifecycle snapshot as an ordinary check, so observe stays decoupled from the kernel that produced it.
package main
import (
"context"
"errors"
"fmt"
"github.com/goloop/observe"
)
func main() {
obs := observe.New()
// healthyFromKernel would come from your lifecycle kernel's status.
healthyFromKernel := func() bool { return true }
obs.Check("app", func(context.Context) error {
if !healthyFromKernel() {
return errors.New("a component failed")
}
return nil
})
fmt.Println(obs.Ready(context.Background()).OK())
}
Output: true
func (*Registry) BuildHandler ¶
BuildHandler serves the build info as JSON with HTTP 200.
func (*Registry) Check ¶
Check registers or replaces a readiness check. A nil function or an empty name is ignored (an empty name would produce a blank JSON key).
func (*Registry) Health ¶
Health returns process-level liveness. It never runs dependency checks and is always ok: it answers "is the process up", not "are dependencies usable".
func (*Registry) HealthHandler ¶
HealthHandler serves liveness as JSON with HTTP 200. It does not run dependency checks: it answers whether the process is up. Mount it at /healthz (or your convention).
func (*Registry) Ready ¶
Ready runs all registered checks (in parallel, each bounded by the check timeout) and returns the aggregated status: error if any check fails.
func (*Registry) ReadyHandler ¶
ReadyHandler serves readiness as JSON. It runs the registered dependency checks and returns HTTP 200 when all pass, or HTTP 503 when any fails, so a load balancer can stop routing traffic. Mount it at /readyz (or your convention).