Skip to content

Commit 6bd7398

Browse files
committed
WIP: Implement config feature
1 parent 7d7a3ba commit 6bd7398

File tree

15 files changed

+796
-21
lines changed

15 files changed

+796
-21
lines changed

spdlog/Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ release-level-info = []
3232
release-level-debug = []
3333
release-level-trace = []
3434

35+
config = ["serde", "erased-serde"]
3536
source-location = []
3637
native = []
3738
libsystemd = ["libsystemd-sys"]
@@ -45,11 +46,13 @@ cfg-if = "1.0.0"
4546
chrono = "0.4.22"
4647
crossbeam = { version = "0.8.2", optional = true }
4748
dyn-clone = "1.0.14"
49+
erased-serde = { version = "0.3.31", optional = true }
4850
flexible-string = { version = "0.1.0", optional = true }
4951
if_chain = "1.0.2"
5052
is-terminal = "0.4"
5153
log = { version = "0.4.8", optional = true }
5254
once_cell = "1.16.0"
55+
serde = { version = "1.0.163", optional = true }
5356
spdlog-internal = { version = "=0.1.0", path = "../spdlog-internal", optional = true }
5457
spdlog-macros = { version = "0.1.0", path = "../spdlog-macros" }
5558
spin = "0.9.8"
@@ -81,6 +84,7 @@ tracing-subscriber = "=0.3.16"
8184
tracing-appender = "=0.2.2"
8285
paste = "1.0.14"
8386
trybuild = "1.0.90"
87+
toml = "0.8.6"
8488

8589
[build-dependencies]
8690
rustc_version = "0.4.0"

spdlog/src/config/mod.rs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
mod registry;
2+
mod source;
3+
4+
pub(crate) mod parse;
5+
6+
pub use registry::*;
7+
use serde::{de::DeserializeOwned, Deserialize};
8+
pub use source::*;
9+
10+
use crate::{sync::*, Result};
11+
12+
// TODO: Force `'static` on name?
13+
// Builder?
14+
#[derive(PartialEq, Eq, Hash)]
15+
pub struct ComponentMetadata<'a> {
16+
pub(crate) name: &'a str,
17+
}
18+
19+
pub trait Configurable: Sized {
20+
type Params: DeserializeOwned + Default + Send;
21+
22+
fn metadata() -> ComponentMetadata<'static>;
23+
fn build(params: Self::Params) -> Result<Self>;
24+
}

spdlog/src/config/parse.rs

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
use erased_serde::Deserializer as ErasedDeserializer;
2+
use serde::{
3+
de::{
4+
value::{MapAccessDeserializer, UnitDeserializer},
5+
Error as SerdeDeError, MapAccess, Visitor,
6+
},
7+
Deserializer,
8+
};
9+
10+
use crate::{config, formatter::*};
11+
12+
pub fn formatter_deser<'de, D>(de: D) -> Result<Option<Box<dyn Formatter>>, D::Error>
13+
where
14+
D: Deserializer<'de>,
15+
{
16+
struct ParseVisitor;
17+
18+
impl<'de> Visitor<'de> for ParseVisitor {
19+
type Value = Box<dyn Formatter>;
20+
21+
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
22+
formatter.write_str("a spdlog-rs component")
23+
}
24+
25+
fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
26+
where
27+
A: MapAccess<'de>,
28+
{
29+
let name = map
30+
.next_entry::<String, String>()?
31+
.filter(|(key, _)| key == "name")
32+
.map(|(_, value)| value)
33+
.ok_or_else(|| SerdeDeError::missing_field("name"))?;
34+
35+
let remaining_args = map.size_hint().unwrap(); // I don't know what situation it will be `None``
36+
37+
let formatter = if remaining_args == 0 {
38+
let mut erased_de =
39+
<dyn ErasedDeserializer>::erase(UnitDeserializer::<A::Error>::new());
40+
config::registry().build_formatter(&name, &mut erased_de)
41+
} else {
42+
let mut erased_de =
43+
<dyn ErasedDeserializer>::erase(MapAccessDeserializer::new(map));
44+
config::registry().build_formatter(&name, &mut erased_de)
45+
}
46+
.map_err(|err| SerdeDeError::custom(err))?;
47+
48+
Ok(formatter)
49+
}
50+
}
51+
52+
Ok(Some(de.deserialize_map(ParseVisitor)?))
53+
}

spdlog/src/config/registry.rs

