@@ -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