use cfg_if::cfg_if;
use std::fmt;
cfg_if! {
if #[cfg(target_arch = "bpf")] {
pub use workflow_log::levels::{ Level, LevelFilter };
} else {
use std::sync::Arc;
pub use log::{ Level, LevelFilter };
use downcast::{ downcast_sync, AnySync };
pub use hexplay::{self, HexViewBuilder};
pub use termcolor::Buffer;
pub struct ColorHexView<'a>{
pub builder: HexViewBuilder<'a>,
pub color_start: usize
}
impl<'a> ColorHexView<'a>{
pub fn new(builder:HexViewBuilder<'a>, colors:Vec<(&'a str, usize)>)->Self{
Self{
builder,
color_start:0
}.add_colors(colors)
}
pub fn add_colors(mut self, colors:Vec<(&'a str, usize)>)->Self{
let mut builder = self.builder;
for (color, len) in colors{
let end = self.color_start+len;
let range = self.color_start..end;
self.color_start = end;
builder = builder.add_color(color, range);
}
self.builder = builder;
self
}
pub fn add_colors_with_range(mut self, colors:Vec<(&'a str, std::ops::Range<usize>)>)->Self{
let mut builder = self.builder;
for (color, range) in colors{
builder = builder.add_color(color, range);
}
self.builder = builder;
self
}
pub fn try_print(self)->std::result::Result<(), String>{
let mut buf = Buffer::ansi();
match self.builder.finish().fmt(&mut buf){
Ok(()) => {
match String::from_utf8(buf.as_slice().to_vec()){
Ok(str)=>{
log_trace!("{}", str);
}
Err(_)=>{
return Err("Unable to convert HexView to string".to_string());
}
}
},
Err(_) => {
return Err("Unable to format HexView".to_string());
}
}
Ok(())
}
}
pub trait Sink : AnySync {
fn write(&self, target: Option<&str>, level : Level, args : &fmt::Arguments<'_>) -> bool;
}
struct SinkHandler {
sink : Arc<dyn Sink>, }
downcast_sync!(dyn Sink);
}
}
cfg_if! {
if #[cfg(target_arch = "bpf")] {
#[inline(always)]
pub fn log_level_enabled(_level: Level) -> bool {
true
}
} else if #[cfg(target_arch = "wasm32")] {
use wasm_bindgen::prelude::*;
static mut LEVEL_FILTER : LevelFilter = LevelFilter::Info;
#[inline(always)]
pub fn log_level_enabled(level: Level) -> bool {
unsafe { LEVEL_FILTER >= level }
}
pub fn set_log_level(level: LevelFilter) {
unsafe { LEVEL_FILTER = level };
}
#[wasm_bindgen]
extern "C" {
#[wasm_bindgen(typescript_type = r###""off" | "error" | "warn" | "info" | "debug" | "trace""###)]
#[derive(Debug)]
pub type LogLevelT;
}
#[doc="Set the logger log level using a string representation."]
#[doc="Available variants are: 'off', 'error', 'warn', 'info', 'debug', 'trace'"]
#[doc="@category General"]
#[wasm_bindgen(js_name = "setLogLevel")]
pub fn set_log_level_wasm(level: LogLevelT) {
if let Some(level) = level.as_string() {
let level = match level.as_str() {
"off" => LevelFilter::Off,
"error" => LevelFilter::Error,
"warn" => LevelFilter::Warn,
"info" => LevelFilter::Info,
"debug" => LevelFilter::Debug,
"trace" => LevelFilter::Trace,
_ => panic!("Invalid log level: {level}"),
};
set_log_level(level);
} else {
panic!("log level must be a string, received: {level:?}");
}
}
cfg_if! {
if #[cfg(feature = "sink")] {
use std::sync::Mutex;
static SINK : Mutex<Option<SinkHandler>> = Mutex::new(None);
pub fn pipe(sink : Option<Arc<dyn Sink>>) {
match sink {
Some(sink) => { *SINK.lock().unwrap() = Some(SinkHandler { sink }); },
None => { *SINK.lock().unwrap() = None; }
}
}
#[inline(always)]
fn to_sink(target: Option<&str>, level : Level, args : &fmt::Arguments<'_>) -> bool {
match SINK.lock().unwrap().as_ref() {
Some(handler) => {
handler.sink.write(target, level, args)
},
None => { false }
}
}
}
}
} else {
use std::sync::Mutex;
lazy_static::lazy_static! {
static ref LEVEL_FILTER : Mutex<LevelFilter> = Mutex::new(LevelFilter::Info);
}
#[inline(always)]
pub fn log_level_enabled(level: Level) -> bool {
*LEVEL_FILTER.lock().unwrap() >= level
}
pub fn set_log_level(level: LevelFilter) {
*LEVEL_FILTER.lock().unwrap() = level;
}
cfg_if! {
if #[cfg(feature = "sink")] {
lazy_static::lazy_static! {
static ref SINK : Mutex<Option<SinkHandler>> = Mutex::new(None);
}
pub fn pipe(sink : Option<Arc<dyn Sink>>) {
match sink {
Some(sink) => { *SINK.lock().unwrap() = Some(SinkHandler { sink }); },
None => { *SINK.lock().unwrap() = None; }
}
}
#[inline(always)]
fn to_sink(target : Option<&str>, level : Level, args : &fmt::Arguments<'_>) -> bool {
match SINK.lock().unwrap().as_ref() {
Some(handler) => {
handler.sink.write(target, level, args)
},
None => { false }
}
}
}
}
#[cfg(feature = "external-logger")]
mod workflow_logger {
use log::{ Level, LevelFilter, Record, Metadata, SetLoggerError };
pub struct WorkflowLogger;
impl log::Log for WorkflowLogger {
fn enabled(&self, metadata: &Metadata) -> bool {
super::log_level_enabled(metadata.level())
}
fn log(&self, record: &Record) {
if self.enabled(record.metadata()) {
match record.metadata().level() {
Level::Error => { super::error_impl(record.args()); },
Level::Warn => { super::warn_impl(record.args()); },
Level::Info => { super::info_impl(record.args()); },
Level::Debug => { super::debug_impl(record.args()); },
Level::Trace => { super::trace_impl(record.args()); },
}
}
}
fn flush(&self) {}
}
static LOGGER: WorkflowLogger = WorkflowLogger;
pub fn init() -> Result<(), SetLoggerError> {
log::set_logger(&LOGGER)
.map(|()| log::set_max_level(LevelFilter::Trace))
}
}
#[cfg(feature = "external-logger")]
pub fn init() -> Result<(), log::SetLoggerError> {
workflow_logger::init()
}
}
}
#[cfg(target_arch = "wasm32")]
pub mod wasm_log {
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
extern "C" {
#[wasm_bindgen(js_namespace = console)]
pub fn log(s: &str);
#[wasm_bindgen(js_namespace = console)]
pub fn warn(s: &str);
#[wasm_bindgen(js_namespace = console)]
pub fn error(s: &str);
}
}
pub mod impls {
use super::*;
#[inline(always)]
#[allow(unused_variables)]
pub fn error_impl(target: Option<&str>, args: &fmt::Arguments<'_>) {
if log_level_enabled(Level::Error) {
#[cfg(all(not(target_arch = "bpf"), feature = "sink"))]
{
if to_sink(target, Level::Error, args) {
return;
}
}
cfg_if! {
if #[cfg(target_arch = "wasm32")] {
workflow_log::wasm_log::error(&args.to_string());
} else if #[cfg(target_arch = "bpf")] {
solana_program::log::sol_log(&args.to_string());
} else {
println!("{args}");
}
}
}
}
#[inline(always)]
#[allow(unused_variables)]
pub fn warn_impl(target: Option<&str>, args: &fmt::Arguments<'_>) {
if log_level_enabled(Level::Warn) {
#[cfg(all(not(target_arch = "bpf"), feature = "sink"))]
{
if to_sink(target, Level::Warn, args) {
return;
}
}
cfg_if! {
if #[cfg(target_arch = "wasm32")] {
workflow_log::wasm_log::warn(&args.to_string());
} else if #[cfg(target_arch = "bpf")] {
solana_program::log::sol_log(&args.to_string());
} else {
println!("{args}");
}
}
}
}
#[inline(always)]
#[allow(unused_variables)]
pub fn info_impl(target: Option<&str>, args: &fmt::Arguments<'_>) {
if log_level_enabled(Level::Info) {
#[cfg(all(not(target_arch = "bpf"), feature = "sink"))]
{
if to_sink(target, Level::Info, args) {
return;
}
}
cfg_if! {
if #[cfg(target_arch = "wasm32")] {
workflow_log::wasm_log::log(&args.to_string());
} else if #[cfg(target_arch = "bpf")] {
solana_program::log::sol_log(&args.to_string());
} else {
println!("{args}");
}
}
}
}
#[inline(always)]
#[allow(unused_variables)]
pub fn debug_impl(target: Option<&str>, args: &fmt::Arguments<'_>) {
if log_level_enabled(Level::Debug) {
#[cfg(all(not(target_arch = "bpf"), feature = "sink"))]
{
if to_sink(target, Level::Debug, args) {
return;
}
}
cfg_if! {
if #[cfg(target_arch = "wasm32")] {
workflow_log::wasm_log::log(&args.to_string());
} else if #[cfg(target_arch = "bpf")] {
solana_program::log::sol_log(&args.to_string());
} else {
println!("{args}");
}
}
}
}
#[inline(always)]
#[allow(unused_variables)]
pub fn trace_impl(target: Option<&str>, args: &fmt::Arguments<'_>) {
if log_level_enabled(Level::Trace) {
#[cfg(all(not(target_arch = "bpf"), feature = "sink"))]
{
if to_sink(target, Level::Trace, args) {
return;
}
}
cfg_if! {
if #[cfg(target_arch = "wasm32")] {
workflow_log::wasm_log::log(&args.to_string());
} else if #[cfg(target_arch = "bpf")] {
solana_program::log::sol_log(&args.to_string());
} else {
println!("{args}");
}
}
}
}
}
#[macro_export]
macro_rules! log_error {
(target: $target:expr, $($arg:tt)+) => (
workflow_log::impls::error_impl(Some($target),&format_args!($($t)*))
);
($($t:tt)*) => (
workflow_log::impls::error_impl(None,&format_args!($($t)*))
)
}
#[macro_export]
macro_rules! log_warn {
(target: $target:expr, $($arg:tt)+) => (
workflow_log::impls::warn_impl(Some($target),&format_args!($($t)*))
);
($($t:tt)*) => (
workflow_log::impls::warn_impl(None,&format_args!($($t)*))
)
}
#[macro_export]
macro_rules! log_info {
(target: $target:expr, $($arg:tt)+) => (
workflow_log::impls::info_impl(Some($target),&format_args!($($t)*))
);
($($t:tt)*) => (
workflow_log::impls::info_impl(None,&format_args!($($t)*))
)
}
#[macro_export]
macro_rules! log_debug {
(target: $target:expr, $($arg:tt)+) => (
workflow_log::impls::debug_impl(Some($target),&format_args!($($t)*))
);
($($t:tt)*) => (
workflow_log::impls::debug_impl(None,&format_args!($($t)*))
)
}
#[macro_export]
macro_rules! log_trace {
(target: $target:expr, $($arg:tt)+) => (
workflow_log::impls::trace_impl(Some($target),&format_args!($($t)*))
);
($($t:tt)*) => (
workflow_log::impls::trace_impl(None,&format_args!($($t)*))
)
}
pub use log_debug;
pub use log_error;
pub use log_info;
pub use log_trace;
pub use log_warn;
#[cfg(not(target_arch = "bpf"))]
pub fn trace_hex(data: &[u8]) {
let hex = format_hex(data);
log_trace!("{}", hex);
}
#[cfg(not(target_arch = "bpf"))]
pub fn format_hex(data: &[u8]) -> String {
let view = hexplay::HexViewBuilder::new(data)
.address_offset(0)
.row_width(16)
.finish();
format!("{view}")
}
#[cfg(not(target_arch = "bpf"))]
pub fn format_hex_with_colors<'a>(
data: &'a [u8],
colors: Vec<(&'a str, usize)>,
) -> ColorHexView<'a> {
let view_builder = hexplay::HexViewBuilder::new(data)
.address_offset(0)
.row_width(16);
ColorHexView::new(view_builder, colors)
}
#[cfg(not(target_arch = "bpf"))]
pub mod color_log {
use super::*;
type Index = usize;
type Length = usize;
type Color<'a> = &'a str;
type Result<T> = std::result::Result<T, String>;
pub trait ColoLogTrace {
fn log_data(&self) -> Vec<u8>;
fn log_index_length_color(&self) -> Option<Vec<(Index, Length, Color)>> {
None
}
fn log_trace(&self) -> Result<bool> {
let data_vec = self.log_data();
let mut view = format_hex_with_colors(&data_vec, vec![]);
if let Some(index_length_color) = self.log_index_length_color() {
let mut colors = Vec::new();
for (index, length, color) in index_length_color {
colors.push((color, index..index + length));
}
view = view.add_colors_with_range(colors);
}
if view.try_print().is_err() {
trace_hex(&data_vec);
return Ok(false);
}
Ok(true)
}
}
}
#[cfg(not(target_arch = "bpf"))]
pub use color_log::*;