Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit a6f1e40

Browse files
committedMar 11, 2025·
Support for converting KV from log crate
1 parent 5415f7a commit a6f1e40

File tree

5 files changed

+135
-56
lines changed

5 files changed

+135
-56
lines changed
 

‎spdlog/benches/spdlog-rs/log_crate_proxy.rs

+6
Original file line numberDiff line numberDiff line change
@@ -29,3 +29,9 @@ fn bench_log_crate_proxy(bencher: &mut Bencher) {
2929
init();
3030
bencher.iter(|| log::info!(bench_log_message!()))
3131
}
32+
33+
#[bench]
34+
fn bench_log_crate_proxy_kv(bencher: &mut Bencher) {
35+
init();
36+
bencher.iter(|| log::info!(key1 = 42, key2 = true; bench_log_message!()))
37+
}

‎spdlog/src/kv.rs

+44-8
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ impl<'a> Key<'a> {
2626
Key(KeyInner::StaticStr(key))
2727
}
2828

29-
fn from_str(key: &'a str) -> Self {
29+
pub(crate) fn from_str(key: &'a str) -> Self {
3030
Key(KeyInner::Str(key))
3131
}
3232

@@ -176,16 +176,16 @@ impl<'a> Iterator for KeyValuesIter<'a> {
176176
pub(crate) type Pair<'a> = (Key<'a>, Value<'a>);
177177

178178
#[cfg(feature = "log")]
179-
pub(crate) struct LogCrateConverter<'a>(Vec<(Key<'a>, Value<'a>)>);
179+
pub(crate) struct LogCrateConverter<'a>(Vec<(log::kv::Key<'a>, ValueOwned)>);
180180

181181
#[cfg(feature = "log")]
182182
impl<'a> LogCrateConverter<'a> {
183183
pub(crate) fn new(capacity: usize) -> Self {
184184
Self(Vec::with_capacity(capacity))
185185
}
186186

187-
pub(crate) fn finalize(self) -> Cow<'a, [Pair<'a>]> {
188-
Cow::Owned(self.0)
187+
pub(crate) fn finalize(self) -> Vec<(log::kv::Key<'a>, ValueOwned)> {
188+
self.0
189189
}
190190
}
191191

@@ -196,10 +196,46 @@ impl<'a> log::kv::VisitSource<'a> for LogCrateConverter<'a> {
196196
key: log::kv::Key<'a>,
197197
value: log::kv::Value<'a>,
198198
) -> Result<(), log::kv::Error> {
199-
self.0.push((
200-
Key::from_str(key.as_str()),
201-
todo!("convert `lov::kv::Value` to `Value`"),
202-
));
199+
struct Visitor(Option<ValueOwned>);
200+
201+
macro_rules! visit_fn {
202+
( $($fn:ident: $ty:ty => $from:ident),+$(,)? ) => {
203+
$(fn $fn(&mut self, value: $ty) -> Result<(), log::kv::Error> {
204+
self.0 = Some(Value::$from(value).to_owned());
205+
Ok(())
206+
})+
207+
};
208+
}
209+
210+
impl log::kv::VisitValue<'_> for Visitor {
211+
fn visit_any(&mut self, value: log::kv::Value) -> Result<(), log::kv::Error> {
212+
// Since we have no way to extract the underlying `&dyn Display`, we have to
213+
// `to_owned()` here
214+
self.0 = Some(Value::from_display(&value).to_owned());
215+
Ok(())
216+
}
217+
218+
fn visit_null(&mut self) -> Result<(), log::kv::Error> {
219+
self.0 = Some(Value::empty().to_owned());
220+
Ok(())
221+
}
222+
223+
visit_fn! {
224+
visit_u64: u64 => from_u64,
225+
visit_i64: i64 => from_i64,
226+
visit_u128: u128 => from_u128,
227+
visit_i128: i128 => from_i128,
228+
visit_f64: f64 => from_f64,
229+
visit_bool: bool => from_bool,
230+
visit_str: &str => from_str,
231+
visit_borrowed_str: &str => from_str,
232+
visit_char: char => from_char,
233+
}
234+
}
235+
236+
let mut visitor = Visitor(None);
237+
value.visit(&mut visitor)?;
238+
self.0.push((key, visitor.0.unwrap()));
203239
Ok(())
204240
}
205241
}

