bootstrap

bootstrap wires together the core infrastructure of a Go service: context lifecycle, structured logging, metrics collection, OS signal handling, and graceful shutdown, in a single function call.

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

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

Package bootstrap wires together the core infrastructure of a Go service: context lifecycle, structured logging, metrics collection, OS signal handling, and graceful shutdown, in a single function call.

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:

  1. A cancellable context.Context is created and threaded through the entire application via BindFunc.
  2. A metrics.Client is created and passed to BindFunc. The default is metrics.Default, the no-op client, so bootstrap pulls in no metrics backend of its own; WithCreateMetricsClientFunc supplies a real one.
  3. A [*slog.Logger] is created and passed to BindFunc. If a logutil.Config is provided with WithLogConfig, the logger emits a metrics counter for every log line, broken down by level.
  4. BindFunc is called. This is where the caller registers HTTP servers, database connections, background workers, etc.
  5. Bootstrap blocks until it receives os.Interrupt (SIGINT), SIGTERM, or until the context is canceled externally.
  6. 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.
  7. 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.
  8. The metrics client is closed so buffered measurements are flushed before the process exits.

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

When To Use

  • A service’s main should be short and the lifecycle wiring consistent across services.
  • Shutdown must wait for background workers, bounded by a timeout.
  • Log records should increment a metrics counter per level.

Example

// A real service lets Bootstrap block on SIGINT or SIGTERM. The example
// cancels the context from inside the bind function so it terminates.
ctx, cancel := context.WithCancel(context.TODO())
defer cancel()

var wg sync.WaitGroup

shutdown := make(chan struct{})

// BindFunc is where the application registers its own components: HTTP
// servers, database connections, background workers. It receives the
// application context, logger, and metrics client already wired.
bindFn := func(ctx context.Context, _ *slog.Logger, mtr metrics.Client) error {
	worker(ctx, &wg, shutdown)

	mtr.IncErrorCounter("startup", "bind", "0")

	fmt.Println("application wired")

	// Stand-in for the signal that would normally end the process.
	cancel()

	return nil
}

// Bootstrap returns once every registered dependant has finished, or
// with an error wrapping ErrShutdownTimeout if they exceed the budget.
err := bootstrap.Bootstrap(
	bindFn,
	bootstrap.WithContext(ctx),
	bootstrap.WithShutdownSignalChan(shutdown),
	bootstrap.WithShutdownWaitGroup(&wg),
	bootstrap.WithShutdownTimeout(5*time.Second),
	bootstrap.WithCreateMetricsClientFunc(func() (metrics.Client, error) {
		return &metrics.Default{}, nil
	}),
)

fmt.Println("bootstrap returned:", err)

// Output:
// application wired
// worker stopped
// bootstrap returned: <nil>

Full source is in example_bootstrap_test.go. More runnable examples are on pkg.go.dev.

Dependencies

Importing this package pulls 4 external modules:

  • github.com/mattn/go-colorable
  • github.com/mattn/go-isatty
  • github.com/rs/zerolog
  • golang.org/x/sys