tracing_web_console/
layer.rs

1//! Main TracingLayer that integrates with Axum
2
3use crate::api::logs::LogsState;
4use crate::storage::LogStorage;
5use crate::subscriber::LogCaptureLayer;
6use axum::routing::get;
7use axum::Router;
8use std::sync::Arc;
9use tower_http::cors::{Any, CorsLayer};
10use tracing_subscriber::layer::SubscriberExt;
11use tracing_subscriber::util::SubscriberInitExt;
12use tracing_subscriber::EnvFilter;
13
14/// Main tracing layer that can be added to an Axum application
15#[derive(Clone)]
16pub struct TracingLayer {
17    router: Router,
18}
19
20impl TracingLayer {
21    /// Create a new TracingLayer with the specified base path
22    ///
23    /// # Arguments
24    ///
25    /// * `base_path` - The base path for all tracing UI routes (e.g., "/tracing")
26    ///
27    /// # Example
28    ///
29    /// ```rust,no_run
30    /// use axum::Router;
31    /// use axum::routing::get;
32    /// use tracing_web_console::TracingLayer;
33    ///
34    /// let app = Router::new()
35    ///     .route("/", get(|| async { "Hello World" }))
36    ///     .merge(TracingLayer::new("/tracing").into_router());
37    /// ```
38    pub fn new(base_path: &str) -> Self {
39        Self::with_capacity(base_path, 10_000)
40    }
41
42    /// Create a new TracingLayer with custom storage capacity
43    ///
44    /// # Arguments
45    ///
46    /// * `base_path` - The base path for all tracing UI routes
47    /// * `capacity` - Maximum number of log events to store in memory
48    pub fn with_capacity(base_path: &str, capacity: usize) -> Self {
49        // Create storage for log events
50        let storage = LogStorage::with_capacity(capacity);
51
52        // Set up tracing subscriber with env filter
53        // Default to "trace" for all targets except:
54        // - this crate (to avoid recursive logging)
55        // - "log" target (noisy compatibility layer from log crate)
56        let env_filter = EnvFilter::try_from_default_env()
57            .unwrap_or_else(|_| EnvFilter::new("trace,tracing_web_console=off,log=off"));
58
59        // Create our custom log capture layer
60        let log_capture_layer = LogCaptureLayer::new(storage.clone());
61
62        // Initialize the tracing subscriber
63        // Note: This will set the global default subscriber
64        tracing_subscriber::registry()
65            .with(env_filter)
66            .with(log_capture_layer)
67            .try_init()
68            .ok(); // Ignore error if already initialized
69
70        // Create shared state
71        let logs_state = Arc::new(LogsState::new(storage.clone()));
72
73        // Create frontend state with base path
74        let frontend_state = crate::frontend::FrontendState {
75            base_path: Arc::new(base_path.to_string()),
76        };
77
78        // Create frontend router with its state
79        let frontend_router = Router::new()
80            .route("/", get(crate::frontend::serve_index))
81            .route("/assets/*path", get(crate::frontend::serve_static))
82            .with_state(frontend_state);
83
84        // Create the API router
85        let api_router = crate::api::create_api_router(logs_state);
86
87        // Merge frontend and API routers
88        let inner_router = frontend_router.merge(api_router);
89
90        // Add CORS middleware for development
91        // In production this allows all origins, which is fine for a debugging/monitoring tool
92        let cors = CorsLayer::new()
93            .allow_origin(Any)
94            .allow_methods(Any)
95            .allow_headers(Any);
96
97        // Nest everything under the base path and add CORS
98        let router = Router::new().nest(base_path, inner_router).layer(cors);
99
100        Self { router }
101    }
102
103    /// Merge this tracing layer with an existing Axum router
104    ///
105    /// This is the recommended way to add the tracing UI to your application
106    ///
107    /// # Example
108    ///
109    /// ```rust,no_run
110    /// use axum::Router;
111    /// use axum::routing::get;
112    /// use tracing_web_console::TracingLayer;
113    ///
114    /// let app = Router::new()
115    ///     .route("/", get(|| async { "Hello World" }))
116    ///     .merge(TracingLayer::new("/tracing").into_router());
117    /// ```
118    pub fn into_router(self) -> Router {
119        self.router
120    }
121}
122
123/// Builder for configuring TracingLayer
124#[allow(dead_code)]
125pub struct TracingLayerBuilder {
126    base_path: String,
127    capacity: usize,
128    initial_filter: String,
129}
130
131impl TracingLayerBuilder {
132    /// Create a new builder with the specified base path
133    #[allow(dead_code)]
134    pub fn new(base_path: &str) -> Self {
135        Self {
136            base_path: base_path.to_string(),
137            capacity: 10_000,
138            initial_filter: "trace".to_string(),
139        }
140    }
141
142    /// Set the storage capacity
143    #[allow(dead_code)]
144    pub fn with_capacity(mut self, capacity: usize) -> Self {
145        self.capacity = capacity;
146        self
147    }
148
149    /// Set the initial log filter
150    #[allow(dead_code)]
151    pub fn with_filter(mut self, filter: &str) -> Self {
152        self.initial_filter = filter.to_string();
153        self
154    }
155
156    /// Build the TracingLayer
157    #[allow(dead_code)]
158    pub fn build(self) -> TracingLayer {
159        TracingLayer::with_capacity(&self.base_path, self.capacity)
160    }
161}
162
163#[cfg(test)]
164mod tests {
165    use super::*;
166
167    #[test]
168    fn test_tracing_layer_creation() {
169        // Basic test to ensure it can be created without panic
170        let _layer = TracingLayer::new("/tracing");
171    }
172
173    #[test]
174    fn test_builder_pattern() {
175        let builder = TracingLayerBuilder::new("/tracing")
176            .with_capacity(5000)
177            .with_filter("debug");
178
179        assert_eq!(builder.base_path, "/tracing");
180        assert_eq!(builder.capacity, 5000);
181        assert_eq!(builder.initial_filter, "debug");
182    }
183
184    #[test]
185    fn test_builder_defaults_to_trace() {
186        let builder = TracingLayerBuilder::new("/tracing");
187        assert_eq!(builder.initial_filter, "trace");
188    }
189}