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