1#![cfg_attr(docsrs, feature(doc_cfg))]
5#![doc(html_favicon_url = "https://nil.dev.br/favicon.png")]
6
7pub mod timer;
8
9use anyhow::Result;
10use bitflags::bitflags;
11use std::{env, fs, io};
12use timer::Timer;
13use tracing::subscriber::set_global_default;
14use tracing_appender::non_blocking::WorkerGuard;
15use tracing_appender::rolling::{RollingFileAppender, Rotation};
16use tracing_subscriber::fmt::Layer;
17use tracing_subscriber::layer::SubscriberExt;
18use tracing_subscriber::{EnvFilter, Layer as _, Registry};
19
20#[bon::builder]
21pub fn setup(
22 #[builder(default)] directives: Directives,
23 #[builder(default)] debug_layers: Layers,
24 #[builder(default)] release_layers: Layers,
25) -> Result<Option<WorkerGuard>> {
26 let mut guard = None::<WorkerGuard>;
27 let mut filter = EnvFilter::builder().from_env()?;
28
29 let layers = if cfg!(debug_assertions) { debug_layers } else { release_layers };
30 let level = env::var("NIL_LOG_LEVEL").unwrap_or_else(|_| String::from("trace"));
31
32 macro_rules! add_directive {
33 ($flag:ident, $krate:literal) => {
34 if directives.contains(Directives::$flag) {
35 let directive = format!("{}={}", $krate, level);
36 filter = filter.add_directive(directive.parse()?);
37 }
38 };
39 ($flag:ident, $krate:literal, $env_var:literal) => {
40 if directives.contains(Directives::$flag) || env::var($env_var).is_ok_and(|it| it == "true") {
41 let directive = format!("{}={}", $krate, level);
42 filter = filter.add_directive(directive.parse()?);
43 }
44 };
45 }
46
47 add_directive!(NIL, "nil");
48 add_directive!(NIL_CLIENT, "nil_client");
49 add_directive!(NIL_CORE, "nil_core");
50 add_directive!(NIL_CRYPTO, "nil_crypto");
51 add_directive!(NIL_SERVER, "nil_server");
52 add_directive!(NIL_SERVER_DATABASE, "nil_server_database");
53
54 add_directive!(TOWER_HTTP, "tower_http", "NIL_LOG_TOWER_HTTP");
55
56 let mut chosen_layers = Vec::new();
57
58 if layers.contains(Layers::FILE) {
61 let path = env::var("NIL_LOG_DIR")?;
62 fs::create_dir_all(&path)?;
63
64 let appender = RollingFileAppender::builder()
65 .rotation(Rotation::HOURLY)
66 .filename_suffix("log")
67 .max_log_files(30 * 24)
68 .build(path)?;
69
70 let (writer, worker_guard) = tracing_appender::non_blocking(appender);
71 chosen_layers.push(
72 Layer::default()
73 .with_ansi(false)
74 .with_timer(Timer)
75 .with_writer(writer)
76 .pretty()
77 .boxed(),
78 );
79
80 guard = Some(worker_guard);
81 }
82
83 if layers.contains(Layers::STDERR) {
84 chosen_layers.push(
85 Layer::default()
86 .with_ansi(true)
87 .with_timer(Timer)
88 .with_writer(io::stderr)
89 .pretty()
90 .boxed(),
91 );
92 }
93
94 let subscriber = Registry::default()
95 .with(chosen_layers)
96 .with(filter);
97
98 set_global_default(subscriber)?;
99
100 Ok(guard)
101}
102
103bitflags! {
104 #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
105 pub struct Directives: u32 {
106 const NIL = 1 << 0;
107 const NIL_CLIENT = 1 << 1;
108 const NIL_CORE = 1 << 2;
109 const NIL_CRYPTO = 1 << 3;
110 const NIL_SERVER = 1 << 4;
111 const NIL_SERVER_DATABASE = 1 << 5;
112 const TOWER_HTTP = 1 << 6;
113 }
114}
115
116impl Default for Directives {
117 fn default() -> Self {
118 Self::all().difference(Self::TOWER_HTTP)
119 }
120}
121
122bitflags! {
123 #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
124 pub struct Layers: u32 {
125 const FILE = 1 << 0;
126 const STDERR = 1 << 1;
127 }
128}
129
130impl Default for Layers {
131 fn default() -> Self {
132 Self::STDERR
133 }
134}