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
2833use 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
111120fn 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