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