Systems · Developer Tools
signal-kit
A focused Rust library for bootstrapping OpenTelemetry observability in async services.
Active · 2026
Rust · Tokio · OpenTelemetry

signal-kit is a small Rust library for bootstrapping production-grade observability in async services.
It exists to solve a familiar problem: every serious backend service eventually needs tracing, metrics, structured logging, exporter configuration, runtime metadata, and shutdown handling. The OpenTelemetry ecosystem gives you the building blocks, but wiring them together correctly can still become repetitive, fragile, and easy to get subtly wrong.
signal-kit packages that setup into a focused, reusable crate. It gives services a clean way to initialise OpenTelemetry-backed tracing and metrics, configure exporters, set up structured logs, and keep observability behaviour consistent across a workspace.
Why I built it
When building Rust services, I found myself repeating the same observability setup across projects: configuring tracing, wiring OpenTelemetry exporters, deciding how environment variables should override defaults, making sure metrics and traces behave consistently, and handling guards so telemetry is flushed properly on shutdown.
None of that code is usually core business logic, but it is critical infrastructure. It needs to be reliable, boring, explicit, and easy to reuse.
signal-kit is my attempt to turn that repeated bootstrap layer into a proper library: small enough to understand, opinionated enough to be useful, and configurable enough to fit different services without becoming a framework.
What it does
At its core, signal-kit provides helpers for initialising observability in Tokio-based Rust services.
It supports:
- Initialising a tracing subscriber wired to OpenTelemetry
- Configuring OpenTelemetry Protocol exporters
- Setting up traces and metrics with consistent defaults
- Producing structured JSON logs or readable local output
- Optional file-based logging with rotation and retention
- Environment-driven configuration for deployment flexibility
- Higher-level builder APIs for services that want explicit configuration in code
The design goal is not to hide OpenTelemetry completely. Instead, signal-kit removes the boilerplate around the most common setup path while still keeping the lifecycle and configuration model visible.
File logging
One of the more practical features is file-based logging with rotation and retention.
This is useful for local development, debugging, and services where retaining recent logs on disk is still valuable alongside centralised telemetry. The crate can write JSON-formatted logs to files, rotate them daily or hourly, and clean up old files based on a retention policy.
The cleanup logic is deliberately conservative. It only removes files matching the expected log filename pattern, which avoids accidentally deleting unrelated files in the same directory. The file logging path is also fail-fast: if logging is explicitly enabled but the file cannot be written, the service fails rather than silently losing logs.
That trade-off is intentional. Observability should not pretend to work when it is broken.
Design principles
signal-kit is built around a few principles.
The first is that observability setup should be reusable. A service workspace should not need five slightly different copies of the same tracing and metrics bootstrap code.
The second is that defaults should be opinionated, but not magical. A service should be able to get sensible behaviour quickly, while still being able to override configuration through environment variables or builder-style APIs.
The third is that lifecycle semantics matter. Telemetry initialisation is not just a one-line setup call; exporters, guards, flushing, and shutdown behaviour need to be treated as part of the service contract.
The final principle is that a library like this should stay narrow. It should initialise observability plumbing, not become an application framework or a dumping ground for domain-specific metrics.
Technical details
The crate is written in Rust and designed for async services using Tokio.
It builds on the Rust tracing ecosystem and OpenTelemetry, with support for OpenTelemetry Protocol export. The public API is centred around simple initialisation helpers and a higher-level ObservabilityBuilder, allowing services to choose between convention-based setup and explicit configuration.
Configuration can come from code or environment variables. This makes the crate useful both for local development and deployed services, where exporter endpoints, log formats, rotation settings, and service metadata are often provided externally.
The repository is structured as a Rust workspace, with signal-kit living under src-crates/signal-kit. That keeps the crate isolated while leaving room for related crates, examples, or applications to be added later.
What I learned
This project sharpened a few design instincts around Rust infrastructure libraries.
The main lesson was that “simple initialisation” code often hides real design complexity. Observability touches application lifecycle, error handling, deployment configuration, local developer experience, and production operations. A good abstraction cannot just wrap everything in a convenience function; it has to make the important behaviour explicit.
It also reinforced the importance of keeping public APIs narrow before open-sourcing a crate. Internal utilities often grow around one codebase’s needs. Turning them into a general-purpose library means removing domain assumptions, tightening feature flags, documenting lifecycle guarantees, and making configuration behaviour predictable.
Why it matters
Good observability is one of the foundations of reliable services, but it is easy for teams to treat the setup as incidental boilerplate.
signal-kit makes that setup deliberate. It gives Rust services a consistent entry point for traces, metrics, and logs, so each service can start from the same reliable baseline instead of reinventing the same plumbing.
The result is a small library with a narrow job: make production-grade observability easier to initialise, easier to configure, and harder to forget.