Skip to main content

nil_log/
lib.rs

1// Copyright (C) Call of Nil contributors
2// SPDX-License-Identifier: AGPL-3.0-only
3
4#![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  // The order matters.
58  // See: https://github.com/tokio-rs/tracing/issues/1817
59  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}