7373//! defaults to the empty string.
7474//! * `{X(user_id)}` - `123e4567-e89b-12d3-a456-426655440000`
7575//! * `{X(nonexistent_key)(no mapping)}` - `no mapping`
76+ //! * `K`, `key_value` - A value from a [log::kv][log_kv] structured logging
77+ //! record attributes. The first argument specifies the key, and the second
78+ //! argument specifies the default value if the key is not present in the
79+ //! log record's attributes. The second argument is optional, and defaults
80+ //! to the empty string. This formatter requires the `log_kv` feature to be
81+ //! enabled.
82+ //! * `{K(user_id)}` - `123e4567-e89b-12d3-a456-426655440000`
83+ //! * `{K(nonexistent_key)(no mapping)}` - `no mapping`
7684//! * An "unnamed" formatter simply formats its argument, applying the format
7785//! specification.
7886//! * `{({l} {m})}` - `INFO hello`
120128//! level `DEBUG` will be truncated to `DEBUG hello, wo`.
121129//!
122130//! [MDC]: https://crates.io/crates/log-mdc
131+ //! [log_kv]: https://docs.rs/log/latest/log/kv/index.html
123132
124133use chrono:: { Local , Utc } ;
125134use derivative:: Derivative ;
@@ -135,6 +144,8 @@ use crate::encode::{
135144#[ cfg( feature = "config_parsing" ) ]
136145use crate :: config:: { Deserialize , Deserializers } ;
137146
147+ use self :: parser:: Formatter ;
148+
138149mod parser;
139150
140151thread_local ! (
@@ -496,46 +507,25 @@ impl<'a> From<Piece<'a>> for Chunk {
496507 "P" | "pid" => no_args ( & formatter. args , parameters, FormattedChunk :: ProcessId ) ,
497508 "i" | "tid" => no_args ( & formatter. args , parameters, FormattedChunk :: SystemThreadId ) ,
498509 "t" | "target" => no_args ( & formatter. args , parameters, FormattedChunk :: Target ) ,
499- "X" | "mdc" => {
500- if formatter. args . len ( ) > 2 {
501- return Chunk :: Error ( "expected at most two arguments" . to_owned ( ) ) ;
502- }
503-
504- let key = match formatter. args . first ( ) {
505- Some ( arg) => {
506- if let Some ( arg) = arg. first ( ) {
507- match arg {
508- Piece :: Text ( key) => key. to_owned ( ) ,
509- Piece :: Error ( ref e) => return Chunk :: Error ( e. clone ( ) ) ,
510- _ => return Chunk :: Error ( "invalid MDC key" . to_owned ( ) ) ,
511- }
512- } else {
513- return Chunk :: Error ( "invalid MDC key" . to_owned ( ) ) ;
514- }
515- }
516- None => return Chunk :: Error ( "missing MDC key" . to_owned ( ) ) ,
517- } ;
518-
519- let default = match formatter. args . get ( 1 ) {
520- Some ( arg) => {
521- if let Some ( arg) = arg. first ( ) {
522- match arg {
523- Piece :: Text ( key) => key. to_owned ( ) ,
524- Piece :: Error ( ref e) => return Chunk :: Error ( e. clone ( ) ) ,
525- _ => return Chunk :: Error ( "invalid MDC default" . to_owned ( ) ) ,
526- }
527- } else {
528- return Chunk :: Error ( "invalid MDC default" . to_owned ( ) ) ;
529- }
530- }
531- None => "" ,
532- } ;
533-
534- Chunk :: Formatted {
535- chunk : FormattedChunk :: Mdc ( key. into ( ) , default. into ( ) ) ,
510+ "X" | "mdc" => match kv_parsing ( & formatter) {
511+ Err ( e) => Chunk :: Error ( format ! ( "MDC: {e}" ) ) ,
512+ Ok ( ( key, default) ) => Chunk :: Formatted {
513+ chunk : FormattedChunk :: Mdc ( key, default) ,
536514 params : parameters,
537- }
538- }
515+ } ,
516+ } ,
517+ #[ cfg( feature = "log_kv" ) ]
518+ "K" | "key_value" => match kv_parsing ( & formatter) {
519+ Err ( e) => Chunk :: Error ( format ! ( "key_value: {e}" ) ) ,
520+ Ok ( ( key, default) ) => Chunk :: Formatted {
521+ chunk : FormattedChunk :: Kv ( key, default) ,
522+ params : parameters,
523+ } ,
524+ } ,
525+ #[ cfg( not( feature = "log_kv" ) ) ]
526+ "K" | "key_value" => Chunk :: Error (
527+ "The log_kv feature is required to parse the key_value argument" . to_owned ( ) ,
528+ ) ,
539529 "" => {
540530 if formatter. args . len ( ) != 1 {
541531 return Chunk :: Error ( "expected exactly one argument" . to_owned ( ) ) ;
@@ -568,6 +558,43 @@ fn no_args(arg: &[Vec<Piece>], params: Parameters, chunk: FormattedChunk) -> Chu
568558 }
569559}
570560
561+ fn kv_parsing < ' a > ( formatter : & ' a Formatter ) -> Result < ( String , String ) , & ' a str > {
562+ if formatter. args . len ( ) > 2 {
563+ return Err ( "expected at most two arguments" ) ;
564+ }
565+
566+ let key = match formatter. args . first ( ) {
567+ Some ( arg) => {
568+ if let Some ( arg) = arg. first ( ) {
569+ match arg {
570+ Piece :: Text ( key) => key. to_owned ( ) ,
571+ Piece :: Error ( ref e) => return Err ( e) ,
572+ _ => return Err ( "invalid key" ) ,
573+ }
574+ } else {
575+ return Err ( "invalid key" ) ;
576+ }
577+ }
578+ None => return Err ( "missing key" ) ,
579+ } ;
580+
581+ let default = match formatter. args . get ( 1 ) {
582+ Some ( arg) => {
583+ if let Some ( arg) = arg. first ( ) {
584+ match arg {
585+ Piece :: Text ( key) => key. to_owned ( ) ,
586+ Piece :: Error ( ref e) => return Err ( e) ,
587+ _ => return Err ( "invalid default" ) ,
588+ }
589+ } else {
590+ return Err ( "invalid default" ) ;
591+ }
592+ }
593+ None => "" ,
594+ } ;
595+ Ok ( ( key. into ( ) , default. into ( ) ) )
596+ }
597+
571598#[ derive( Clone , Eq , PartialEq , Hash , Debug ) ]
572599enum Timezone {
573600 Utc ,
@@ -593,6 +620,8 @@ enum FormattedChunk {
593620 Debug ( Vec < Chunk > ) ,
594621 Release ( Vec < Chunk > ) ,
595622 Mdc ( String , String ) ,
623+ #[ cfg( feature = "log_kv" ) ]
624+ Kv ( String , String ) ,
596625}
597626
598627impl FormattedChunk {
@@ -666,6 +695,15 @@ impl FormattedChunk {
666695 FormattedChunk :: Mdc ( ref key, ref default) => {
667696 log_mdc:: get ( key, |v| write ! ( w, "{}" , v. unwrap_or( default ) ) )
668697 }
698+ #[ cfg( feature = "log_kv" ) ]
699+ FormattedChunk :: Kv ( ref key, ref default) => {
700+ use log:: kv:: ToKey ;
701+ if let Some ( v) = record. key_values ( ) . get ( key. to_key ( ) ) {
702+ write ! ( w, "{v}" )
703+ } else {
704+ write ! ( w, "{default}" )
705+ }
706+ }
669707 }
670708 }
671709}
@@ -751,6 +789,8 @@ mod tests {
751789 #[ cfg( feature = "simple_writer" ) ]
752790 use std:: thread;
753791
792+ #[ cfg( feature = "log_kv" ) ]
793+ use super :: Parser ;
754794 use super :: { Chunk , PatternEncoder } ;
755795 #[ cfg( feature = "simple_writer" ) ]
756796 use crate :: encode:: writer:: simple:: SimpleWriter ;
@@ -1031,6 +1071,71 @@ mod tests {
10311071 assert_eq ! ( buf, b"missing value" ) ;
10321072 }
10331073
1074+ #[ test]
1075+ #[ cfg( all( feature = "simple_writer" , feature = "log_kv" ) ) ]
1076+ fn test_kv ( ) {
1077+ let pw = PatternEncoder :: new ( "{K(user_id)}" ) ;
1078+ let kv = [ ( "user_id" , "kv value" ) ] ;
1079+
1080+ let mut buf = vec ! [ ] ;
1081+ pw. encode (
1082+ & mut SimpleWriter ( & mut buf) ,
1083+ & Record :: builder ( ) . key_values ( & kv) . build ( ) ,
1084+ )
1085+ . unwrap ( ) ;
1086+
1087+ assert_eq ! ( buf, b"kv value" ) ;
1088+ }
1089+
1090+ #[ test]
1091+ #[ cfg( all( feature = "simple_writer" , feature = "log_kv" ) ) ]
1092+ fn test_kv_missing_default ( ) {
1093+ let pw = PatternEncoder :: new ( "{K(user_id)}" ) ;
1094+
1095+ let mut buf = vec ! [ ] ;
1096+ pw. encode ( & mut SimpleWriter ( & mut buf) , & Record :: builder ( ) . build ( ) )
1097+ . unwrap ( ) ;
1098+
1099+ assert_eq ! ( buf, b"" ) ;
1100+ }
1101+
1102+ #[ test]
1103+ #[ cfg( all( feature = "simple_writer" , feature = "log_kv" ) ) ]
1104+ fn test_kv_missing_custom ( ) {
1105+ let pw = PatternEncoder :: new ( "{K(user_id)(missing value)}" ) ;
1106+
1107+ let mut buf = vec ! [ ] ;
1108+ pw. encode ( & mut SimpleWriter ( & mut buf) , & Record :: builder ( ) . build ( ) )
1109+ . unwrap ( ) ;
1110+
1111+ assert_eq ! ( buf, b"missing value" ) ;
1112+ }
1113+
1114+ #[ test]
1115+ #[ cfg( feature = "log_kv" ) ]
1116+ fn test_kv_from_piece_to_chunk ( ) {
1117+ let tests = vec ! [
1118+ (
1119+ "[{K(user_id)(foobar)(test):<5.5}]" ,
1120+ "expected at most two arguments" ,
1121+ ) ,
1122+ ( "[{K({l user_id):<5.5}]" , "expected '}'" ) ,
1123+ ( "[{K({l} user_id):<5.5}]" , "key_value: invalid key" ) ,
1124+ ( "[{K:<5.5}]" , "key_value: missing key" ) ,
1125+ ( "[{K(user_id)({l):<5.5}]" , "expected '}'" ) ,
1126+ ( "[{K(user_id)({l}):<5.5}]" , "key_value: invalid default" ) ,
1127+ ( "[{K(user_id)():<5.5} {M}]" , "key_value: invalid default" ) ,
1128+ ] ;
1129+
1130+ for ( pattern, error_msg) in tests {
1131+ let chunks: Vec < Chunk > = Parser :: new ( pattern) . map ( From :: from) . collect ( ) ;
1132+ match chunks. get ( 1 ) . unwrap ( ) {
1133+ Chunk :: Error ( err) => assert ! ( err. contains( error_msg) ) ,
1134+ _ => panic ! ( ) ,
1135+ }
1136+ }
1137+ }
1138+
10341139 #[ test]
10351140 #[ cfg( feature = "simple_writer" ) ]
10361141 fn debug_release ( ) {
0 commit comments