Skip to content

Commit aa3b72f

Browse files
authored
Add support for kv feature (#69)
- Write structured data in `extra` argument - Set new minimal version for log crate
1 parent 83d0ff7 commit aa3b72f

6 files changed

Lines changed: 74 additions & 23 deletions

File tree

Cargo.toml

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,20 @@ rust-version = "1.74"
1414

1515
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
1616

17+
[features]
18+
kv = ["log/kv"]
19+
1720
[dependencies]
1821
arc-swap = "~1"
1922
# It's OK to ask for std on log, because pyo3 needs it too.
20-
log = { version = "~0.4.4", default-features = false, features = ["std"] }
23+
log = { version = "~0.4.21", default-features = false, features = ["std"] }
2124
pyo3 = { version = "0.26", default-features = false }
2225

2326
[dev-dependencies]
24-
pyo3 = { version = "0.26", default-features = false, features = ["auto-initialize", "macros"] }
27+
pyo3 = { version = "0.26", default-features = false, features = [
28+
"auto-initialize",
29+
"macros",
30+
] }
2531

2632
# `pyo3-macros` is lying about the minimal version for its `syn` dependency.
2733
# Because we're testing with `-Zminimal-versions`, we need to explicitly set it here.

examples/hello_world/Cargo.lock

Lines changed: 2 additions & 11 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

examples/hello_world/Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,6 @@ name = "hello_world"
1111
crate-type = ["cdylib"]
1212

1313
[dependencies]
14-
log = "~0.4"
14+
log = { version = "~0.4", features = ["kv"] }
1515
pyo3 = { version = "~0.26", features = ["extension-module"] }
16-
pyo3-log = { path = "../.." }
16+
pyo3-log = { path = "../..", features = ["kv"] }

examples/hello_world/hw.py

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,24 @@
33
import logging
44
import hello_world
55

6-
FORMAT = '%(levelname)s %(name)s %(asctime)-15s %(filename)s:%(lineno)d %(message)s'
7-
logging.basicConfig(format=FORMAT)
8-
logging.getLogger().setLevel(logging.INFO)
9-
logging.info("Test 1")
6+
7+
class ExtraInfoFormatter(logging.Formatter):
8+
def format(self, record):
9+
record_dict = record.__dict__.copy()
10+
standard_keys = logging.makeLogRecord({}).__dict__.keys()
11+
extras = {k: v for k, v in record_dict.items()
12+
if k not in standard_keys}
13+
extras_string = f" {extras}" if extras else ""
14+
formatted_message = super().format(record)
15+
return f"{formatted_message}{extras_string}"
16+
17+
18+
logger = logging.getLogger('hello_world')
19+
handler = logging.StreamHandler()
20+
handler.setFormatter(ExtraInfoFormatter("%(levelname)s:%(message)s"))
21+
logger.addHandler(handler)
22+
logger.setLevel(logging.INFO)
23+
24+
logger.info("Hello, world!", extra={"foo": 42, "bar": "baz"})
25+
1026
hello_world.log_hello()

examples/hello_world/src/lib.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use log::{debug, trace, info};
1+
use log::{debug, info, trace};
22
use pyo3::prelude::*;
33
use pyo3::wrap_pyfunction;
44
use pyo3_log::{Caching, Logger};
@@ -10,12 +10,12 @@ fn log_hello() {
1010
debug!("Stuff");
1111
info!("Hello {}", "world");
1212
info!("Hello 2{}", "world");
13+
info!(test = 5; "Hello world with KV");
1314
}
1415

1516
#[pymodule]
1617
fn hello_world(py: Python<'_>, m: &Bound<'_, PyModule>) -> PyResult<()> {
17-
let _ = Logger::new(py, Caching::LoggersAndLevels)?
18-
.install();
18+
let _ = Logger::new(py, Caching::LoggersAndLevels)?.install();
1919

2020
m.add_wrapped(wrap_pyfunction!(log_hello))?;
2121

src/lib.rs

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -452,7 +452,42 @@ impl Logger {
452452
// it. And besides, we can save ourselves few python calls if it's turned off.
453453
if is_enabled_for(&logger, record.level())? {
454454
let none = py.None();
455-
// TODO: kv pairs, if enabled as a feature?
455+
456+
#[allow(unused_mut)]
457+
let mut extra = py.None().into_bound(py);
458+
459+
#[cfg(feature = "kv")]
460+
if record.key_values().count() > 0 {
461+
// write structured data to 'extra', serializing the values
462+
use log::kv::{Key, Value, VisitSource};
463+
use pyo3::types::{PyDict, PyString};
464+
465+
struct PyDictVisitor<'p> {
466+
dict: Bound<'p, PyDict>,
467+
}
468+
469+
impl<'kvs, 'p> VisitSource<'kvs> for PyDictVisitor<'p> {
470+
fn visit_pair(
471+
&mut self,
472+
key: Key<'kvs>,
473+
value: Value<'kvs>,
474+
) -> Result<(), log::kv::Error> {
475+
let py_key = PyString::new(self.dict.py(), key.as_str());
476+
let py_value = PyString::new(self.dict.py(), &value.to_string());
477+
478+
let _ = self.dict.set_item(py_key, py_value);
479+
Ok(())
480+
}
481+
}
482+
483+
let mut visitor = PyDictVisitor {
484+
dict: PyDict::new(py),
485+
};
486+
let _ = record.key_values().visit(&mut visitor);
487+
488+
extra = visitor.dict.into_any();
489+
}
490+
456491
let record = logger.call_method1(
457492
"makeRecord",
458493
(
@@ -463,8 +498,11 @@ impl Logger {
463498
msg,
464499
PyTuple::empty(py), // args
465500
&none, // exc_info
501+
&none, // func
502+
extra, // extra
466503
),
467504
)?;
505+
468506
logger.call_method1("handle", (record,))?;
469507
}
470508

0 commit comments

Comments
 (0)