‎spdlog/src/log_crate_proxy.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use std::time::SystemTime;
22

3-
use crate::{default_logger, sync::*, Logger, Record};
3+
use crate::{default_logger, sync::*, LogCrateRecord, Logger};
44

55
/// Proxy layer for compatible [log crate].
66
///
@@ -76,8 +76,8 @@ impl log::Log for LogCrateProxy {
7676

7777
fn log(&self, record: &log::Record) {
7878
let logger = self.logger();
79-
let record = Record::from_log_crate_record(&logger, record, SystemTime::now());
80-
logger.log(&record)
79+
let record = LogCrateRecord::new(&logger, record, SystemTime::now());
80+
logger.log(&record.as_record())
8181
}
8282

8383
fn flush(&self) {

‎spdlog/src/record.rs

+63-45
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ use crate::{kv, Level, SourceLocation};
2222
// possible to correct.
2323
#[derive(Clone, Debug)]
2424
pub struct Record<'a> {
25-
logger_name: Option<Cow<'a, str>>,
25+
logger_name: Option<&'a str>,
2626
payload: Cow<'a, str>,
2727
kvs: Cow<'a, [kv::Pair<'a>]>,
2828
inner: Cow<'a, RecordInner>,
@@ -46,7 +46,7 @@ impl<'a> Record<'a> {
4646
kvs: &'a [(kv::Key<'a>, kv::Value<'a>)],
4747
) -> Record<'a> {
4848
Record {
49-
logger_name: logger_name.map(Cow::Borrowed),
49+
logger_name,
5050
payload: payload.into(),
5151
kvs: Cow::Borrowed(kvs),
5252
inner: Cow::Owned(RecordInner {
@@ -62,7 +62,7 @@ impl<'a> Record<'a> {
6262
#[must_use]
6363
pub fn to_owned(&self) -> RecordOwned {
6464
RecordOwned {
65-
logger_name: self.logger_name.clone().map(|n| n.into_owned()),
65+
logger_name: self.logger_name.map(|n| n.to_owned()),
6666
payload: self.payload.to_string(),
6767
kvs: self
6868
.kvs
@@ -126,47 +126,6 @@ impl<'a> Record<'a> {
126126
}
127127
}
128128

129-
#[cfg(feature = "log")]
130-
#[must_use]
131-
pub(crate) fn from_log_crate_record(
132-
logger: &'a crate::Logger,
133-
record: &'a log::Record,
134-
time: SystemTime,
135-
) -> Self {
136-
let args = record.args();
137-
138-
Self {
139-
// If the logger has a name configured, use that name. Otherwise, the name can also be
140-
// given by the target of the log record.
141-
logger_name: logger.name().map(Cow::Borrowed).or_else(|| {
142-
let log_target = record.target();
143-
if log_target.is_empty() {
144-
None
145-
} else {
146-
Some(Cow::Owned(String::from(log_target)))
147-
}
148-
}),
149-
kvs: {
150-
let kvs = record.key_values();
151-
let mut cvt = kv::LogCrateConverter::new(kvs.count());
152-
assert!(kvs.visit(&mut cvt).is_ok());
153-
cvt.finalize()
154-
},
155-
payload: match args.as_str() {
156-
Some(literal_str) => literal_str.into(),
157-
None => args.to_string().into(),
158-
},
159-
inner: Cow::Owned(RecordInner {
160-
level: record.level().into(),
161-
source_location: SourceLocation::from_log_crate_record(record),
162-
time,
163-
// For records from `log` crate, they never seem to come from different threads, so
164-
// getting the current TID here should be correct
165-
tid: get_current_tid(),
166-
}),
167-
}
168-
}
169-
170129
#[cfg(test)]
171130
pub(crate) fn set_time(&mut self, new: SystemTime) {
172131
self.inner.to_mut().time = new;
@@ -189,7 +148,7 @@ impl RecordOwned {
189148
#[must_use]
190149
pub fn as_ref(&self) -> Record {
191150
Record {
192-
logger_name: self.logger_name.as_deref().map(Cow::Borrowed),
151+
logger_name: self.logger_name.as_deref(),
193152
payload: Cow::Borrowed(&self.payload),
194153
kvs: Cow::Owned(
195154
self.kvs
@@ -245,6 +204,65 @@ impl RecordOwned {
245204
// When adding more getters, also add to `Record`
246205
}
247206

207+
#[cfg(feature = "log")]
208+
#[derive(Clone, Debug)]
209+
pub(crate) struct LogCrateRecord<'a> {
210+
logger_name: Option<&'a str>,
211+
payload: Cow<'a, str>,
212+
kvs: Vec<(log::kv::Key<'a>, kv::ValueOwned)>,
213+
inner: Cow<'a, RecordInner>,
214+
}
215+
216+
#[cfg(feature = "log")]
217+
impl<'a> LogCrateRecord<'a> {
218+
#[must_use]
219+
pub(crate) fn new(
220+
logger: &'a crate::Logger,
221+
record: &'a log::Record,
222+
time: SystemTime,
223+
) -> Self {
224+
let args = record.args();
225+
226+
Self {
227+
// If the logger has a name configured, use that name. Otherwise, the name can also be
228+
// given by the target of the log record.
229+
logger_name: logger.name().or_else(|| Some(record.target())),
230+
kvs: {
231+
let kvs = record.key_values();
232+
let mut cvt = kv::LogCrateConverter::new(kvs.count());
233+
assert!(kvs.visit(&mut cvt).is_ok());
234+
cvt.finalize()
235+
},
236+
payload: match args.as_str() {
237+
Some(literal_str) => literal_str.into(),
238+
None => args.to_string().into(),
239+
},
240+
inner: Cow::Owned(RecordInner {
241+
level: record.level().into(),
242+
source_location: SourceLocation::from_log_crate_record(record),
243+
time,
244+
// For records from `log` crate, they never seem to come from different threads, so
245+
// getting the current TID here should be correct
246+
tid: get_current_tid(),
247+
}),
248+
}
249+
}
250+
251+
#[must_use]
252+
pub(crate) fn as_record(&self) -> Record {
253+
Record {
254+
logger_name: self.logger_name,
255+
payload: self.payload.clone(),
256+
kvs: self
257+
.kvs
258+
.iter()
259+
.map(|(k, v)| (kv::Key::from_str(k.as_str()), v.by_ref()))
260+
.collect(),
261+
inner: self.inner.clone(),
262+
}
263+
}
264+
}
265+
248266
fn get_current_tid() -> u64 {
249267
#[cfg(target_os = "linux")]
250268
#[must_use]

‎spdlog/tests/log_crate_proxy.rs

+19
Original file line numberDiff line numberDiff line change
@@ -49,3 +49,22 @@ fn test_target() {
4949
log::info!(target: "MyLogger", "body");
5050
assert_eq!(sink.clone_string(), format!("[MyLogger] body{__EOL}"));
5151
}
52+
53+
#[cfg(feature = "log")]
54+
#[test]
55+
fn test_kv() {
56+
let formatter = Box::new(PatternFormatter::new(pattern!("{payload} {kv}{eol}")));
57+
let sink = Arc::new(StringSink::with(|b| b.formatter(formatter)));
58+
let logger = Arc::new(build_test_logger(|b| b.sink(sink.clone())));
59+
60+
let _guard = GLOBAL_LOG_CRATE_PROXY_MUTEX.lock().unwrap();
61+
spdlog::init_log_crate_proxy().ok();
62+
spdlog::log_crate_proxy().set_logger(Some(logger));
63+
log::set_max_level(log::LevelFilter::Trace);
64+
65+
log::info!(key1 = 42, key2 = true; "a {} event", "log");
66+
assert_eq!(
67+
sink.clone_string(),
68+
format!("a log event {{ key1=42, key2=true }}{__EOL}")
69+
);
70+
}

0 commit comments

Comments
 (0)