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