workflow_log/
log.rs

1use cfg_if::cfg_if;
2use std::fmt;
3
4cfg_if! {
5    if #[cfg(target_arch = "bpf")] {
6        pub use workflow_log::levels::{ Level, LevelFilter };
7    } else {
8        use std::sync::Arc;
9        pub use log::{ Level, LevelFilter };
10        use downcast::{ downcast_sync, AnySync };
11        pub use hexplay::{self, HexViewBuilder};
12        pub use termcolor::Buffer;
13        //use core::ops::Range;
14
15        pub struct ColorHexView<'a>{
16            pub builder: HexViewBuilder<'a>,
17            pub color_start: usize
18        }
19        impl<'a> ColorHexView<'a>{
20            pub fn new(builder:HexViewBuilder<'a>, colors:Vec<(&'a str, usize)>)->Self{
21                Self{
22                    builder,
23                    color_start:0
24                }.add_colors(colors)
25            }
26
27            pub fn add_colors(mut self, colors:Vec<(&'a str, usize)>)->Self{
28                let mut builder = self.builder;
29                for (color, len) in colors{
30                    let end = self.color_start+len;
31                    let range = self.color_start..end;
32                    self.color_start = end;
33                    builder = builder.add_color(color, range);
34                }
35                self.builder = builder;
36                self
37            }
38
39            pub fn add_colors_with_range(mut self, colors:Vec<(&'a str, std::ops::Range<usize>)>)->Self{
40                let mut builder = self.builder;
41                for (color, range) in colors{
42                    builder = builder.add_color(color, range);
43                }
44                self.builder = builder;
45                self
46            }
47
48            pub fn try_print(self)->std::result::Result<(), String>{
49                let mut buf = Buffer::ansi();
50                match self.builder.finish().fmt(&mut buf){
51                    Ok(()) => {
52                        match String::from_utf8(buf.as_slice().to_vec()){
53                            Ok(str)=>{
54                                log_trace!("{}", str);
55                            }
56                            Err(_)=>{
57                                return Err("Unable to convert HexView to string".to_string());
58                            }
59                        }
60                    },
61                    Err(_) => {
62                        return Err("Unable to format HexView".to_string());
63                    }
64                }
65                Ok(())
66            }
67        }
68
69        /// A log sink trait that can be installed into the log subsystem using the [`pipe`]
70        /// function and will receive all log messages.
71        pub trait Sink : AnySync {
72            fn write(&self, target: Option<&str>, level : Level, args : &fmt::Arguments<'_>) -> bool;
73        }
74
75        struct SinkHandler {
76            // #[allow(dead_code)]
77            sink : Arc<dyn Sink>, // + Send + Sync + 'static>,
78        }
79
80        downcast_sync!(dyn Sink);
81    }
82}
83
84cfg_if! {
85    if #[cfg(target_arch = "bpf")] {
86        #[inline(always)]
87        pub fn log_level_enabled(_level: Level) -> bool {
88            true
89        }
90    } else if #[cfg(target_arch = "wasm32")] {
91        use wasm_bindgen::prelude::*;
92
93        static mut LEVEL_FILTER : LevelFilter = LevelFilter::Info;
94        #[inline(always)]
95        pub fn log_level_enabled(level: Level) -> bool {
96            unsafe { LEVEL_FILTER >= level }
97        }
98        pub fn set_log_level(level: LevelFilter) {
99            unsafe { LEVEL_FILTER = level };
100        }
101
102        #[wasm_bindgen]
103        extern "C" {
104            #[wasm_bindgen(typescript_type = r###""off" | "error" | "warn" | "info" | "debug" | "trace""###)]
105            #[derive(Debug)]
106            pub type LogLevelT;
107        }
108
109        #[doc="Set the logger log level using a string representation."]
110        #[doc="Available variants are: 'off', 'error', 'warn', 'info', 'debug', 'trace'"]
111        #[doc="@category General"]
112        #[wasm_bindgen(js_name = "setLogLevel")]
113        // pub fn set_log_level_str(level: &str) {
114        pub fn set_log_level_wasm(level: LogLevelT) {
115            if let Some(level) = level.as_string() {
116                let level = match level.as_str() {
117                    "off" => LevelFilter::Off,
118                    "error" => LevelFilter::Error,
119                    "warn" => LevelFilter::Warn,
120                    "info" => LevelFilter::Info,
121                    "debug" => LevelFilter::Debug,
122                    "trace" => LevelFilter::Trace,
123                    _ => panic!("Invalid log level: {level}"),
124                };
125                set_log_level(level);
126            } else {
127                panic!("log level must be a string, received: {level:?}");
128            }
129        }
130
131        cfg_if! {
132            if #[cfg(feature = "sink")] {
133                use std::sync::Mutex;
134                static SINK : Mutex<Option<SinkHandler>> = Mutex::new(None);
135                // pub fn pipe(sink : Arc<dyn Sink + Send + Sync + 'static>) {
136                pub fn pipe(sink : Option<Arc<dyn Sink>>) {
137                    match sink {
138                        Some(sink) => { *SINK.lock().unwrap() = Some(SinkHandler { sink }); },
139                        None => { *SINK.lock().unwrap() = None; }
140                    }
141                }
142                #[inline(always)]
143                fn to_sink(target: Option<&str>, level : Level, args : &fmt::Arguments<'_>) -> bool {
144                    match SINK.lock().unwrap().as_ref() {
145                        Some(handler) => {
146                            handler.sink.write(target, level, args)
147                        },
148                        None => { false }
149                    }
150                }
151            }
152        }
153
154    } else {
155        use std::sync::Mutex;
156
157        lazy_static::lazy_static! {
158            static ref LEVEL_FILTER : Mutex<LevelFilter> = Mutex::new(LevelFilter::Info);
159        }
160        #[inline(always)]
161        /// Returns true if the current log level is below the
162        /// currently set [`LevelFilter`]
163        pub fn log_level_enabled(level: Level) -> bool {
164            *LEVEL_FILTER.lock().unwrap() >= level
165        }
166        /// Enable filtering of log messages using the [`LevelFilter`]
167        pub fn set_log_level(level: LevelFilter) {
168            *LEVEL_FILTER.lock().unwrap() = level;
169        }
170        cfg_if! {
171            if #[cfg(feature = "sink")] {
172                lazy_static::lazy_static! {
173                    static ref SINK : Mutex<Option<SinkHandler>> = Mutex::new(None);
174                }
175                // pub fn pipe(sink : Option<Arc<dyn Sink + Send + Sync + 'static>>) {
176                /// Receives an Option with an `Arc`ed [`Sink`] trait reference
177                /// and installs it as a log sink / receiver.
178                /// The sink can be later disabled by invoking `pipe(None)`
179                pub fn pipe(sink : Option<Arc<dyn Sink>>) {
180                    match sink {
181                        Some(sink) => { *SINK.lock().unwrap() = Some(SinkHandler { sink }); },
182                        None => { *SINK.lock().unwrap() = None; }
183                    }
184
185                }
186                #[inline(always)]
187                fn to_sink(target : Option<&str>, level : Level, args : &fmt::Arguments<'_>) -> bool {
188                    match SINK.lock().unwrap().as_ref() {
189                        Some(handler) => {
190                            handler.sink.write(target, level, args)
191                        },
192                        None => { false }
193                    }
194                }
195            }
196        }
197
198        #[cfg(feature = "external-logger")]
199        mod workflow_logger {
200            use log::{ Level, LevelFilter, Record, Metadata, SetLoggerError };
201
202            pub struct WorkflowLogger;
203
204            impl log::Log for WorkflowLogger {
205                fn enabled(&self, metadata: &Metadata) -> bool {
206                    super::log_level_enabled(metadata.level())
207                }
208
209                fn log(&self, record: &Record) {
210                    if self.enabled(record.metadata()) {
211                        match record.metadata().level() {
212                            Level::Error => { super::error_impl(record.args()); },
213                            Level::Warn => { super::warn_impl(record.args()); },
214                            Level::Info => { super::info_impl(record.args()); },
215                            Level::Debug => { super::debug_impl(record.args()); },
216                            Level::Trace => { super::trace_impl(record.args()); },
217                        }
218                    }
219                }
220
221                fn flush(&self) {}
222            }
223
224            static LOGGER: WorkflowLogger = WorkflowLogger;
225
226            pub fn init() -> Result<(), SetLoggerError> {
227                log::set_logger(&LOGGER)
228                    .map(|()| log::set_max_level(LevelFilter::Trace))
229            }
230        }
231
232        #[cfg(feature = "external-logger")]
233        pub fn init() -> Result<(), log::SetLoggerError> {
234            workflow_logger::init()
235        }
236
237    }
238}
239
240#[cfg(target_arch = "wasm32")]
241pub mod wasm_log {
242    use wasm_bindgen::prelude::*;
243
244    #[wasm_bindgen]
245    extern "C" {
246        #[wasm_bindgen(js_namespace = console)]
247        pub fn log(s: &str);
248        #[wasm_bindgen(js_namespace = console)]
249        pub fn warn(s: &str);
250        #[wasm_bindgen(js_namespace = console)]
251        pub fn error(s: &str);
252    }
253}
254
255pub mod impls {
256    use super::*;
257
258    #[inline(always)]
259    #[allow(unused_variables)]
260    pub fn error_impl(target: Option<&str>, args: &fmt::Arguments<'_>) {
261        if log_level_enabled(Level::Error) {
262            #[cfg(all(not(target_arch = "bpf"), feature = "sink"))]
263            {
264                if to_sink(target, Level::Error, args) {
265                    return;
266                }
267            }
268            cfg_if! {
269                if #[cfg(target_arch = "wasm32")] {
270                    workflow_log::wasm_log::error(&args.to_string());
271                } else if #[cfg(target_arch = "bpf")] {
272                    solana_program::log::sol_log(&args.to_string());
273                } else {
274                    println!("{args}");
275                }
276            }
277        }
278    }
279
280    #[inline(always)]
281    #[allow(unused_variables)]
282    pub fn warn_impl(target: Option<&str>, args: &fmt::Arguments<'_>) {
283        if log_level_enabled(Level::Warn) {
284            #[cfg(all(not(target_arch = "bpf"), feature = "sink"))]
285            {
286                if to_sink(target, Level::Warn, args) {
287                    return;
288                }
289            }
290            cfg_if! {
291                if #[cfg(target_arch = "wasm32")] {
292                    workflow_log::wasm_log::warn(&args.to_string());
293                } else if #[cfg(target_arch = "bpf")] {
294                    solana_program::log::sol_log(&args.to_string());
295                } else {
296                    println!("{args}");
297                }
298            }
299        }
300    }
301
302    #[inline(always)]
303    #[allow(unused_variables)]
304    pub fn info_impl(target: Option<&str>, args: &fmt::Arguments<'_>) {
305        if log_level_enabled(Level::Info) {
306            #[cfg(all(not(target_arch = "bpf"), feature = "sink"))]
307            {
308                if to_sink(target, Level::Info, args) {
309                    return;
310                }
311            }
312            cfg_if! {
313                if #[cfg(target_arch = "wasm32")] {
314                    workflow_log::wasm_log::log(&args.to_string());
315                } else if #[cfg(target_arch = "bpf")] {
316                    solana_program::log::sol_log(&args.to_string());
317                } else {
318                    println!("{args}");
319                }
320            }
321        }
322    }
323
324    #[inline(always)]
325    #[allow(unused_variables)]
326    pub fn debug_impl(target: Option<&str>, args: &fmt::Arguments<'_>) {
327        if log_level_enabled(Level::Debug) {
328            #[cfg(all(not(target_arch = "bpf"), feature = "sink"))]
329            {
330                if to_sink(target, Level::Debug, args) {
331                    return;
332                }
333            }
334            cfg_if! {
335                if #[cfg(target_arch = "wasm32")] {
336                    workflow_log::wasm_log::log(&args.to_string());
337                } else if #[cfg(target_arch = "bpf")] {
338                    solana_program::log::sol_log(&args.to_string());
339                } else {
340                    println!("{args}");
341                }
342            }
343        }
344    }
345
346    #[inline(always)]
347    #[allow(unused_variables)]
348    pub fn trace_impl(target: Option<&str>, args: &fmt::Arguments<'_>) {
349        if log_level_enabled(Level::Trace) {
350            #[cfg(all(not(target_arch = "bpf"), feature = "sink"))]
351            {
352                if to_sink(target, Level::Trace, args) {
353                    return;
354                }
355            }
356            cfg_if! {
357                if #[cfg(target_arch = "wasm32")] {
358                    workflow_log::wasm_log::log(&args.to_string());
359                } else if #[cfg(target_arch = "bpf")] {
360                    solana_program::log::sol_log(&args.to_string());
361                } else {
362                    println!("{args}");
363                }
364            }
365        }
366    }
367}
368
369/// Format and log message with [`Level::Error`]
370#[macro_export]
371macro_rules! log_error {
372    (target: $target:expr, $($arg:tt)+) => (
373        workflow_log::impls::error_impl(Some($target),&format_args!($($t)*))
374    );
375
376    ($($t:tt)*) => (
377        workflow_log::impls::error_impl(None,&format_args!($($t)*))
378    )
379}
380
381/// Format and log message with [`Level::Warn`]
382#[macro_export]
383macro_rules! log_warn {
384    (target: $target:expr, $($arg:tt)+) => (
385        workflow_log::impls::warn_impl(Some($target),&format_args!($($t)*))
386    );
387
388    ($($t:tt)*) => (
389        workflow_log::impls::warn_impl(None,&format_args!($($t)*))
390    )
391}
392
393/// Format and log message with [`Level::Info`]
394#[macro_export]
395macro_rules! log_info {
396    (target: $target:expr, $($arg:tt)+) => (
397        workflow_log::impls::info_impl(Some($target),&format_args!($($t)*))
398    );
399
400    ($($t:tt)*) => (
401        workflow_log::impls::info_impl(None,&format_args!($($t)*))
402    )
403}
404
405/// Format and log message with [`Level::Debug`]
406#[macro_export]
407macro_rules! log_debug {
408    (target: $target:expr, $($arg:tt)+) => (
409        workflow_log::impls::debug_impl(Some($target),&format_args!($($t)*))
410    );
411
412    ($($t:tt)*) => (
413        workflow_log::impls::debug_impl(None,&format_args!($($t)*))
414    )
415}
416
417/// Format and log message with [`Level::Trace`]
418#[macro_export]
419macro_rules! log_trace {
420    (target: $target:expr, $($arg:tt)+) => (
421        workflow_log::impls::trace_impl(Some($target),&format_args!($($t)*))
422    );
423
424    ($($t:tt)*) => (
425        workflow_log::impls::trace_impl(None,&format_args!($($t)*))
426    )
427}
428
429pub use log_debug;
430pub use log_error;
431pub use log_info;
432pub use log_trace;
433pub use log_warn;
434
435/// Prints (using [`log_trace`]) a data slice
436/// formatted as a hex data dump.
437#[cfg(not(target_arch = "bpf"))]
438pub fn trace_hex(data: &[u8]) {
439    let hex = format_hex(data);
440    log_trace!("{}", hex);
441}
442
443/// Returns a string formatted as a hex data dump
444/// of the supplied slice argument.
445#[cfg(not(target_arch = "bpf"))]
446pub fn format_hex(data: &[u8]) -> String {
447    let view = hexplay::HexViewBuilder::new(data)
448        .address_offset(0)
449        .row_width(16)
450        .finish();
451
452    format!("{view}")
453}
454
455/// Formats a hex data dump to contain color ranges
456#[cfg(not(target_arch = "bpf"))]
457pub fn format_hex_with_colors<'a>(
458    data: &'a [u8],
459    colors: Vec<(&'a str, usize)>,
460) -> ColorHexView<'a> {
461    let view_builder = hexplay::HexViewBuilder::new(data)
462        .address_offset(0)
463        .row_width(16);
464
465    ColorHexView::new(view_builder, colors)
466}
467#[cfg(not(target_arch = "bpf"))]
468pub mod color_log {
469    use super::*;
470    type Index = usize;
471    type Length = usize;
472    type Color<'a> = &'a str;
473    type Result<T> = std::result::Result<T, String>;
474    pub trait ColoLogTrace {
475        fn log_data(&self) -> Vec<u8>;
476        fn log_index_length_color(&self) -> Option<Vec<(Index, Length, Color)>> {
477            None
478        }
479
480        fn log_trace(&self) -> Result<bool> {
481            let data_vec = self.log_data();
482            let mut view = format_hex_with_colors(&data_vec, vec![]);
483            if let Some(index_length_color) = self.log_index_length_color() {
484                let mut colors = Vec::new();
485                for (index, length, color) in index_length_color {
486                    colors.push((color, index..index + length));
487                }
488                view = view.add_colors_with_range(colors);
489            }
490
491            if view.try_print().is_err() {
492                trace_hex(&data_vec);
493                return Ok(false);
494            }
495            Ok(true)
496        }
497    }
498}
499
500#[cfg(not(target_arch = "bpf"))]
501pub use color_log::*;