stellation_backend_cli/
trace.rs

1//! Tracing support.
2
3use std::env;
4
5use console::style;
6use stellation_core::dev::StctlMetadata;
7use tracing::field::Visit;
8use tracing::{Level, Subscriber};
9use tracing_subscriber::filter::filter_fn;
10use tracing_subscriber::layer::Context;
11use tracing_subscriber::prelude::*;
12use tracing_subscriber::registry::LookupSpan;
13use tracing_subscriber::{EnvFilter, Layer};
14
15/// A layer that emits pretty access logs for stellation servers.
16#[derive(Debug, Default)]
17pub struct AccessLog {}
18
19/// Returns a layer that emits pretty access logs.
20pub fn pretty_access() -> AccessLog {
21    AccessLog {}
22}
23
24impl<S> Layer<S> for AccessLog
25where
26    S: Subscriber + for<'lookup> LookupSpan<'lookup>,
27{
28    fn on_event(&self, event: &tracing::Event<'_>, _ctx: Context<'_, S>) {
29        if event.metadata().target() != "stellation_backend::endpoint::trace" {
30            return;
31        }
32
33        #[derive(Default, Debug)]
34        struct Values {
35            duration: Option<u128>,
36            path: Option<String>,
37            method: Option<String>,
38            status: Option<u64>,
39        }
40
41        impl Visit for Values {
42            fn record_u128(&mut self, field: &tracing::field::Field, value: u128) {
43                if field.as_ref() == "duration" {
44                    self.duration = Some(value);
45                }
46            }
47
48            fn record_str(&mut self, field: &tracing::field::Field, value: &str) {
49                if field.as_ref() == "path" {
50                    self.path = Some(value.to_string());
51                }
52            }
53
54            fn record_u64(&mut self, field: &tracing::field::Field, value: u64) {
55                if field.as_ref() == "status" {
56                    self.status = Some(value);
57                }
58            }
59
60            fn record_debug(&mut self, field: &tracing::field::Field, value: &dyn std::fmt::Debug) {
61                if field.as_ref() == "method" {
62                    self.method = Some(format!("{value:?}"));
63                }
64            }
65        }
66
67        let mut values = Values::default();
68        event.record(&mut values);
69
70        if let (Some(path), Some(duration), Some(status), Some(method)) =
71            (values.path, values.duration, values.status, values.method)
72        {
73            let duration = Some(duration)
74                .and_then(|m| i32::try_from(m).ok())
75                .map(f64::from)
76                .expect("duration took too long")
77                / 100_000.0;
78
79            let status = match status {
80                m if m < 200 => style(m).cyan(),
81                m if m < 300 => style(m).green(),
82                m if m < 400 => style(m).yellow(),
83                m => style(m).red(),
84            }
85            .bold();
86
87            eprintln!("{method:>6} {status} {duration:>8.2}ms {path}");
88        }
89    }
90}
91
92/// Initialise tracing with default settings.
93pub fn init_default<S>(var_name: S)
94where
95    S: Into<String>,
96{
97    let var_name = var_name.into();
98    let env_filter = EnvFilter::builder()
99        .with_default_directive(Level::INFO.into())
100        .with_env_var(var_name)
101        .from_env_lossy();
102
103    match env::var(StctlMetadata::ENV_NAME) {
104        Ok(_) => {
105            // Register pretty logging if under development server.
106            tracing_subscriber::registry()
107                .with(pretty_access())
108                .with(
109                    tracing_subscriber::fmt::layer()
110                        .compact()
111                        // access logs are processed by the access log layer
112                        .with_filter(filter_fn(|metadata| {
113                            metadata.target() != "stellation_backend::endpoint::trace"
114                        })),
115                )
116                .with(env_filter)
117                .init();
118        }
119        Err(_) => {
120            tracing_subscriber::registry()
121                .with(tracing_subscriber::fmt::layer().compact())
122                .with(env_filter)
123                .init();
124        }
125    }
126}