telemetry_init/lib.rs
1//! One-call observability bootstrap for Rust services.
2//!
3//! `telemetry-init` replaces the estate's hand-wired
4//! `tracing_subscriber` + `otelkit` + `metrics-kit` init blocks (~40–80
5//! lines each, six dialects) with a single fallible call that wires, in
6//! estate-default shape:
7//!
8//! - **Logs** — `tracing_subscriber` with an `EnvFilter` (the configured
9//! directive, overridden by `RUST_LOG`) and a `fmt` layer: single-line
10//! JSON by default, human format for local dev.
11//! - **Metrics** — a lock-free
12//! [`metrics_kit::Registry`] with a cardinality budget, handed to you
13//! as an [`Arc`] so hot-path handles register through it (feature
14//! `metrics`, default on).
15//! - **Traces** — an `opentelemetry-otlp` exporter wired as a
16//! `tracing-opentelemetry` layer with `service.name`/`service.version`
17//! resource attributes and parent-based ratio sampling (feature
18//! `otlp`, default off).
19//!
20//! # Design
21//!
22//! - **One init, typed failure.** [`Telemetry::init`] returns
23//! [`TelemetryError`]; double-initialization — the global subscriber is
24//! a once-per-process resource — is [`TelemetryError::AlreadyInitialized`],
25//! never a panic.
26//! - **No hidden globals for metrics.** The registry is returned to the
27//! caller as an [`Arc`] (the metrics-kit pattern). Only the tracing
28//! subscriber is global, because that is tracing's own design.
29//! - **Explicit shutdown, best-effort drop.** [`Telemetry::shutdown`] is
30//! the guaranteed flush path and idempotent; [`Drop`] does a
31//! best-effort flush in case teardown is forgotten.
32//! - **No silent fallbacks.** Requesting [`LogFormat::Json`] without the
33//! `json` feature is a configuration error, not quiet plain text.
34//!
35//! # Example
36//!
37//! ```
38//! use telemetry_init::{LogFormat, Telemetry, TelemetryConfig};
39//!
40//! let telemetry = Telemetry::init(
41//! TelemetryConfig::new("payments-api")
42//! .version(env!("CARGO_PKG_VERSION"))
43//! .log_format(LogFormat::Pretty)
44//! .log_level("info"),
45//! )?;
46//!
47//! tracing::info!("service started");
48//!
49//! telemetry.shutdown()?;
50//! # Ok::<(), telemetry_init::TelemetryError>(())
51//! ```
52//!
53//! # Metrics
54//!
55//! With the default `metrics` feature, [`Telemetry::metrics`] hands back
56//! the registry for hot-path registration:
57//!
58//! ```
59//! # #[cfg(feature = "metrics")]
60//! # {
61//! use telemetry_init::{Telemetry, TelemetryConfig};
62//!
63//! let telemetry = Telemetry::init(
64//! TelemetryConfig::new("payments-api").metrics_budget(4096),
65//! )?;
66//!
67//! let requests = telemetry
68//! .metrics()
69//! .counter("http_requests_total", "Total HTTP requests.", &[])
70//! .expect("unique series name");
71//! requests.inc();
72//!
73//! assert!(telemetry.metrics().render().contains("http_requests_total 1"));
74//! telemetry.shutdown()?;
75//! # }
76//! # Ok::<(), telemetry_init::TelemetryError>(())
77//! ```
78//!
79//! # Traces (feature `otlp`)
80//!
81//! ```rust,ignore
82//! let telemetry = Telemetry::init(
83//! TelemetryConfig::new("payments-api")
84//! .otlp_endpoint("http://localhost:4317")
85//! .sample_rate(0.1),
86//! )?;
87//! ```
88//!
89//! # Why not `otelkit::init`?
90//!
91//! `otelkit` v2's public API is a whole-subscriber init: `otelkit::init`
92//! installs its own global subscriber and returns a flush guard. It
93//! cannot compose as a layer inside this crate's single subscriber, and
94//! its OTLP path ignores the log format and `RUST_LOG`. telemetry-init
95//! therefore wires `opentelemetry-otlp` + `tracing-opentelemetry`
96//! directly, keeping one code path for every feature combination.
97//!
98//! # Feature flags
99//!
100//! | Feature | Default | Description |
101//! |----------|---------|-------------|
102//! | `metrics` | yes | metrics-kit registry via [`Telemetry::metrics`] |
103//! | `json` | yes | JSON log format (`LogFormat::Json`) |
104//! | `otlp` | no | OTLP trace export via `opentelemetry-otlp` |
105//!
106//! [`Arc`]: std::sync::Arc
107
108#![forbid(unsafe_code)]
109#![deny(missing_docs)]
110
111mod config;
112mod error;
113mod telemetry;
114
115#[cfg(feature = "metrics")]
116pub use config::DEFAULT_METRICS_BUDGET;
117pub use config::{
118 LogFormat, TelemetryConfig, DEFAULT_LOG_LEVEL, DEFAULT_SAMPLE_RATE, DEFAULT_SERVICE_VERSION,
119};
120pub use error::TelemetryError;
121pub use telemetry::{build_subscriber, Telemetry};