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_LUA, "nil_lua");
52 add_directive!(NIL_LUA_CLI, "nil_lua_cli");
53 add_directive!(NIL_SERVER, "nil_server");
54 add_directive!(NIL_SERVER_DATABASE, "nil_server_database");
55
56 add_directive!(TOWER_HTTP, "tower_http", "NIL_LOG_TOWER_HTTP");
57
58 let mut chosen_layers = Vec::new();
59
60 if layers.contains(Layers::FILE) {
63 let path = env::var("NIL_LOG_DIR")?;
64 fs::create_dir_all(&path)?;
65
66 let appender = RollingFileAppender::builder()
67 .rotation(Rotation::HOURLY)
68 .filename_suffix("log")
69 .max_log_files(30 * 24)
70 .build(path)?;
71
72 let (writer, worker_guard) = tracing_appender::non_blocking(appender);
73 chosen_layers.push(
74 Layer::default()
75 .with_ansi(false)
76 .with_timer(Timer)
77 .with_writer(writer)
78 .pretty()
79 .boxed(),
80 );
81
82 guard = Some(worker_guard);
83 }
84
85 if layers.contains(Layers::STDERR) {
86 chosen_layers.push(
87 Layer::default()
88 .with_ansi(true)
89 .with_timer(Timer)
90 .with_writer(io::stderr)
91 .pretty()
92 .boxed(),
93 );
94 }
95
96 let subscriber = Registry::default()
97 .with(chosen_layers)
98 .with(filter);
99
100 set_global_default(subscriber)?;
101
102 Ok(guard)
103}
104
105bitflags! {
106 #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
107 pub struct Directives: u32 {
108 const NIL = 1 << 0;
109 const NIL_CLIENT = 1 << 1;
110 const NIL_CORE = 1 << 2;
111 const NIL_CRYPTO = 1 << 3;
112 const NIL_LUA = 1 << 4;
113 const NIL_LUA_CLI = 1 << 5;
114 const NIL_SERVER = 1 << 6;
115 const NIL_SERVER_DATABASE = 1 << 7;
116 const TOWER_HTTP = 1 << 8;
117 }
118}
119
120impl Default for Directives {
121 fn default() -> Self {
122 Self::all().difference(Self::TOWER_HTTP)
123 }
124}
125
126bitflags! {
127 #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
128 pub struct Layers: u32 {
129 const FILE = 1 << 0;
130 const STDERR = 1 << 1;
131 }
132}
133
134impl Default for Layers {
135 fn default() -> Self {
136 Self::STDERR
137 }
138}