Lines changed: 276 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,276 @@
1+
use std::collections::HashMap;
2+
3+
use erased_serde::Deserializer as ErasedDeserializer;
4+
5+
use super::ComponentMetadata;
6+
use crate::{
7+
config::Configurable,
8+
error::ConfigError,
9+
formatter::{Formatter, FullFormatter, PatternFormatter, RuntimePattern},
10+
sink::*,
11+
sync::*,
12+
Error, Result, Sink,
13+
};
14+
15+
type StdResult<T> = std::result::Result<T, Box<dyn std::error::Error>>;
16+
17+
// https://github.com/dtolnay/erased-serde/issues/97
18+
mod erased_serde_ext {
19+
use erased_serde::Result;
20+
use serde::de::Deserialize;
21+
22+
use super::*;
23+
24+
pub trait ErasedDeserialize<'a> {
25+
fn erased_deserialize_in_place(
26+
&mut self,
27+
de: &mut dyn ErasedDeserializer<'a>,
28+
) -> Result<()>;
29+
}
30+
31+
pub trait ErasedDeserializeOwned: for<'a> ErasedDeserialize<'a> {}
32+
33+
impl<T: for<'a> ErasedDeserialize<'a>> ErasedDeserializeOwned for T {}
34+
35+
impl<'a, T: Deserialize<'a>> ErasedDeserialize<'a> for T {
36+
fn erased_deserialize_in_place(
37+
&mut self,
38+
de: &mut dyn ErasedDeserializer<'a>,
39+
) -> Result<()> {
40+
Deserialize::deserialize_in_place(de, self)
41+
}
42+
}
43+
}
44+
use erased_serde_ext::*;
45+
46+
type ComponentDeser<C: Configurable> = fn(de: &mut dyn ErasedDeserializer) -> Result<C>;
47+
48+
type RegisteredComponents<C: Configurable> = HashMap<&'static str, ComponentDeser<C>>;
49+
50+
pub struct Registry {
51+
sink: Mutex<RegisteredComponents<Box<dyn Sink>>>,
52+
formatter: Mutex<RegisteredComponents<Box<dyn Formatter>>>,
53+
54+
// TODO: Consider make them compile-time
55+
builtin_sink: Mutex<RegisteredComponents<Box<dyn Sink>>>,
56+
builtin_formatter: Mutex<RegisteredComponents<Box<dyn Formatter>>>,
57+
}
58+
59+
impl Registry {
60+
pub fn register_sink<S>(&mut self) -> Result<()>
61+
where
62+
S: Sink + Configurable + 'static,
63+
{
64+
self.register_sink_inner::<S>()
65+
}
66+
67+
pub fn register_formatter<F>(&mut self) -> Result<()>
68+
where
69+
F: Formatter + Configurable + 'static,
70+
{
71+
self.register_formatter_inner::<F>()
72+
}
73+
}
74+
75+
impl Registry {
76+
pub(crate) fn with_builtin() -> Self {
77+
let mut registry = Self {
78+
sink: Mutex::new(HashMap::new()),
79+
formatter: Mutex::new(HashMap::new()),
80+
builtin_sink: Mutex::new(HashMap::new()),
81+
builtin_formatter: Mutex::new(HashMap::new()),
82+
};
83+
registry.register_builtin().unwrap(); // Builtin components should not fail to register
84+
registry
85+
}
86+
87+
fn register_builtin(&mut self) -> Result<()> {
88+
self.register_builtin_sink::<FileSink>()?;
89+
self.register_builtin_formatter::<FullFormatter>()?;
90+
self.register_builtin_formatter::<PatternFormatter<RuntimePattern>>()?;
91+
Ok(())
92+
}
93+
94+
pub(crate) fn build_sink(
95+
&self,
96+
name: &str,
97+
de: &mut dyn ErasedDeserializer,
98+
) -> Result<Box<dyn Sink>> {
99+
self.builtin_sink
100+
.lock_expect()
101+
.get(name)
102+
.ok_or_else(|| Error::Config(ConfigError::UnknownComponent(name.to_string())))
103+
.and_then(|f| f(de))
104+
}
105+
106+
pub(crate) fn build_formatter(
107+
&self,
108+
name: &str,
109+
de: &mut dyn ErasedDeserializer,
110+
) -> Result<Box<dyn Formatter>> {
111+
self.builtin_formatter
112+
.lock_expect()
113+
.get(name)
114+
.ok_or_else(|| Error::Config(ConfigError::UnknownComponent(name.to_string())))
115+
.and_then(|f| f(de))
116+
}
117+
118+
impl_registers! {
119+
fn register_sink_inner => sink, Sink,
120+
fn register_formatter_inner => formatter, Formatter,
121+
pub(crate) fn register_builtin_sink => builtin_sink, Sink,
122+
pub(crate) fn register_builtin_formatter => builtin_formatter, Formatter,
123+
}
124+
}
125+
126+
// TODO: Append prefix '$' for custom components
127+
macro_rules! impl_registers {
128+
( $($vis:vis fn $fn_name:ident => $var:ident, $trait:ident),+ $(,)? ) => {
129+
$($vis fn $fn_name<C>(&mut self) -> Result<()>
130+
where
131+
C: $trait + Configurable + 'static,
132+
{
133+
let f = |de: &mut dyn ErasedDeserializer| -> Result<Box<dyn $trait>> {
134+
let mut params = C::Params::default();
135+
params.erased_deserialize_in_place(de).unwrap(); // TODO: Wrong input will trigger a Err, handle it!
136+
Ok(Box::new(C::build(params)?))
137+
};
138+
139+
self.$var
140+
.lock_expect()
141+
.insert(C::metadata().name, f)
142+
.map_or(Ok(()), |_| Err(Error::Config(ConfigError::MultipleRegistration)))
143+
})+
144+
};
145+
}
146+
use impl_registers;
147+
148+
// TODO: Consider removing the `'static` lifetime. Maybe using `Arc<>`?
149+
pub(crate) fn registry() -> &'static Registry {
150+
static REGISTRY: Lazy<Registry> = Lazy::new(Registry::with_builtin);
151+
&REGISTRY
152+
}
153+
154+
#[cfg(test)]
155+
mod tests {
156+
use std::fmt::Write;
157+
158+
use serde::Deserializer;
159+
160+
use super::*;
161+
use crate::{formatter::FmtExtraInfo, prelude::*, ErrorHandler, Record, StringBuf};
162+
163+
pub struct MockSink(i32);
164+
165+
impl Sink for MockSink {
166+
fn log(&self, _record: &Record) -> Result<()> {
167+
unimplemented!()
168+
}
169+
170+
fn flush(&self) -> Result<()> {
171+
unimplemented!()
172+
}
173+
174+
fn level_filter(&self) -> LevelFilter {
175+
unimplemented!()
176+
}
177+
178+
fn set_level_filter(&self, _level_filter: LevelFilter) {
179+
unimplemented!()
180+
}
181+
182+
fn set_formatter(&self, _formatter: Box<dyn Formatter>) {
183+
unimplemented!()
184+
}
185+
186+
fn set_error_handler(&self, handler: Option<ErrorHandler>) {
187+
handler.unwrap()(Error::__ForInternalTestsUseOnly(self.0))
188+
}
189+
}
190+
191+
#[derive(Default, serde::Deserialize)]
192+
pub struct MockParams {
193+
arg: i32,
194+
}
195+
196+
impl Configurable for MockSink {
197+
type Params = MockParams;
198+
199+
fn metadata() -> ComponentMetadata<'static> {
200+
ComponentMetadata { name: "MockSink" }
201+
}
202+
203+
fn build(params: Self::Params) -> Result<Self> {
204+
Ok(Self(params.arg))
205+
}
206+
}
207+
208+
#[derive(Clone)]
209+
pub struct MockFormatter(i32);
210+
211+
impl Formatter for MockFormatter {
212+
fn format(&self, _record: &Record, dest: &mut StringBuf) -> Result<FmtExtraInfo> {
213+
write!(dest, "{}", self.0).unwrap();
214+
Ok(FmtExtraInfo::new())
215+
}
216+
217+
fn clone_box(&self) -> Box<dyn Formatter> {
218+
Box::new(self.clone())
219+
}
220+
}
221+
222+
impl Configurable for MockFormatter {
223+
type Params = MockParams;
224+
225+
fn metadata() -> ComponentMetadata<'static> {
226+
ComponentMetadata {
227+
name: "MockFormatter",
228+
}
229+
}
230+
231+
fn build(params: Self::Params) -> Result<Self> {
232+
Ok(Self(params.arg))
233+
}
234+
}
235+
236+
fn registry_for_test() -> Registry {
237+
let mut registry = Registry::with_builtin();
238+
registry.register_sink::<MockSink>().unwrap();
239+
registry.register_formatter::<MockFormatter>().unwrap();
240+
registry
241+
}
242+
243+
#[test]
244+
fn build_sink_from_params() {
245+
let registry = registry_for_test();
246+
247+
let mut erased_de =
248+
<dyn ErasedDeserializer>::erase(toml::Deserializer::new("arg = 114514"));
249+
let sink = registry.build_sink("MockSink", &mut erased_de).unwrap();
250+
sink.set_error_handler(Some(|err| {
251+
assert!(matches!(err, Error::__ForInternalTestsUseOnly(114514)))
252+
}));
253+
254+
// TODO: test wrong kind
255+
}
256+
257+
#[test]
258+
fn build_formatter_from_params() {
259+
let registry = registry_for_test();
260+
261+
let mut erased_de =
262+
<dyn ErasedDeserializer>::erase(toml::Deserializer::new("arg = 1919810"));
263+
let formatter = registry
264+
.build_formatter("MockFormatter", &mut erased_de)
265+
.unwrap();
266+
let mut dest = StringBuf::new();
267+
formatter
268+
.format(&Record::new(Level::Info, ""), &mut dest)
269+
.unwrap();
270+
assert_eq!(dest, "1919810")
271+
272+
// TODO: test wrong kind
273+
}
274+
275+
// TODO: Test custom components
276+
}

0 commit comments

Comments
 (0)