Skip to content

Commit 230f2ea

Browse files
committed
Add log::kv support
1 parent f688e38 commit 230f2ea

File tree

3 files changed

+220
-18
lines changed

3 files changed

+220
-18
lines changed

Cargo.toml

Lines changed: 37 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
[package]
22
name = "log4rs"
33
version = "1.3.0"
4-
authors = ["Steven Fackler <sfackler@gmail.com>", "Evan Simmons <esims89@gmail.com>"]
4+
authors = [
5+
"Steven Fackler <sfackler@gmail.com>",
6+
"Evan Simmons <esims89@gmail.com>",
7+
]
58
description = "A highly configurable multi-output logging implementation for the `log` facade"
69
license = "MIT OR Apache-2.0"
710
repository = "https://github.com/estk/log4rs"
@@ -13,7 +16,13 @@ rust-version = "1.69"
1316
[features]
1417
default = ["all_components", "config_parsing", "yaml_format"]
1518

16-
config_parsing = ["humantime", "serde", "serde-value", "typemap-ors", "log/serde"]
19+
config_parsing = [
20+
"humantime",
21+
"serde",
22+
"serde-value",
23+
"typemap-ors",
24+
"log/serde",
25+
]
1726
yaml_format = ["serde_yaml"]
1827
json_format = ["serde_json"]
1928
toml_format = ["toml"]
@@ -27,13 +36,21 @@ fixed_window_roller = []
2736
size_trigger = []
2837
time_trigger = ["rand"]
2938
onstartup_trigger = []
30-
json_encoder = ["serde", "serde_json", "chrono", "log-mdc", "log/serde", "thread-id"]
39+
json_encoder = [
40+
"serde",
41+
"serde_json",
42+
"chrono",
43+
"log-mdc",
44+
"log/serde",
45+
"thread-id",
46+
]
3147
pattern_encoder = ["chrono", "log-mdc", "thread-id"]
3248
ansi_writer = []
3349
console_writer = ["ansi_writer", "libc", "winapi"]
3450
simple_writer = []
3551
threshold_filter = []
3652
background_rotation = []
53+
log_kv = ["log/kv"]
3754

3855
all_components = [
3956
"console_appender",
@@ -47,7 +64,7 @@ all_components = [
4764
"onstartup_trigger",
4865
"json_encoder",
4966
"pattern_encoder",
50-
"threshold_filter"
67+
"threshold_filter",
5168
]
5269

5370
gzip = ["flate2"]
@@ -58,7 +75,9 @@ harness = false
5875

5976
[dependencies]
6077
arc-swap = "1.6"
61-
chrono = { version = "0.4.23", optional = true, features = ["clock"], default-features = false }
78+
chrono = { version = "0.4.23", optional = true, features = [
79+
"clock",
80+
], default-features = false }
6281
flate2 = { version = "1.0", optional = true }
6382
fnv = "1.0"
6483
humantime = { version = "2.1", optional = true }
@@ -72,14 +91,20 @@ serde_json = { version = "1.0", optional = true }
7291
serde_yaml = { version = "0.9", optional = true }
7392
toml = { version = "<0.8.10", optional = true }
7493
parking_lot = { version = "0.12.0", optional = true }
75-
rand = { version = "0.8", optional = true}
94+
rand = { version = "0.8", optional = true }
7695
thiserror = "1.0.15"
7796
anyhow = "1.0.28"
7897
derivative = "2.2"
7998
once_cell = "1.17.1"
8099

81100
[target.'cfg(windows)'.dependencies]
82-
winapi = { version = "0.3", optional = true, features = ["handleapi", "minwindef", "processenv", "winbase", "wincon"] }
101+
winapi = { version = "0.3", optional = true, features = [
102+
"handleapi",
103+
"minwindef",
104+
"processenv",
105+
"winbase",
106+
"wincon",
107+
] }
83108

84109
[target.'cfg(not(windows))'.dependencies]
85110
libc = { version = "0.2", optional = true }
@@ -98,7 +123,11 @@ required-features = ["json_encoder", "console_appender"]
98123

99124
[[example]]
100125
name = "log_to_file"
101-
required-features = ["console_appender", "file_appender", "rolling_file_appender"]
126+
required-features = [
127+
"console_appender",
128+
"file_appender",
129+
"rolling_file_appender",
130+
]
102131

103132
[[example]]
104133
name = "compile_time_config"

src/encode/json.rs

Lines changed: 56 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,11 @@
2424
//! }
2525
//! }
2626
//! ```
27+
//! If the `log_kv` feature is enabled, an additional `attributes` field will
28+
//! contain a map of the record's [log::kv][log_kv] structured logging
29+
//! attributes.
30+
//!
31+
//! [log_kv]: https://docs.rs/log/latest/log/kv/index.html
2732
2833
use chrono::{
2934
format::{DelayedFormat, Fixed, Item},
@@ -76,6 +81,8 @@ impl JsonEncoder {
7681
thread: thread.name(),
7782
thread_id: thread_id::get(),
7883
mdc: Mdc,
84+
#[cfg(feature = "log_kv")]
85+
attributes: kv::get_attributes(record.key_values())?,
7986
};
8087
message.serialize(&mut serde_json::Serializer::new(&mut *w))?;
8188
w.write_all(NEWLINE.as_bytes())?;
@@ -106,6 +113,8 @@ struct Message<'a> {
106113
thread: Option<&'a str>,
107114
thread_id: usize,
108115
mdc: Mdc,
116+
#[cfg(feature = "log_kv")]
117+
attributes: kv::Map,
109118
}
110119

111120
fn ser_display<T, S>(v: &T, s: S) -> Result<S::Ok, S::Error>
@@ -162,6 +171,34 @@ impl Deserialize for JsonEncoderDeserializer {
162171
Ok(Box::<JsonEncoder>::default())
163172
}
164173
}
174+
#[cfg(feature = "log_kv")]
175+
mod kv {
176+
use log::kv::VisitSource;
177+
use std::collections::BTreeMap;
178+
179+
pub(crate) type Map = BTreeMap<String, String>;
180+
181+
pub(crate) fn get_attributes(source: &dyn log::kv::Source) -> anyhow::Result<Map> {
182+
struct Visitor {
183+
inner: Map,
184+
}
185+
impl<'kvs> VisitSource<'kvs> for Visitor {
186+
fn visit_pair(
187+
&mut self,
188+
key: log::kv::Key<'kvs>,
189+
value: log::kv::Value<'kvs>,
190+
) -> Result<(), log::kv::Error> {
191+
self.inner.insert(format!("{key}"), format!("{value}"));
192+
Ok(())
193+
}
194+
}
195+
let mut visitor = Visitor {
196+
inner: BTreeMap::new(),
197+
};
198+
source.visit(&mut visitor)?;
199+
Ok(visitor.inner)
200+
}
201+
}
165202

