1use opentelemetry::global;
2use tracing_appender::non_blocking::{NonBlocking, WorkerGuard};
3use tracing_subscriber::fmt::time::UtcTime;
4use tracing_subscriber::prelude::*;
5use tracing_subscriber::Layer;
6mod otel;
7
8use crate::error::LoggerError;
9use crate::LoggingOptions;
10
11#[derive(Debug, PartialEq, Clone, Copy)]
12enum Environment {
13 Prod,
14 Test,
15}
16
17pub fn init(opts: &LoggingOptions) -> LoggingGuard {
19 #![allow(clippy::trivially_copy_pass_by_ref, clippy::needless_borrow)]
20 match try_init(&opts, Environment::Prod) {
21 Ok(guard) => guard,
22 Err(e) => panic!("Error initializing logger: {}", e),
23 }
24}
25
26#[must_use]
28pub fn init_test(opts: &LoggingOptions) -> Option<LoggingGuard> {
29 #![allow(clippy::trivially_copy_pass_by_ref, clippy::needless_borrow)]
30 try_init(&opts, Environment::Test).ok()
31}
32
33#[must_use]
34#[derive(Debug)]
35pub struct LoggingGuard {
37 #[allow(unused)]
38 env: Environment,
39 #[allow(unused)]
40 logfile: Option<WorkerGuard>,
41 #[allow(unused)]
42 console: WorkerGuard,
43 #[allow(unused)]
44 tracer_provider: Option<opentelemetry::sdk::trace::TracerProvider>,
45}
46
47impl LoggingGuard {
48 fn new(
49 env: Environment,
50 logfile: Option<WorkerGuard>,
51 console: WorkerGuard,
52 tracer_provider: Option<opentelemetry::sdk::trace::TracerProvider>,
53 ) -> Self {
54 Self {
55 env,
56 logfile,
57 console,
58 tracer_provider,
59 }
60 }
61 #[allow(clippy::missing_const_for_fn)]
63 pub fn teardown(&self) {
64 }
66
67 pub fn flush(&mut self) {
69 let has_otel = self.tracer_provider.take().is_some();
70
71 if has_otel {
72 let (sender, receiver) = std::sync::mpsc::channel();
77 let handle = std::thread::spawn(move || {
78 opentelemetry::global::shutdown_tracer_provider();
79 let _ = sender.send(());
80 });
81
82 let _ = receiver.recv_timeout(std::time::Duration::from_millis(200));
84
85 if !handle.is_finished() {
88 debug!("open telemetry tracer provider did not shut down in time, forcing shutdown");
89 }
90 }
91 }
92}
93
94impl Drop for LoggingGuard {
95 fn drop(&mut self) {
96 self.flush();
97 }
98}
99
100fn get_stderr_writer(_opts: &LoggingOptions) -> (NonBlocking, WorkerGuard) {
101 let (stderr_writer, console_guard) = tracing_appender::non_blocking(std::io::stderr());
102
103 (stderr_writer, console_guard)
104}
105
106#[allow(clippy::too_many_lines)]
107fn try_init(opts: &LoggingOptions, environment: Environment) -> Result<LoggingGuard, LoggerError> {
108 #[cfg(windows)]
109 let with_color = ansi_term::enable_ansi_support().is_ok();
110 #[cfg(not(windows))]
111 let with_color = true;
112
113 let timer = UtcTime::new(time::format_description::parse("[year]-[month]-[day]T[hour]:[minute]:[second]").unwrap());
114 let (stderr_writer, console_guard) = get_stderr_writer(opts);
115
116 let needs_simple_tracer = tokio::runtime::Handle::try_current().is_err() || environment == Environment::Test;
117
118 let (otel_layer, tracer_provider) = opts.otlp_endpoint.as_ref().map_or_else(
120 || (None, None),
121 |otlp_endpoint| {
122 let (tracer, provider) = if needs_simple_tracer {
123 otel::build_simple(otlp_endpoint).unwrap()
124 } else {
125 otel::build_batch(otlp_endpoint).unwrap() };
127
128 let _ = global::set_tracer_provider(provider.clone());
129
130 let layer = Some(
131 tracing_opentelemetry::layer()
132 .with_tracer(tracer)
133 .with_filter(opts.levels.telemetry.clone()),
134 );
135 (layer, Some(provider))
136 },
137 );
138
139 let (verbose_layer, normal_layer, logfile_guard, test_layer) = match environment {
142 Environment::Prod => {
143 if opts.verbose {
144 (
145 Some(
146 tracing_subscriber::fmt::layer()
147 .with_writer(stderr_writer)
148 .with_ansi(with_color)
149 .with_timer(timer)
150 .with_thread_names(cfg!(debug_assertions))
151 .with_target(cfg!(debug_assertions))
152 .with_file(cfg!(debug_assertions))
153 .with_line_number(cfg!(debug_assertions))
154 .with_filter(opts.levels.stderr.clone()),
155 ),
156 None,
157 None,
158 None,
159 )
160 } else {
161 (
162 None,
163 Some(
164 tracing_subscriber::fmt::layer()
165 .with_writer(stderr_writer)
166 .with_thread_names(false)
167 .with_ansi(with_color)
168 .with_target(false)
169 .with_timer(timer)
170 .with_filter(opts.levels.stderr.clone()),
171 ),
172 None,
173 None,
174 )
175 }
176 }
177 Environment::Test => (
178 None,
179 None,
180 None,
181 Some(
182 tracing_subscriber::fmt::layer()
183 .with_writer(stderr_writer)
184 .with_ansi(with_color)
185 .without_time()
186 .with_target(true)
187 .with_test_writer()
188 .with_filter(opts.levels.stderr.clone()),
189 ),
190 ),
191 };
192
193 let subscriber = tracing_subscriber::registry()
194 .with(otel_layer)
195 .with(test_layer)
196 .with(verbose_layer)
197 .with(normal_layer);
198
199 #[cfg(feature = "console")]
200 let subscriber = subscriber.with(console_subscriber::spawn());
201
202 tracing::subscriber::set_global_default(subscriber)?;
203 let guards = Ok(LoggingGuard::new(
204 environment,
205 logfile_guard,
206 console_guard,
207 tracer_provider,
208 ));
209 trace!(options=?opts,"logger initialized");
210
211 guards
212}