tracing_subscriber_sqlite/
lib.rs1use std::{
2 collections::HashMap,
3 fmt::Write,
4 sync::{atomic::AtomicU64, Mutex},
5};
6
7use rusqlite::Connection;
8use time::OffsetDateTime;
9use tracing::{field::Visit, level_filters::LevelFilter, span};
10#[cfg(feature = "tracing-log")]
11use tracing_log::NormalizeEvent;
12
13pub const SQL_SCHEMA: &str = include_str!("../schema/log.sql");
14
15pub fn prepare_database(conn: &Connection) -> rusqlite::Result<()> {
16 conn.execute(SQL_SCHEMA, ()).map(|_| {})
17}
18
19pub struct Subscriber {
20 id: AtomicU64,
21 connection: Mutex<Connection>,
22 max_level: LevelFilter,
23 black_list: Option<Box<[&'static str]>>,
24 white_list: Option<Box<[&'static str]>>,
25}
26
27impl Subscriber {
28 pub fn new(connection: Connection) -> Self {
29 Self::with_max_level(connection, LevelFilter::TRACE)
30 }
31
32 fn with_details(
33 connection: Mutex<Connection>,
34 max_level: LevelFilter,
35 black_list: Option<Box<[&'static str]>>,
36 white_list: Option<Box<[&'static str]>>,
37 ) -> Self {
38 Self {
39 id: AtomicU64::new(1),
40 connection,
41 max_level,
42 black_list,
43 white_list,
44 }
45 }
46
47 pub fn with_max_level(connection: Connection, max_level: LevelFilter) -> Self {
48 Self::with_details(Mutex::new(connection), max_level, None, None)
49 }
50
51 pub fn black_list(&self) -> Option<&[&'static str]> {
52 self.black_list.as_deref()
53 }
54
55 pub fn white_list(&self) -> Option<&[&'static str]> {
56 self.white_list.as_deref()
57 }
58}
59
60impl tracing::Subscriber for Subscriber {
61 fn enabled(&self, metadata: &tracing::Metadata<'_>) -> bool {
62 metadata.level() <= &self.max_level
63 && metadata.module_path().map_or(true, |m| {
64 let starts_with = |module: &&str| m.starts_with(module);
65 let has_module = |modules: &[&str]| modules.iter().any(starts_with);
66 self.white_list().map_or(true, has_module)
67 && !(self.black_list().map_or(false, has_module))
68 })
69 }
70
71 fn max_level_hint(&self) -> Option<tracing::level_filters::LevelFilter> {
72 Some(self.max_level)
73 }
74
75 fn new_span(&self, _span: &span::Attributes<'_>) -> span::Id {
76 let id = self.id.fetch_add(1, std::sync::atomic::Ordering::AcqRel);
77 span::Id::from_u64(id)
78 }
79
80 fn record(&self, _span: &span::Id, _values: &span::Record<'_>) {}
81
82 fn record_follows_from(&self, _span: &span::Id, _follows: &span::Id) {}
83
84 fn event(&self, event: &tracing::Event<'_>) {
85 #[cfg(feature = "tracing-log")]
86 let normalized_meta = event.normalized_metadata();
87 #[cfg(feature = "tracing-log")]
88 let meta = match normalized_meta.as_ref() {
89 Some(meta) if self.enabled(meta) => meta,
90 None => event.metadata(),
91 _ => return,
92 };
93
94 #[cfg(not(feature = "tracing-log"))]
95 let meta = event.metadata();
96
97 let level = meta.level().as_str();
98 let moudle = meta.module_path();
99 let file = meta.file();
100 let line = meta.line();
101
102 let mut message = String::new();
103 let mut kvs = HashMap::new();
104
105 event.record(&mut Visitor {
106 message: &mut message,
107 kvs: &mut kvs,
108 });
109
110 let conn = self.connection.lock().unwrap();
111 let now = OffsetDateTime::now_utc();
112 conn.execute(
113 "INSERT INTO logs_v0 (time, level, module, file, line, message, structured) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7)",
114 (now, level, moudle, file, line, message, serde_json::to_string(&kvs).unwrap()),
115 )
116 .unwrap();
117 }
118
119 fn enter(&self, _span: &span::Id) {}
120
121 fn exit(&self, _span: &span::Id) {}
122}
123
124struct Visitor<'a> {
125 pub message: &'a mut String,
126 pub kvs: &'a mut HashMap<&'static str, String>, }
128
129impl<'a> Visit for Visitor<'a> {
130 fn record_debug(&mut self, field: &tracing::field::Field, value: &dyn std::fmt::Debug) {
131 match field.name() {
132 "message" => write!(self.message, "{value:?}").unwrap(),
133 #[cfg(feature = "tracing-log")]
134 "log.line" | "log.file" | "log.target" | "log.module_path" => {}
135 name => {
136 self.kvs.insert(name, format!("{value:?}"));
137 }
138 }
139 }
140}
141
142#[derive(Debug)]
143pub struct SubscriberBuilder {
144 max_level: LevelFilter,
145 black_list: Option<Box<[&'static str]>>,
146 white_list: Option<Box<[&'static str]>>,
147}
148
149impl SubscriberBuilder {
150 pub fn new() -> Self {
151 Self::default()
152 }
153
154 pub fn with_max_level(self, max_level: LevelFilter) -> Self {
155 Self { max_level, ..self }
156 }
157
158 pub fn with_black_list(self, black_list: impl IntoIterator<Item = &'static str>) -> Self {
160 Self {
161 black_list: Some(black_list.into_iter().collect()),
162 ..self
163 }
164 }
165
166 pub fn with_white_list(self, white_list: impl IntoIterator<Item = &'static str>) -> Self {
168 Self {
169 white_list: Some(white_list.into_iter().collect()),
170 ..self
171 }
172 }
173
174 pub fn build(self, conn: Connection) -> Subscriber {
175 Subscriber::with_details(
176 Mutex::new(conn),
177 self.max_level,
178 self.black_list,
179 self.white_list,
180 )
181 }
182
183 pub fn build_prepared(self, conn: Connection) -> Result<Subscriber, rusqlite::Error> {
184 prepare_database(&conn)?;
185
186 Ok(self.build(conn))
187 }
188}
189
190impl Default for SubscriberBuilder {
191 fn default() -> Self {
192 Self {
193 max_level: LevelFilter::DEBUG,
194 black_list: None,
195 white_list: None,
196 }
197 }
198}