Skip to main content

ssg/core/
otel.rs

1// Copyright © 2023 - 2026 Static Site Generator (SSG). All rights reserved.
2// SPDX-License-Identifier: Apache-2.0 OR MIT
3
4//! OpenTelemetry build-pipeline tracing scaffolding (issue #422).
5//!
6//! This module is intentionally a *scaffold*. The full deliverable
7//! ships in two phases:
8//!
9//! 1. **Phase A (this commit)** — `otel` Cargo feature, `--trace` CLI
10//!    flag, an `init_if_enabled` initialiser that attaches a
11//!    `tracing-subscriber` JSON formatter to stdout, and one demo
12//!    span around `pipeline::execute_build_pipeline` via
13//!    `#[tracing::instrument]`.
14//!
15//! 2. **Phase B (deferred follow-up)** — per-plugin spans inside
16//!    `PluginManager::run_*`, file-count + duration + peak-RSS
17//!    delta fields, OTLP/gRPC + Jaeger exporter wiring, and a
18//!    Grafana dashboard JSON. Phase B requires `tokio` to be
19//!    introduced; the rest of SSG is rayon-based, so that
20//!    architectural decision is deliberately deferred.
21//!
22//! When the `otel` feature is **off**, this module compiles to an
23//! empty stub — `init_if_enabled` is a no-op. Callers may invoke it
24//! unconditionally and it will simply do nothing.
25
26/// Initialises tracing if both:
27///
28/// 1. The crate was compiled with the `otel` feature, and
29/// 2. `enabled` is `true` (typically driven by the `--trace` CLI flag).
30///
31/// On `(true, true)`: installs a `tracing-subscriber` global
32/// dispatcher with JSON formatting to stdout, level filter from
33/// `RUST_LOG` (default `info`).
34///
35/// In any other case: returns immediately, no global state mutated.
36///
37/// # Returns
38///
39/// `true` if a subscriber was installed; `false` otherwise.
40///
41/// # Examples
42///
43/// ```rust
44/// use ssg::otel::init_if_enabled;
45///
46/// // Disabled ⇒ always returns false, never installs a subscriber.
47/// assert!(!init_if_enabled(false));
48/// ```
49pub fn init_if_enabled(enabled: bool) -> bool {
50    if !enabled {
51        return false;
52    }
53    real::init()
54}
55
56#[cfg(feature = "otel")]
57mod real {
58    use tracing_subscriber::{fmt::format::FmtSpan, prelude::*, EnvFilter};
59
60    pub(super) fn init() -> bool {
61        // Idempotent: if a subscriber is already installed (e.g.
62        // double `--trace` invocation in a script that re-enters),
63        // silently no-op.
64        let filter = EnvFilter::try_from_default_env()
65            .or_else(|_| EnvFilter::try_new("info"))
66            .unwrap_or_default();
67
68        let layer = tracing_subscriber::fmt::layer()
69            .json()
70            .with_span_events(FmtSpan::CLOSE)
71            .with_target(true);
72
73        let installed = tracing_subscriber::registry()
74            .with(filter)
75            .with(layer)
76            .try_init()
77            .is_ok();
78
79        if installed {
80            tracing::info!(
81                target = "ssg::otel",
82                "OpenTelemetry build tracing enabled (JSON to stdout)"
83            );
84        }
85        installed
86    }
87}
88
89#[cfg(not(feature = "otel"))]
90mod real {
91    /// When the `otel` feature is disabled the runtime is absent;
92    /// even if `--trace` is passed, we emit a warning via the
93    /// existing `log` facade and return `false`. The CLI flag is
94    /// still parsed so scripts work across feature-on/feature-off
95    /// builds without conditional logic.
96    pub(super) fn init() -> bool {
97        log::warn!(
98            "[--trace] requested but this binary was built without the \
99             `otel` feature. Rebuild with `cargo build --features otel` \
100             to enable build-pipeline tracing."
101        );
102        false
103    }
104}
105
106#[cfg(test)]
107mod tests {
108    use super::*;
109
110    #[test]
111    fn init_disabled_is_noop_returns_false() {
112        // Always returns false when the caller passes `false`,
113        // regardless of feature state.
114        assert!(!init_if_enabled(false));
115    }
116
117    #[cfg(not(feature = "otel"))]
118    #[test]
119    fn init_enabled_without_feature_warns_and_returns_false() {
120        // Without the feature compiled in, the second call also
121        // returns false (it logs a warning via `log` rather than
122        // panicking).
123        assert!(!init_if_enabled(true));
124    }
125}