cex/
log.rs

1//! # Backtrace
2//!
3//! Backtrace is disabled by default. When enabled, locations of error propagation
4//! by `ret!()`, `throw!()` and `?` operator will be stored in the `Err` variant.
5//!
6//! ## Use `log` feature to enable backtrace.
7//!
8//! ```toml
9//! [dependencies.cex]
10//! version = "0.5"
11//! features = ["log"]
12//! ```
13//!
14//! ## Use `env_log` feature to enable backtrace if the envirnoment variable
15//! `RUST_BACKTRACE` is 1 or "full".
16//!
17//! ```toml
18//! [dependencies.cex]
19//! version = "0.5"
20//! features = ["env_log"]
21//! ```
22//!
23//! ## Use `pretty_log` feature to pretty-print the frames, as if "{:#?}" were used.
24//!
25//! ```toml
26//! [dependencies.cex]
27//! version = "0.5"
28//! features = ["log","pretty_log"]
29//! # or features = ["env_log","pretty_log"]
30//! ```
31//!
32//! ```rust,no_run
33//! use enumx::export::*;
34//! use enumx::predefined::*;
35//! use cex::*;
36//!
37//! #[cex]
38//! pub fn foo() -> Result!( () throws () ) {
39//!     throw!( () );
40//! }
41//!
42//! #[cex]
43//! pub fn bar() -> Result!( () throws () ) {
44//!     ret!( foo()? );
45//! }
46//!
47//! fn main() {
48//!     bar().unwrap();
49//! }
50//! ```
51//!
52//! The output is similar as follows:
53//!
54//! ```text
55//! thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: _0(Log {
56//!     error: (),
57//!     agent: [
58//!         Frame {
59//!             module: "my_program",
60//!             file: "src/main.rs",
61//!             line: 5,
62//!             column: 13,
63//!             info: Some(
64//!                 "throw!(())",
65//!             ),
66//!         },
67//!         Frame {
68//!             module: "my_program",
69//!             file: "src/main.rs",
70//!             line: 10,
71//!             column: 11,
72//!             info: Some(
73//!                 "foo()",
74//!             ),
75//!         },
76//!     ],
77//! })', src/main.rs:14:5
78//! ```
79//!
80//! ## Forward log features to cex crate
81//!
82//! ```toml
83//! [features]
84//! log = ["cex/log"]
85//! env_log = ["cex/env_log"]
86//! pretty_log = ["cex/pretty_log"]
87//! ```
88//!
89//! ## `ret!()`/`throw!()` could have the second argument as a customized log item.
90//!
91//! ```text
92//! ret!( expr, || frame!( "expect an ok value" ));
93//! throw!( expr, || frame!( "oops" ));
94//! ```
95//!
96//! Even if backtrace is disabled at compile time, these will compile. The second
97//! argument just has no effect.
98
99use std::{
100    env,
101    fmt::Debug,
102    marker::PhantomData,
103};
104
105/// Log agent.
106pub trait LogAgent {
107    type Item;
108
109    fn new() -> Self;
110    fn create_log( item: Self::Item ) -> Self;
111    fn append_log( &mut self, item: Self::Item );
112}
113
114impl<T> LogAgent for Vec<T> {
115    type Item = T;
116
117    fn new() -> Self { Vec::new() }
118    fn create_log( item: T ) -> Self { vec![ item ] }
119    fn append_log( &mut self, item: T ) { self.push( item ); }
120}
121
122impl<T> LogAgent for PhantomData<T> {
123    type Item = T;
124
125    fn new() -> Self { PhantomData }
126    fn create_log( _item: T ) -> Self { PhantomData }
127    fn append_log( &mut self, _item: T ) {}
128}
129
130impl LogAgent for String {
131    type Item = String;
132
133    fn new() -> Self { String::new() }
134    fn create_log( item: String ) -> Self { item }
135    fn append_log( &mut self, item: String ) { self.push_str( &format!( "\n{}", item )); }
136}
137
138/// A wrapper struct for logging error value.
139#[derive( PartialEq,Eq )]
140pub struct Log<Inner, Agent: LogAgent = Vec<Frame>> {
141    pub error : Inner, // the error
142    pub agent : Agent, // log agent
143}
144
145#[cfg( not( feature = "pretty_log" ))]
146impl<Inner,Agent> Debug for Log<Inner,Agent>
147    where Inner: Debug
148        , Agent: Debug + LogAgent
149{
150    fn fmt( &self, f: &mut std::fmt::Formatter ) -> std::fmt::Result {
151        f.debug_struct("Log")
152         .field( "error", &self.error )
153         .field( "agent", &self.agent )
154         .finish()
155    }
156}
157
158#[cfg( feature = "pretty_log" )]
159impl<Inner,Agent> Debug for Log<Inner,Agent>
160    where Inner: Debug
161        , Agent: Debug + LogAgent
162{
163    fn fmt( &self, f: &mut std::fmt::Formatter ) -> std::fmt::Result {
164        if f.alternate() {
165            f.debug_struct("Log")
166             .field( "error", &self.error )
167             .field( "agent", &self.agent )
168             .finish()
169        } else {
170            write!( f, "{:#?}", self )
171        }
172    }
173}
174
175/// A type alias for opt-out logging at compile time.
176pub type NoLog<Inner,Item=Frame> = Log<Inner,PhantomData<Item>>;
177
178/// Wraps a type with Log.
179pub trait ToLog<Agent> : Sized
180    where Agent : LogAgent
181{
182    fn new_log( self ) -> Log<Self,Agent>;
183    fn to_log( self, item: Agent::Item ) -> Log<Self,Agent>;
184}
185
186impl<Inner,Agent> ToLog<Agent> for Inner
187    where Agent : LogAgent
188{
189    fn new_log( self ) -> Log<Self,Agent> {
190        Log{ error: self, agent: Agent::new() }
191    }
192
193    fn to_log( self, item: Agent::Item ) -> Log<Inner,Agent> {
194        Log{ error: self, agent: Agent::create_log( item )}
195    }
196}
197
198/// Appends a log item.
199pub trait Logger<Agent> : Sized
200    where Agent : LogAgent
201{
202    fn log( self, item: Agent::Item ) -> Self;
203}
204
205impl<Agent,E> Logger<Agent> for Log<E,Agent>
206    where Agent : LogAgent
207{
208    fn log( mut self, item: Agent::Item ) -> Self {
209        self.agent.append_log( item );
210        self
211    }
212}
213
214macro_rules! impl_logger_for_predefined_enumx {
215    ($($enumx:ident => $($_index:ident $gen:ident)*;)+) => {
216        use ::enumx::predefined::*;
217        $(
218            impl<Agent$(,$gen)*> Logger<Agent> for $enumx<$($gen),*>
219                where Agent : LogAgent
220                  $(, $gen  : Logger<Agent> )*
221            {
222                fn log( self, _item: Agent::Item ) -> Self {
223                    match self {
224                        $( $enumx::$_index( $_index ) => $enumx::$_index( Logger::<Agent>::log( $_index, _item )), )*
225                    }
226                }
227            }
228        )+
229    };
230}
231
232impl_logger_for_predefined_enumx! {
233    Enum0  => ;
234    Enum1  => _0 T0;
235    Enum2  => _0 T0 _1 T1;
236    Enum3  => _0 T0 _1 T1 _2 T2;
237    Enum4  => _0 T0 _1 T1 _2 T2 _3 T3;
238    Enum5  => _0 T0 _1 T1 _2 T2 _3 T3 _4 T4;
239    Enum6  => _0 T0 _1 T1 _2 T2 _3 T3 _4 T4 _5 T5;
240    Enum7  => _0 T0 _1 T1 _2 T2 _3 T3 _4 T4 _5 T5 _6 T6;
241    Enum8  => _0 T0 _1 T1 _2 T2 _3 T3 _4 T4 _5 T5 _6 T6 _7 T7;
242    Enum9  => _0 T0 _1 T1 _2 T2 _3 T3 _4 T4 _5 T5 _6 T6 _7 T7 _8 T8;
243    Enum10 => _0 T0 _1 T1 _2 T2 _3 T3 _4 T4 _5 T5 _6 T6 _7 T7 _8 T8 _9 T9;
244    Enum11 => _0 T0 _1 T1 _2 T2 _3 T3 _4 T4 _5 T5 _6 T6 _7 T7 _8 T8 _9 T9 _10 T10;
245    Enum12 => _0 T0 _1 T1 _2 T2 _3 T3 _4 T4 _5 T5 _6 T6 _7 T7 _8 T8 _9 T9 _10 T10 _11 T11;
246    Enum13 => _0 T0 _1 T1 _2 T2 _3 T3 _4 T4 _5 T5 _6 T6 _7 T7 _8 T8 _9 T9 _10 T10 _11 T11 _12 T12;
247    Enum14 => _0 T0 _1 T1 _2 T2 _3 T3 _4 T4 _5 T5 _6 T6 _7 T7 _8 T8 _9 T9 _10 T10 _11 T11 _12 T12 _13 T13;
248    Enum15 => _0 T0 _1 T1 _2 T2 _3 T3 _4 T4 _5 T5 _6 T6 _7 T7 _8 T8 _9 T9 _10 T10 _11 T11 _12 T12 _13 T13 _14 T14;
249    Enum16 => _0 T0 _1 T1 _2 T2 _3 T3 _4 T4 _5 T5 _6 T6 _7 T7 _8 T8 _9 T9 _10 T10 _11 T11 _12 T12 _13 T13 _14 T14 _15 T15;
250}
251
252/// Environment variable `RUST_BACKTRACE` controlled log agent.
253#[derive( Debug, PartialEq, Eq )]
254pub struct Env<Agent: LogAgent>( Agent );
255
256impl<Agent> LogAgent for Env<Agent>
257    where Agent : LogAgent
258{
259    type Item = <Agent as LogAgent>::Item;
260
261    fn new() -> Self {
262        Env( Agent::new() )
263    }
264
265    fn append_log( &mut self, item: Self::Item ) {
266        if env_log_enabled() {
267            self.0.append_log( item );
268        }
269    }
270
271    fn create_log( item: Self::Item ) -> Self {
272        if env_log_enabled() {
273            Env( Agent::create_log( item ))
274        } else {
275            Env( Agent::new() )
276        }
277    }
278}
279
280fn env_log_enabled() -> bool {
281    env::var( "RUST_BACKTRACE" )
282        .map( |value| value == "1" || value == "full" )
283        .unwrap_or( false )
284}
285
286/// Wraps the `Ok` variant with Log
287pub trait MapToLog<Agent> : Sized
288    where Agent : LogAgent
289{
290    type Output;
291
292    fn map_to_log( self, item: Agent::Item ) -> Self::Output;
293}
294
295impl<Agent,T,E> MapToLog<Agent> for Result<T,E>
296    where E     : ToLog<Agent>
297        , Agent : LogAgent
298{
299    type Output = Result<Log<T,Agent>, E>;
300
301    fn map_to_log( self, item: Agent::Item ) -> Self::Output {
302        self.map( |v| v.to_log( item ))
303    }
304}
305
306/// Wraps the `Err` variant with Log
307pub trait MapErrToLog<Agent> : Sized
308    where Agent : LogAgent
309{
310    type Output;
311
312    fn map_err_to_log( self, item: Agent::Item ) -> Self::Output;
313}
314
315impl<Agent,T,E> MapErrToLog<Agent> for Result<T,E>
316    where E     : ToLog<Agent>
317        , Agent : LogAgent
318{
319    type Output = Result<T, Log<E,Agent>>;
320
321    fn map_err_to_log( self, item: Agent::Item ) -> Self::Output {
322        self.map_err( |e| e.to_log( item ))
323    }
324}
325
326/// Appends a log item to the `Ok` variant.
327pub trait MapLog<Agent> : Sized
328    where Agent : LogAgent
329{
330    type Output;
331
332    fn map_log( self, item: Agent::Item ) -> Self::Output;
333}
334
335impl<Agent,T,E> MapLog<Agent> for Result<T,E>
336    where T     : Logger<Agent>
337        , Agent : LogAgent
338{
339    type Output = Self;
340
341    fn map_log( self, item: Agent::Item ) -> Self::Output {
342        self.map( |e| e.log( item ))
343    }
344}
345
346/// Appends a log item to the `Err` variant.
347pub trait MapErrLog<Agent> : Sized
348    where Agent : LogAgent
349{
350    type Output;
351
352    fn map_err_log( self, item: Agent::Item ) -> Self::Output;
353}
354
355impl<Agent,T,E> MapErrLog<Agent> for Result<T,E>
356    where E     : Logger<Agent>
357        , Agent : LogAgent
358{
359    type Output = Self;
360
361    fn map_err_log( self, item: Agent::Item ) -> Self::Output {
362        self.map_err( |e| e.log( item ))
363    }
364}
365
366/// A struct for store one frame for backtrace.
367#[derive( Debug,Default,PartialEq,Eq,PartialOrd,Ord )]
368pub struct Frame {
369    pub module : &'static str,
370    pub file   : &'static str,
371    pub line   : u32,
372    pub column : u32,
373    pub info   : Option<String>,
374}
375
376impl Frame {
377    pub fn new( module: &'static str, file: &'static str, line: u32, column: u32, info: Option<String> ) -> Self {
378        Frame{ module, file, line, column, info }
379    }
380}
381
382/// A macro to generate a `Frame`, to store the source of the error with file
383/// name, module path, line/column numbers, and an optional context info, using
384/// the same syntax with `format!()`.
385///
386/// An example: `frame!( "An unexpected {:?} was detect.", local_var ))`
387#[macro_export]
388macro_rules! frame {
389    ( $expr:expr ) => {
390        Frame::new( module_path!(), file!(), line!(), column!(), Some( String::from( $expr )))
391    };
392    () => {
393        Frame::new( module_path!(), file!(), line!(), column!(), None )
394    };
395}