1#![deny(warnings, clippy::all, clippy::pedantic, missing_docs)]
35#![forbid(unsafe_code)]
36
37use logcontrol::{KnownLogTarget, LogControl1, LogControl1Error, LogLevel};
38use tracing::Subscriber;
39use tracing_subscriber::filter::LevelFilter;
40use tracing_subscriber::layer::Layered;
41use tracing_subscriber::registry::LookupSpan;
42use tracing_subscriber::{fmt, reload, Layer};
43
44pub use logcontrol;
45pub use logcontrol::stderr_connected_to_journal;
46pub use logcontrol::syslog_identifier;
47
48#[derive(Debug, Clone, Copy, PartialEq, Eq)]
49enum TracingLogTarget {
50 Console,
51 Journal,
52 Null,
53}
54
55impl From<TracingLogTarget> for KnownLogTarget {
56 fn from(value: TracingLogTarget) -> Self {
57 match value {
58 TracingLogTarget::Console => KnownLogTarget::Console,
59 TracingLogTarget::Journal => KnownLogTarget::Journal,
60 TracingLogTarget::Null => KnownLogTarget::Null,
61 }
62 }
63}
64
65fn from_known_log_target(
66 target: KnownLogTarget,
67 connected_to_journal: bool,
68) -> Result<TracingLogTarget, LogControl1Error> {
69 match target {
70 KnownLogTarget::Auto if connected_to_journal => Ok(TracingLogTarget::Journal),
71 KnownLogTarget::Auto | KnownLogTarget::Console => Ok(TracingLogTarget::Console),
72 KnownLogTarget::Journal => Ok(TracingLogTarget::Journal),
73 KnownLogTarget::Null => Ok(TracingLogTarget::Null),
74 other => Err(LogControl1Error::UnsupportedLogTarget(
75 other.as_str().to_string(),
76 )),
77 }
78}
79
80pub fn from_log_level(level: LogLevel) -> Result<tracing::Level, LogControl1Error> {
90 match level {
91 LogLevel::Err => Ok(tracing::Level::ERROR),
92 LogLevel::Warning => Ok(tracing::Level::WARN),
93 LogLevel::Notice => Ok(tracing::Level::INFO),
94 LogLevel::Info => Ok(tracing::Level::DEBUG),
95 LogLevel::Debug => Ok(tracing::Level::TRACE),
96 unsupported => Err(LogControl1Error::UnsupportedLogLevel(unsupported)),
97 }
98}
99
100fn to_log_level(level: tracing::Level) -> LogLevel {
102 match level {
103 tracing::Level::ERROR => LogLevel::Err,
104 tracing::Level::WARN => LogLevel::Warning,
105 tracing::Level::INFO => LogLevel::Notice,
106 tracing::Level::DEBUG => LogLevel::Info,
107 tracing::Level::TRACE => LogLevel::Debug,
108 }
109}
110
111pub trait LogControl1LayerFactory {
113 type JournalLayer<S: Subscriber + for<'span> LookupSpan<'span>>: Layer<S>;
115 type ConsoleLayer<S: Subscriber + for<'span> LookupSpan<'span>>: Layer<S>;
117
118 fn create_journal_layer<S: Subscriber + for<'span> LookupSpan<'span>>(
127 &self,
128 syslog_identifier: String,
129 ) -> Result<Self::JournalLayer<S>, LogControl1Error>;
130
131 fn create_console_layer<S: Subscriber + for<'span> LookupSpan<'span>>(
137 &self,
138 ) -> Result<Self::ConsoleLayer<S>, LogControl1Error>;
139}
140
141pub struct PrettyLogControl1LayerFactory;
149
150impl LogControl1LayerFactory for PrettyLogControl1LayerFactory {
151 type JournalLayer<S: Subscriber + for<'span> LookupSpan<'span>> = tracing_journald::Layer;
152
153 type ConsoleLayer<S: Subscriber + for<'span> LookupSpan<'span>> =
154 fmt::Layer<S, fmt::format::Pretty, fmt::format::Format<fmt::format::Pretty>>;
155
156 fn create_journal_layer<S: Subscriber + for<'span> LookupSpan<'span>>(
157 &self,
158 syslog_identifier: String,
159 ) -> Result<Self::JournalLayer<S>, LogControl1Error> {
160 Ok(tracing_journald::Layer::new()?
161 .with_field_prefix(None)
162 .with_syslog_identifier(syslog_identifier))
163 }
164
165 fn create_console_layer<S: Subscriber + for<'span> LookupSpan<'span>>(
166 &self,
167 ) -> Result<Self::ConsoleLayer<S>, LogControl1Error> {
168 Ok(tracing_subscriber::fmt::layer().pretty())
169 }
170}
171
172pub type LogTargetLayer<F, S> = Layered<
174 Option<<F as LogControl1LayerFactory>::ConsoleLayer<S>>,
175 Option<<F as LogControl1LayerFactory>::JournalLayer<S>>,
176 S,
177>;
178
179pub type LogControl1Layer<F, S> =
181 Layered<reload::Layer<LogTargetLayer<F, S>, S>, reload::Layer<LevelFilter, S>, S>;
182
183fn make_target_layer<F: LogControl1LayerFactory, S>(
191 factory: &F,
192 target: TracingLogTarget,
193 syslog_identifier: &str,
194) -> Result<LogTargetLayer<F, S>, LogControl1Error>
195where
196 S: Subscriber + for<'span> LookupSpan<'span>,
197{
198 let stdout = if let TracingLogTarget::Console = target {
199 Some(factory.create_console_layer::<S>()?)
200 } else {
201 None
202 };
203 let journal = if let TracingLogTarget::Journal = target {
204 Some(factory.create_journal_layer::<S>(syslog_identifier.to_string())?)
205 } else {
206 None
207 };
208 Ok(tracing_subscriber::Layer::and_then(journal, stdout))
209}
210
211pub struct TracingLogControl1<F, S>
227where
228 F: LogControl1LayerFactory,
229 S: Subscriber + for<'span> LookupSpan<'span>,
230{
231 connected_to_journal: bool,
233 syslog_identifier: String,
235 level: tracing::Level,
237 target: TracingLogTarget,
239 layer_factory: F,
241 level_handle: reload::Handle<LevelFilter, S>,
243 target_handle: reload::Handle<LogTargetLayer<F, S>, S>,
245}
246
247impl<F, S> TracingLogControl1<F, S>
248where
249 F: LogControl1LayerFactory,
250 S: Subscriber + for<'span> LookupSpan<'span>,
251{
252 pub fn new(
275 factory: F,
276 connected_to_journal: bool,
277 syslog_identifier: String,
278 target: KnownLogTarget,
279 level: tracing::Level,
280 ) -> Result<(Self, LogControl1Layer<F, S>), LogControl1Error> {
281 let tracing_target = from_known_log_target(target, connected_to_journal)?;
282 let (target_layer, target_handle) = reload::Layer::new(make_target_layer(
283 &factory,
284 tracing_target,
285 &syslog_identifier,
286 )?);
287 let (level_layer, level_handle) = reload::Layer::new(LevelFilter::from_level(level));
288 let control_layer = Layer::and_then(level_layer, target_layer);
289 let control = Self {
290 connected_to_journal,
291 layer_factory: factory,
292 syslog_identifier,
293 level,
294 target: tracing_target,
295 level_handle,
296 target_handle,
297 };
298
299 Ok((control, control_layer))
300 }
301
302 pub fn new_auto(
314 factory: F,
315 level: tracing::Level,
316 ) -> Result<(Self, LogControl1Layer<F, S>), LogControl1Error> {
317 Self::new(
318 factory,
319 logcontrol::stderr_connected_to_journal(),
320 logcontrol::syslog_identifier(),
321 KnownLogTarget::Auto,
322 level,
323 )
324 }
325}
326
327impl<F, S> LogControl1 for TracingLogControl1<F, S>
328where
329 F: LogControl1LayerFactory,
330 S: Subscriber + for<'span> LookupSpan<'span>,
331{
332 fn level(&self) -> LogLevel {
333 to_log_level(self.level)
334 }
335
336 fn set_level(&mut self, level: LogLevel) -> Result<(), LogControl1Error> {
337 let tracing_level = from_log_level(level)?;
338 self.level_handle
339 .reload(LevelFilter::from_level(tracing_level))
340 .map_err(|error| {
341 LogControl1Error::Failure(format!(
342 "Failed to reload target layer to switch to log target {level}: {error}"
343 ))
344 })?;
345 self.level = tracing_level;
346 Ok(())
347 }
348
349 fn target(&self) -> &str {
350 KnownLogTarget::from(self.target).as_str()
351 }
352
353 fn set_target<T: AsRef<str>>(&mut self, target: T) -> Result<(), LogControl1Error> {
354 let new_tracing_target = from_known_log_target(
355 KnownLogTarget::try_from(target.as_ref())?,
356 self.connected_to_journal,
357 )?;
358 let new_layer = make_target_layer(
359 &self.layer_factory,
360 new_tracing_target,
361 &self.syslog_identifier,
362 )?;
363 self.target_handle.reload(new_layer).map_err(|error| {
364 LogControl1Error::Failure(format!(
365 "Failed to reload target layer to switch to log target {}: {error}",
366 target.as_ref()
367 ))
368 })?;
369 self.target = new_tracing_target;
370 Ok(())
371 }
372
373 fn syslog_identifier(&self) -> &str {
374 &self.syslog_identifier
375 }
376}
377
378#[cfg(test)]
379mod tests {
380 use static_assertions::assert_impl_all;
381 use tracing_subscriber::Registry;
382
383 use crate::{PrettyLogControl1LayerFactory, TracingLogControl1};
384
385 assert_impl_all!(TracingLogControl1<PrettyLogControl1LayerFactory, Registry>: Send, Sync);
387}