166203
#[cfg(test)]
167204
#[cfg(feature = "simple_writer")]
@@ -189,26 +226,35 @@ mod test {
189226

190227
let encoder = JsonEncoder::new();
191228

229+
let mut record_builder = Record::builder();
230+
record_builder
231+
.level(level)
232+
.target(target)
233+
.module_path(Some(module_path))
234+
.file(Some(file))
235+
.line(Some(line));
236+
237+
#[cfg(feature = "log_kv")]
238+
record_builder.key_values(&[("log_foo", "log_bar")]);
239+
192240
let mut buf = vec![];
193241
encoder
194242
.encode_inner(
195243
&mut SimpleWriter(&mut buf),
196244
time,
197-
&Record::builder()
198-
.level(level)
199-
.target(target)
200-
.module_path(Some(module_path))
201-
.file(Some(file))
202-
.line(Some(line))
203-
.args(format_args!("{}", message))
204-
.build(),
245+
&record_builder.args(format_args!("{}", message)).build(),
205246
)
206247
.unwrap();
207248

249+
#[cfg(feature = "log_kv")]
250+
let expected_attributes = ",\"attributes\":{\"log_foo\":\"log_bar\"}";
251+
#[cfg(not(feature = "log_kv"))]
252+
let expected_attributes = "";
253+
208254
let expected = format!(
209255
"{{\"time\":\"{}\",\"level\":\"{}\",\"message\":\"{}\",\"module_path\":\"{}\",\
210256
\"file\":\"{}\",\"line\":{},\"target\":\"{}\",\
211-
\"thread\":\"{}\",\"thread_id\":{},\"mdc\":{{\"foo\":\"bar\"}}}}",
257+
\"thread\":\"{}\",\"thread_id\":{},\"mdc\":{{\"foo\":\"bar\"}}{}}}",
212258
time.to_rfc3339(),
213259
level,
214260
message,
@@ -218,6 +264,7 @@ mod test {
218264
target,
219265
thread,
220266
thread_id::get(),
267+
expected_attributes
221268
);
222269
assert_eq!(expected, String::from_utf8(buf).unwrap().trim());
223270
}

0 commit comments

Comments
 (0)