bootstrap

bootstrap wires context, logging, metrics, signal handling and bounded graceful shutdown into one call, with no metrics backend pulled in by default.

Part of nurago, a collection of independent Go packages for backend services.

import "github.com/tecnickcom/nurago/pkg/bootstrap"

One call that builds the context, logger and metrics client, hands them to your wiring function, then blocks until a shutdown signal and waits for registered workers to finish.

err := bootstrap.Bootstrap(bind,
    bootstrap.WithLogConfig(logutil.DefaultConfig()),
    bootstrap.WithShutdownTimeout(30*time.Second),
    bootstrap.WithShutdownWaitGroup(shutdownWG),
    bootstrap.WithShutdownSignalChan(shutdownCh),
)

The sequence

  1. A cancellable context is created and threaded through everything.
  2. A metrics.Client is created.
  3. A *slog.Logger is created.
  4. Your BindFunc is called with all three. This is where servers, database connections and workers are registered.
  5. Bootstrap blocks until SIGINT, SIGTERM, or external context cancellation.
  6. The shutdown signal is broadcast on the shared channel and the context is cancelled.
  7. It waits on the shared sync.WaitGroup, bounded by a timeout.

Your code appears once, at step 4, and receives the three things every component needs rather than constructing them itself or reaching for globals.

No metrics backend is pulled in

The default client is metrics.Default, the no-op implementation, so bootstrap imports no metrics backend of its own.

That keeps the dependency graph honest. A package that defaulted to Prometheus would put the Prometheus client library in the build of every service that used bootstrap, whether or not it wanted it. WithCreateMetricsClientFunc supplies a real one, and the weight arrives only with the choice. See /docs/dependency-footprint/.

When a logutil.Config is supplied through WithLogConfig, the logger emits a metrics counter per log line broken down by level, so error rates are derived from what was actually logged.

Two shutdown mechanisms, because components differ

Shutdown broadcasts on a channel and cancels the context, and both are needed.

Context cancellation suits anything already accepting a context: an in-flight request, a database query, an outbound call. Those observe ctx.Done() and stop.

A channel suits a component that must run after the context is cancelled. An HTTP server draining in-flight requests cannot use the cancelled context to do it, because the requests it is draining are the ones that context would kill.

Broadcasting on both lets each component pick whichever is correct for it.

The timeout is the important setting

WithShutdownTimeout bounds the wait on the waitgroup.

Without a bound, one worker that never finishes hangs shutdown forever. In a container, the orchestrator then sends SIGKILL after its own grace period, which kills in-flight requests, skips buffered telemetry flushes, and turns a clean deployment into a rolling set of errors.

The relationship to set deliberately: the bootstrap timeout must be shorter than the platform’s termination grace period, or the platform kills the process before the timeout can do its job. It must be longer than the HTTP server’s own shutdown timeout, or it gives up while the server is still draining correctly.

WithShutdownWaitGroup is what components register with. A periodic task, a queue consumer, or a background worker adds itself so shutdown waits for it rather than only for HTTP.

What it does not do

No dependency injection container, no plugin system, no service registry, and no framework your handlers implement. BindFunc is a plain function; what it builds and how is entirely yours.

Dependencies

This package reaches no external module: it uses only the Go standard library, plus metrics and logutil.