Documentation
¶
Overview ¶
Package bootstrap solves the repetitive, error-prone problem of wiring together the core infrastructure of a Go service: context lifecycle, structured logging, metrics collection, OS signal handling, and graceful shutdown — all with a single function call.
Problem ¶
Every long-running Go service needs the same boilerplate: create a context, configure a logger, initialize metrics, wire up components, listen for SIGTERM, and coordinate a clean shutdown without dropping in-flight work. Doing this correctly — with timeouts, WaitGroups, and proper signal handling — is repetitive and easy to get wrong. This package encapsulates that pattern once, tested and production-ready.
How It Works ¶
The entry point is Bootstrap. It accepts a BindFunc — the caller-supplied function that wires up all application-specific components — plus a variadic list of Option values that tune the runtime behavior:
- A cancellable context.Context is created and threaded through the entire application via BindFunc.
- A metrics.Client is created (Prometheus by default) and passed to BindFunc.
- A *slog.Logger is created and passed to BindFunc. If a logutil.Config is provided with WithLogConfig, the logger automatically emits a metrics counter for every log line, broken down by level — giving instant observability into error rates.
- BindFunc is called. This is where the caller registers HTTP servers, database connections, background workers, etc.
- Bootstrap blocks until it receives os.Interrupt (SIGINT), SIGTERM, or until the context is canceled externally.
- A shutdown signal is broadcast on the shared channel (see WithShutdownSignalChan) and the application context is canceled, so every registered dependent — whether keyed on the channel or on ctx.Done() — can start its own teardown.
- Bootstrap waits for all dependants to finish via a sync.WaitGroup (see WithShutdownWaitGroup), bounded by a configurable timeout (see WithShutdownTimeout) to prevent hanging indefinitely. If the timeout fires first, Bootstrap returns an error wrapping ErrShutdownTimeout.
- The metrics client is closed so buffered measurements are flushed before the process exits.
Key Features ¶
- Single-function API: one call handles the entire lifecycle.
- Functional options pattern: zero mandatory configuration; override only what you need via WithContext, WithLogConfig, WithLogger, WithCreateLoggerFunc, WithCreateMetricsClientFunc, WithShutdownTimeout, WithShutdownWaitGroup, and WithShutdownSignalChan.
- Automatic log-level metrics: when a logutil.Config is supplied, every log emission increments a labeled counter, enabling SLO-style alerting on error log rates without extra instrumentation.
- Graceful shutdown with timeout: dependants (HTTP servers, consumers, …) communicate completion through a shared sync.WaitGroup; Bootstrap honors a deadline so a stuck goroutine can never block the process forever.
- Testable by design: WithContext lets tests inject a cancellable context to drive the shutdown path without sending real OS signals.
Notes ¶
Bootstrap installs process-global OS signal handling via os/signal.Notify, so it is intended to be called once per process (for example from main) and not concurrently with another Bootstrap call in the same process. Supplying a logutil.Config with WithLogConfig also replaces the process-wide default logger (via slog.SetDefault) and redirects the standard library log package.
Usage ¶
Wire your application in a BindFunc and pass it to Bootstrap:
func bind(ctx context.Context, l *slog.Logger, m metrics.Client) error {
// register HTTP servers, workers, DB connections, etc.
return nil
}
func main() {
shutdownWG := &sync.WaitGroup{}
shutdownCh := make(chan struct{})
err := bootstrap.Bootstrap(
bind,
bootstrap.WithLogConfig(logutil.DefaultConfig()),
bootstrap.WithShutdownTimeout(30*time.Second),
bootstrap.WithShutdownWaitGroup(shutdownWG),
bootstrap.WithShutdownSignalChan(shutdownCh),
)
if err != nil {
log.Fatal(err)
}
}
For a complete, runnable implementation see — in order:
- examples/service/cmd/main.go
- examples/service/internal/cli/cli.go
- examples/service/internal/cli/bind.go
Index ¶
- Variables
- func Bootstrap(bindFn BindFunc, opts ...Option) error
- type BindFunc
- type CreateLoggerFunc
- type CreateMetricsClientFunc
- type Option
- func WithContext(ctx context.Context) Option
- func WithCreateLoggerFunc(fn CreateLoggerFunc) Option
- func WithCreateMetricsClientFunc(fn CreateMetricsClientFunc) Option
- func WithLogConfig(c *logutil.Config) Option
- func WithLogger(l *slog.Logger) Option
- func WithShutdownSignalChan(ch chan struct{}) Option
- func WithShutdownTimeout(timeout time.Duration) Option
- func WithShutdownWaitGroup(wg *sync.WaitGroup) Option
Constants ¶
This section is empty.
Variables ¶
var ( // ErrNilBindFunc is returned when Bootstrap is called with a nil BindFunc. ErrNilBindFunc = errors.New("bindFn is required") // ErrNilContext is returned when the application context is nil. ErrNilContext = errors.New("context is required") // ErrNilCreateLoggerFunc is returned when the logger factory is nil. ErrNilCreateLoggerFunc = errors.New("createLoggerFunc is required") // ErrNilLogConfig is returned when WithLogConfig was used with a nil logutil.Config. ErrNilLogConfig = errors.New("logConfig is required when using WithLogConfig") // ErrNilCreateMetricsClientFunc is returned when the metrics client factory is nil. ErrNilCreateMetricsClientFunc = errors.New("createMetricsClientFunc is required") // ErrInvalidShutdownTimeout is returned when the shutdown timeout is not positive. ErrInvalidShutdownTimeout = errors.New("invalid shutdownTimeout") // ErrNilShutdownWaitGroup is returned when the shutdown WaitGroup is nil. ErrNilShutdownWaitGroup = errors.New("shutdownWaitGroup is required") // ErrNilShutdownSignalChan is returned when the shutdown signal channel is nil. ErrNilShutdownSignalChan = errors.New("shutdownSignalChan is required") // ErrShutdownTimeout is wrapped into the error returned by Bootstrap when the // registered dependants do not finish within the configured shutdown timeout. ErrShutdownTimeout = errors.New("graceful shutdown timed out") )
Exported sentinel errors returned by Bootstrap and its configuration validation so callers can match them with errors.Is.
Functions ¶
func Bootstrap ¶
Bootstrap initializes core service infrastructure and manages process lifecycle.
It solves the repeated startup/shutdown orchestration problem by centralizing context setup, logger and metrics creation, application binding, signal handling, and graceful termination in one call.
The function applies options, validates configuration, invokes bindFn to wire dependants, waits for shutdown signals, broadcasts a shutdown event to all listeners, then waits for dependant completion up to the configured timeout.
It returns a wrapped error when configuration is invalid, when metrics or application binding fails, or — wrapping ErrShutdownTimeout — when dependants do not finish within the configured shutdown timeout. All returned errors are matchable with errors.Is.
The main benefit is predictable service lifecycle behavior with less boilerplate and fewer shutdown edge-case bugs.
Types ¶
type BindFunc ¶
BindFunc wires application components using the prepared context, logger, and metrics client.
type CreateLoggerFunc ¶
CreateLoggerFunc constructs the root logger used by Bootstrap.
type CreateMetricsClientFunc ¶
CreateMetricsClientFunc constructs the metrics backend used by Bootstrap.
type Option ¶
type Option func(*config)
Option configures Bootstrap runtime behavior.
func WithContext ¶
WithContext overrides the root application context used by Bootstrap.
This is especially useful in tests and controlled runtime environments where cancellation and deadlines are managed externally.
func WithCreateLoggerFunc ¶
func WithCreateLoggerFunc(fn CreateLoggerFunc) Option
WithCreateLoggerFunc injects a custom logger factory.
It should be used as an alternative to WithLogConfig and WithLogger when a fresh logger instance must be created lazily at bootstrap time.
func WithCreateMetricsClientFunc ¶
func WithCreateMetricsClientFunc(fn CreateMetricsClientFunc) Option
WithCreateMetricsClientFunc overrides metrics client creation.
Use it to plug in a custom metrics backend while keeping Bootstrap lifecycle behavior unchanged.
func WithLogConfig ¶
WithLogConfig configures logger creation from a logutil.Config.
It should be used as an alternative to WithLogger and WithCreateLoggerFunc. The benefit is centralized logger policy with optional hooks (including log-level metrics wiring performed by Bootstrap).
As a side effect of building the logger from the config, Bootstrap replaces the process-wide default logger (via slog.SetDefault) and redirects the standard library log package's output through it.
func WithLogger ¶
WithLogger injects a prebuilt root logger instance.
It should be used as an alternative to WithLogConfig and WithCreateLoggerFunc when logger construction is owned by the caller.
func WithShutdownSignalChan ¶
func WithShutdownSignalChan(ch chan struct{}) Option
WithShutdownSignalChan sets the shared broadcast channel for shutdown start.
Bootstrap closes this channel when shutdown begins. Dependants can watch this channel to trigger their own graceful termination logic.
The channel must be single-use: Bootstrap closes it exactly once, so it must not be reused across multiple Bootstrap calls nor closed by the caller. Reusing or pre-closing it makes the close panic.
func WithShutdownTimeout ¶
WithShutdownTimeout sets the maximum graceful-shutdown wait duration.
Bootstrap returns after this timeout even if dependant goroutines have not completed, preventing indefinite process hangs. When the timeout fires before dependants finish, Bootstrap returns an error wrapping ErrShutdownTimeout.
func WithShutdownWaitGroup ¶
WithShutdownWaitGroup sets the shared WaitGroup used to track dependant shutdown.
On shutdown, Bootstrap waits until wg reaches zero or the configured timeout expires. Dependants (for example HTTP servers) should Add before starting and Done when teardown is complete.