11use super :: Export ;
22use crate :: protocol:: { InstanceMessage , LogMessage , TelemetryMessage } ;
3- use std:: prelude :: v1 :: String ;
3+ use std:: string :: String ;
44
5- /// Exporter that pretty prints telemetry messages to stdout .
5+ /// Exporter that pretty prints telemetry messages to stderr .
66///
77/// This exporter only supports log messages (e.g. `error!("foo")`).
88///
@@ -18,10 +18,15 @@ use std::prelude::v1::String;
1818/// use veecle_telemetry::protocol::ExecutionId;
1919///
2020/// let execution_id = ExecutionId::random(&mut rand::rng());
21- /// set_exporter(execution_id, &ConsolePrettyExporter).unwrap();
21+ /// set_exporter(execution_id, &ConsolePrettyExporter::DEFAULT ).unwrap();
2222/// ```
23- #[ derive( Debug ) ]
24- pub struct ConsolePrettyExporter ;
23+ #[ derive( Debug , Default ) ]
24+ pub struct ConsolePrettyExporter ( ( ) ) ;
25+
26+ impl ConsolePrettyExporter {
27+ /// A `const` version of `ConsolePrettyExporter::default()` to allow use as a `&'static`.
28+ pub const DEFAULT : Self = ConsolePrettyExporter ( ( ) ) ;
29+ }
2530
2631impl Export for ConsolePrettyExporter {
2732 fn export (
@@ -31,22 +36,152 @@ impl Export for ConsolePrettyExporter {
3136 message,
3237 } : InstanceMessage ,
3338 ) {
34- if let TelemetryMessage :: Log ( LogMessage {
35- time_unix_nano,
36- severity,
37- body,
38- attributes,
39- ..
40- } ) = message
41- {
42- let attributes = attributes
43- . iter ( )
44- . fold ( String :: new ( ) , |mut formatted, key_value| {
45- formatted. push_str ( ", " ) ;
46- formatted. push_str ( & std:: format!( "{}" , key_value) ) ;
47- formatted
48- } ) ;
49- std:: println!( "[{severity:?}:{time_unix_nano}] {body}: \" {attributes}\" " ) ;
39+ format_message ( message, std:: io:: stderr ( ) ) ;
40+ }
41+ }
42+
43+ fn format_message ( message : TelemetryMessage , mut output : impl std:: io:: Write ) {
44+ if let TelemetryMessage :: Log ( LogMessage {
45+ time_unix_nano,
46+ severity,
47+ body,
48+ attributes,
49+ ..
50+ } ) = message
51+ {
52+ // Millisecond accuracy is probably enough for a console logger.
53+ let time = time_unix_nano / 1_000_000 ;
54+
55+ let attributes = if attributes. is_empty ( ) {
56+ String :: new ( )
57+ } else {
58+ let mut attributes =
59+ attributes
60+ . iter ( )
61+ . fold ( String :: from ( " [" ) , |mut formatted, key_value| {
62+ use std:: fmt:: Write ;
63+ write ! ( formatted, "{key_value}, " ) . unwrap ( ) ;
64+ formatted
65+ } ) ;
66+ // Remove trailing `, `.
67+ attributes. truncate ( attributes. len ( ) - 2 ) ;
68+ attributes + "]"
69+ } ;
70+
71+ // `Debug` doesn't apply padding, so pre-render to allow padding below.
72+ let severity = std:: format!( "{severity:?}" ) ;
73+
74+ // Severity is up to 5 characters, pad it to stay consistent.
75+ //
76+ // Using a min-width of 6 for time means that if it is boot-time it will remain
77+ // consistently 6 digits wide until ~15 minutes have passed, after that it changes
78+ // slowly enough to not be distracting.
79+ // For Unix time it will already be 13 digits wide until 2286.
80+ std:: writeln!( output, "[{severity:>5}:{time:6}] {body}{attributes}" ) . unwrap ( ) ;
81+ }
82+ }
83+
84+ #[ cfg( test) ]
85+ mod tests {
86+ use super :: format_message;
87+ use crate :: macros:: attributes;
88+ use crate :: protocol:: { LogMessage , Severity , TelemetryMessage } ;
89+ use indoc:: indoc;
90+ use pretty_assertions:: assert_eq;
91+ use std:: vec:: Vec ;
92+
93+ #[ test]
94+ fn smoke_test ( ) {
95+ let mut output = Vec :: new ( ) ;
96+
97+ let ns = 1_000_000_000 ;
98+ let messages = [
99+ // First some "boot time" messages with very low timestamps.
100+ ( 1_000_000 , Severity :: Trace , "booting" , attributes ! ( ) as & [ _ ] ) ,
101+ (
102+ 5_000_000 ,
103+ Severity :: Debug ,
104+ "booted" ,
105+ attributes ! ( truth = true , lies = false ) ,
106+ ) ,
107+ (
108+ 5 * ns,
109+ Severity :: Info ,
110+ "running" ,
111+ attributes ! ( mille = 1000 , milli = 0.001 ) ,
112+ ) ,
113+ ( 60 * ns, Severity :: Warn , "running late" , attributes ! ( ) ) ,
114+ ( 61 * ns, Severity :: Error , "really late" , attributes ! ( ) ) ,
115+ ( 3600 * ns, Severity :: Fatal , "terminating" , attributes ! ( ) ) ,
116+ // Then some "Unix time" messages sent around 2060.
117+ (
118+ 2703621600 * ns,
119+ Severity :: Trace ,
120+ "Then are _we_ inhabited by history" ,
121+ attributes ! ( ) as & [ _ ] ,
122+ ) ,
123+ (
124+ 2821816800 * ns,
125+ Severity :: Debug ,
126+ "Light dawns and marble heads, what the hell does this mean" ,
127+ attributes ! ( ) ,
128+ ) ,
129+ (
130+ 2860956000 * ns,
131+ Severity :: Info ,
132+ "This terror that hunts" ,
133+ attributes ! ( Typed = true , date = "1960-08-29" ) ,
134+ ) ,
135+ (
136+ 3118950000 * ns,
137+ Severity :: Warn ,
138+ "I have no words, the finest cenotaph" ,
139+ attributes ! ( ) ,
140+ ) ,
141+ (
142+ 3119036400 * ns,
143+ Severity :: Error ,
144+ "A sun to read the dark" ,
145+ attributes ! ( or = "A son to rend the dark" ) ,
146+ ) ,
147+ (
148+ 3122146800 * ns,
149+ Severity :: Fatal ,
150+ "_Tirer comme des lapins_" ,
151+ attributes ! ( translated = "Shot like rabbits" ) ,
152+ ) ,
153+ ] ;
154+
155+ for ( time_unix_nano, severity, body, attributes) in messages {
156+ format_message (
157+ TelemetryMessage :: Log ( LogMessage {
158+ span_id : None ,
159+ trace_id : None ,
160+ time_unix_nano,
161+ severity,
162+ body : body. into ( ) ,
163+ attributes : attributes. into ( ) ,
164+ } ) ,
165+ & mut output,
166+ ) ;
50167 }
168+
169+ assert_eq ! (
170+ str :: from_utf8( & output) . unwrap( ) ,
171+ indoc! { r#"
172+ [Trace: 1] booting
173+ [Debug: 5] booted [truth: true, lies: false]
174+ [ Info: 5000] running [mille: 1000, milli: 0.001]
175+ [ Warn: 60000] running late
176+ [Error: 61000] really late
177+ [Fatal:3600000] terminating
178+ [Trace:2703621600000] Then are _we_ inhabited by history
179+ [Debug:2821816800000] Light dawns and marble heads, what the hell does this mean
180+ [ Info:2860956000000] This terror that hunts [Typed: true, date: "1960-08-29"]
181+ [ Warn:3118950000000] I have no words, the finest cenotaph
182+ [Error:3119036400000] A sun to read the dark [or: "A son to rend the dark"]
183+ [Fatal:3122146800000] _Tirer comme des lapins_ [translated: "Shot like rabbits"]
184+ "# }
185+ ) ;
51186 }
52187}
0 commit comments