From 6c246a9ce221b36d65c64bf64cebba91c73a9386 Mon Sep 17 00:00:00 2001 From: Xhristin3 Date: Fri, 6 Mar 2026 14:29:05 -0800 Subject: [PATCH 1/3] feat: add structured logging with tracing ecosystem --- Cargo.toml | 2 ++ worker/Cargo.toml | 2 ++ worker/src/logging.rs | 30 ++++++++++++++++++++++++++++++ worker/src/main.rs | 36 +++++++++++++++++++++++++++++++++--- 4 files changed, 67 insertions(+), 3 deletions(-) create mode 100644 worker/src/logging.rs diff --git a/Cargo.toml b/Cargo.toml index ef72894..a5d05b4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,3 +24,5 @@ thiserror = "1" tokio = { version = "1", features = ["rt-multi-thread", "macros"] } stellar-strkey = "0.0.8" ed25519-dalek = "2" +tracing = "0.1" +tracing-subscriber = { version = "0.3", features = ["env-filter", "json"] } diff --git a/worker/Cargo.toml b/worker/Cargo.toml index df0dacc..3b589ff 100644 --- a/worker/Cargo.toml +++ b/worker/Cargo.toml @@ -15,3 +15,5 @@ thiserror = "1" tokio = { version = "1", features = ["rt-multi-thread", "macros"] } stellar-strkey = "0.0.8" ed25519-dalek = "2" +tracing = "0.1" +tracing-subscriber = { version = "0.3", features = ["env-filter", "json"] } diff --git a/worker/src/logging.rs b/worker/src/logging.rs new file mode 100644 index 0000000..b21a6b3 --- /dev/null +++ b/worker/src/logging.rs @@ -0,0 +1,30 @@ +use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt, EnvFilter, fmt}; +use std::env; + +pub fn init_logging() -> Result<(), Box> { + let log_level = env::var("LOG_LEVEL").unwrap_or_else(|_| "info".to_string()); + + let env_filter = EnvFilter::try_from_default_env() + .unwrap_or_else(|_| EnvFilter::new(&log_level)); + + let is_production = env::var("RUST_ENV").unwrap_or_else(|_| "development".to_string()) == "production"; + + if is_production { + tracing_subscriber::registry() + .with(env_filter) + .with(fmt::layer().json().with_current_span(true)) + .init(); + } else { + tracing_subscriber::registry() + .with(env_filter) + .with(fmt::layer() + .pretty() + .with_target(true) + .with_thread_ids(true) + .with_file(true) + .with_line_number(true)) + .init(); + } + + Ok(()) +} diff --git a/worker/src/main.rs b/worker/src/main.rs index b2987e7..ecc33a7 100644 --- a/worker/src/main.rs +++ b/worker/src/main.rs @@ -1,5 +1,8 @@ // Contract Fox Worker - Background service for contract management +mod logging; + use serde::{Deserialize, Serialize}; +use tracing::{info, warn, error, debug, instrument}; #[derive(Debug, Serialize, Deserialize)] pub struct WorkerConfig { @@ -18,18 +21,45 @@ impl Default for WorkerConfig { } } +#[instrument(skip(config), fields(rpc_url = %config.rpc_url, network = %config.network, poll_interval = config.poll_interval))] pub async fn run_worker(config: WorkerConfig) -> Result<(), Box> { - println!("Starting worker with config: {:?}", config); + info!("Starting contract fox worker"); loop { - println!("Polling for contract updates..."); - tokio::time::sleep(tokio::time::Duration::from_secs(config.poll_interval)).await; + debug!("Polling for contract updates"); + + match tokio::time::timeout( + tokio::time::Duration::from_secs(config.poll_interval), + check_contracts(&config) + ).await { + Ok(result) => { + if let Err(e) = result { + error!("Error checking contracts: {}", e); + } + } + Err(_) => { + debug!("Polling timeout reached, continuing to next iteration"); + } + } } } +#[instrument(skip(config))] +async fn check_contracts(config: &WorkerConfig) -> Result<(), Box> { + debug!("Checking contracts on network: {}", config.network); + + tokio::time::sleep(tokio::time::Duration::from_millis(100)).await; + + Ok(()) +} + #[tokio::main] async fn main() -> Result<(), Box> { + logging::init_logging()?; + let config = WorkerConfig::default(); + info!("Worker initialized with default configuration"); + run_worker(config).await } From c91038f8f6b3f6ace38baa0985ce74d7f99d1644 Mon Sep 17 00:00:00 2001 From: Xhristin3 Date: Fri, 6 Mar 2026 14:52:58 -0800 Subject: [PATCH 2/3] feat: implement structured logging with tracing ecosystem --- Cargo.lock | 133 ++++++++++++++++++++++++++++++++++++++++ worker/src/logging.rs | 14 ++++- worker/src/main.rs | 14 ++++- worker/src/redaction.rs | 57 +++++++++++++++++ 4 files changed, 214 insertions(+), 4 deletions(-) create mode 100644 worker/src/redaction.rs diff --git a/Cargo.lock b/Cargo.lock index f332f6a..8d505a3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,6 +17,15 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +[[package]] +name = "aho-corasick" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] + [[package]] name = "android_system_properties" version = "0.1.5" @@ -191,6 +200,8 @@ dependencies = [ "stellar-strkey", "thiserror", "tokio", + "tracing", + "tracing-subscriber", ] [[package]] @@ -213,6 +224,8 @@ dependencies = [ "stellar-strkey", "thiserror", "tokio", + "tracing", + "tracing-subscriber", ] [[package]] @@ -1082,6 +1095,12 @@ dependencies = [ "cpufeatures", ] +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + [[package]] name = "libc" version = "0.2.182" @@ -1112,6 +1131,15 @@ version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" +[[package]] +name = "matchers" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" +dependencies = [ + "regex-automata", +] + [[package]] name = "memchr" version = "2.8.0" @@ -1161,6 +1189,15 @@ dependencies = [ "tempfile", ] +[[package]] +name = "nu-ansi-term" +version = "0.50.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" +dependencies = [ + "windows-sys 0.61.2", +] + [[package]] name = "num-bigint" version = "0.4.4" @@ -1401,6 +1438,23 @@ dependencies = [ "getrandom 0.2.11", ] +[[package]] +name = "regex-automata" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" + [[package]] name = "reqwest" version = "0.12.28" @@ -1686,6 +1740,15 @@ dependencies = [ "keccak", ] +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + [[package]] name = "shlex" version = "1.3.0" @@ -2053,6 +2116,15 @@ dependencies = [ "syn", ] +[[package]] +name = "thread_local" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" +dependencies = [ + "cfg-if", +] + [[package]] name = "time" version = "0.3.44" @@ -2205,9 +2277,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" dependencies = [ "pin-project-lite", + "tracing-attributes", "tracing-core", ] +[[package]] +name = "tracing-attributes" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "tracing-core" version = "0.1.36" @@ -2215,6 +2299,49 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" dependencies = [ "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-serde" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "704b1aeb7be0d0a84fc9828cae51dab5970fee5088f83d1dd7ee6f6246fc6ff1" +dependencies = [ + "serde", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f30143827ddab0d256fd843b7a66d164e9f271cfa0dde49142c5ca0ca291f1e" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex-automata", + "serde", + "serde_json", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", + "tracing-serde", ] [[package]] @@ -2259,6 +2386,12 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + [[package]] name = "vcpkg" version = "0.2.15" diff --git a/worker/src/logging.rs b/worker/src/logging.rs index b21a6b3..a2b1b79 100644 --- a/worker/src/logging.rs +++ b/worker/src/logging.rs @@ -4,8 +4,16 @@ use std::env; pub fn init_logging() -> Result<(), Box> { let log_level = env::var("LOG_LEVEL").unwrap_or_else(|_| "info".to_string()); - let env_filter = EnvFilter::try_from_default_env() - .unwrap_or_else(|_| EnvFilter::new(&log_level)); + eprintln!("DEBUG: Setting log level to: {}", log_level); + + let filter_str = format!("worker={}", log_level); + eprintln!("DEBUG: Using filter string: {}", filter_str); + + let env_filter = EnvFilter::try_from_str(&filter_str) + .unwrap_or_else(|e| { + eprintln!("DEBUG: Failed to create filter from '{}', using default: {}", filter_str, e); + EnvFilter::new("worker=info") + }); let is_production = env::var("RUST_ENV").unwrap_or_else(|_| "development".to_string()) == "production"; @@ -26,5 +34,7 @@ pub fn init_logging() -> Result<(), Box> { .init(); } + tracing::info!("Logging initialized - level: {}, mode: {}", log_level, if is_production { "production" } else { "development" }); + Ok(()) } diff --git a/worker/src/main.rs b/worker/src/main.rs index ecc33a7..62957f4 100644 --- a/worker/src/main.rs +++ b/worker/src/main.rs @@ -1,5 +1,6 @@ // Contract Fox Worker - Background service for contract management mod logging; +mod redaction; use serde::{Deserialize, Serialize}; use tracing::{info, warn, error, debug, instrument}; @@ -11,6 +12,16 @@ pub struct WorkerConfig { pub network: String, } +impl WorkerConfig { + pub fn log_safe(&self) -> serde_json::Value { + serde_json::json!({ + "rpc_url": self.rpc_url, + "poll_interval": self.poll_interval, + "network": self.network + }) + } +} + impl Default for WorkerConfig { fn default() -> Self { Self { @@ -21,9 +32,9 @@ impl Default for WorkerConfig { } } -#[instrument(skip(config), fields(rpc_url = %config.rpc_url, network = %config.network, poll_interval = config.poll_interval))] pub async fn run_worker(config: WorkerConfig) -> Result<(), Box> { info!("Starting contract fox worker"); + debug!("Worker configuration: {}", serde_json::to_string_pretty(&config.log_safe()).unwrap_or_else(|_| "Failed to serialize config".to_string())); loop { debug!("Polling for contract updates"); @@ -44,7 +55,6 @@ pub async fn run_worker(config: WorkerConfig) -> Result<(), Box Result<(), Box> { debug!("Checking contracts on network: {}", config.network); diff --git a/worker/src/redaction.rs b/worker/src/redaction.rs new file mode 100644 index 0000000..97cb643 --- /dev/null +++ b/worker/src/redaction.rs @@ -0,0 +1,57 @@ +use tracing::instrument; + +pub fn redact_secret(secret: &str) -> String { + if secret.len() <= 8 { + "*".repeat(secret.len()) + } else { + format!("{}...{}", &secret[..4], &secret[secret.len()-4..]) + } +} + +pub fn redact_if_secret(key: &str, value: &str) -> String { + let lower_key = key.to_lowercase(); + if lower_key.contains("secret") || + lower_key.contains("private") || + lower_key.contains("key") || + lower_key.contains("password") || + lower_key.contains("token") || + lower_key.contains("credential") { + redact_secret(value) + } else { + value.to_string() + } +} + +#[instrument(skip_all)] +pub fn log_safe_config(config: &serde_json::Value) -> serde_json::Value { + match config { + serde_json::Value::Object(map) => { + let mut safe_map = serde_json::Map::new(); + for (key, value) in map { + let safe_key = key.clone(); + let safe_value = match value { + serde_json::Value::String(s) => { + serde_json::Value::String(redact_if_secret(key, s)) + } + serde_json::Value::Object(_) => log_safe_config(value), + serde_json::Value::Array(arr) => { + let safe_arr: Vec = arr.iter() + .map(|v| log_safe_config(v)) + .collect(); + serde_json::Value::Array(safe_arr) + } + _ => value.clone(), + }; + safe_map.insert(safe_key, safe_value); + } + serde_json::Value::Object(safe_map) + } + serde_json::Value::Array(arr) => { + let safe_arr: Vec = arr.iter() + .map(|v| log_safe_config(v)) + .collect(); + serde_json::Value::Array(safe_arr) + } + _ => config.clone(), + } +} From 8ebb076eaf9aed0d155f104d37b840bbf4ffd93a Mon Sep 17 00:00:00 2001 From: Xhristin3 Date: Fri, 6 Mar 2026 15:03:31 -0800 Subject: [PATCH 3/3] fix: ensure CI workflow compatibility --- worker/src/logging.rs | 42 ++++++++++++++++++++++------------------- worker/src/main.rs | 24 ++++++++++++++--------- worker/src/redaction.rs | 24 +++++++++++------------ 3 files changed, 49 insertions(+), 41 deletions(-) diff --git a/worker/src/logging.rs b/worker/src/logging.rs index a2b1b79..c2c8db8 100644 --- a/worker/src/logging.rs +++ b/worker/src/logging.rs @@ -1,21 +1,15 @@ -use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt, EnvFilter, fmt}; use std::env; +use tracing_subscriber::{EnvFilter, fmt, layer::SubscriberExt, util::SubscriberInitExt}; pub fn init_logging() -> Result<(), Box> { let log_level = env::var("LOG_LEVEL").unwrap_or_else(|_| "info".to_string()); - - eprintln!("DEBUG: Setting log level to: {}", log_level); - + let filter_str = format!("worker={}", log_level); - eprintln!("DEBUG: Using filter string: {}", filter_str); - - let env_filter = EnvFilter::try_from_str(&filter_str) - .unwrap_or_else(|e| { - eprintln!("DEBUG: Failed to create filter from '{}', using default: {}", filter_str, e); - EnvFilter::new("worker=info") - }); + let env_filter = + EnvFilter::try_from_str(&filter_str).unwrap_or_else(|_| EnvFilter::new("worker=info")); - let is_production = env::var("RUST_ENV").unwrap_or_else(|_| "development".to_string()) == "production"; + let is_production = + env::var("RUST_ENV").unwrap_or_else(|_| "development".to_string()) == "production"; if is_production { tracing_subscriber::registry() @@ -25,16 +19,26 @@ pub fn init_logging() -> Result<(), Box> { } else { tracing_subscriber::registry() .with(env_filter) - .with(fmt::layer() - .pretty() - .with_target(true) - .with_thread_ids(true) - .with_file(true) - .with_line_number(true)) + .with( + fmt::layer() + .pretty() + .with_target(true) + .with_thread_ids(true) + .with_file(true) + .with_line_number(true), + ) .init(); } - tracing::info!("Logging initialized - level: {}, mode: {}", log_level, if is_production { "production" } else { "development" }); + tracing::info!( + "Logging initialized - level: {}, mode: {}", + log_level, + if is_production { + "production" + } else { + "development" + } + ); Ok(()) } diff --git a/worker/src/main.rs b/worker/src/main.rs index 62957f4..00d892a 100644 --- a/worker/src/main.rs +++ b/worker/src/main.rs @@ -3,7 +3,7 @@ mod logging; mod redaction; use serde::{Deserialize, Serialize}; -use tracing::{info, warn, error, debug, instrument}; +use tracing::{debug, error, info, instrument, warn}; #[derive(Debug, Serialize, Deserialize)] pub struct WorkerConfig { @@ -34,15 +34,21 @@ impl Default for WorkerConfig { pub async fn run_worker(config: WorkerConfig) -> Result<(), Box> { info!("Starting contract fox worker"); - debug!("Worker configuration: {}", serde_json::to_string_pretty(&config.log_safe()).unwrap_or_else(|_| "Failed to serialize config".to_string())); + debug!( + "Worker configuration: {}", + serde_json::to_string_pretty(&config.log_safe()) + .unwrap_or_else(|_| "Failed to serialize config".to_string()) + ); loop { debug!("Polling for contract updates"); - + match tokio::time::timeout( tokio::time::Duration::from_secs(config.poll_interval), - check_contracts(&config) - ).await { + check_contracts(&config), + ) + .await + { Ok(result) => { if let Err(e) = result { error!("Error checking contracts: {}", e); @@ -57,19 +63,19 @@ pub async fn run_worker(config: WorkerConfig) -> Result<(), Box Result<(), Box> { debug!("Checking contracts on network: {}", config.network); - + tokio::time::sleep(tokio::time::Duration::from_millis(100)).await; - + Ok(()) } #[tokio::main] async fn main() -> Result<(), Box> { logging::init_logging()?; - + let config = WorkerConfig::default(); info!("Worker initialized with default configuration"); - + run_worker(config).await } diff --git a/worker/src/redaction.rs b/worker/src/redaction.rs index 97cb643..7da9601 100644 --- a/worker/src/redaction.rs +++ b/worker/src/redaction.rs @@ -4,18 +4,19 @@ pub fn redact_secret(secret: &str) -> String { if secret.len() <= 8 { "*".repeat(secret.len()) } else { - format!("{}...{}", &secret[..4], &secret[secret.len()-4..]) + format!("{}...{}", &secret[..4], &secret[secret.len() - 4..]) } } pub fn redact_if_secret(key: &str, value: &str) -> String { let lower_key = key.to_lowercase(); - if lower_key.contains("secret") || - lower_key.contains("private") || - lower_key.contains("key") || - lower_key.contains("password") || - lower_key.contains("token") || - lower_key.contains("credential") { + if lower_key.contains("secret") + || lower_key.contains("private") + || lower_key.contains("key") + || lower_key.contains("password") + || lower_key.contains("token") + || lower_key.contains("credential") + { redact_secret(value) } else { value.to_string() @@ -35,9 +36,8 @@ pub fn log_safe_config(config: &serde_json::Value) -> serde_json::Value { } serde_json::Value::Object(_) => log_safe_config(value), serde_json::Value::Array(arr) => { - let safe_arr: Vec = arr.iter() - .map(|v| log_safe_config(v)) - .collect(); + let safe_arr: Vec = + arr.iter().map(|v| log_safe_config(v)).collect(); serde_json::Value::Array(safe_arr) } _ => value.clone(), @@ -47,9 +47,7 @@ pub fn log_safe_config(config: &serde_json::Value) -> serde_json::Value { serde_json::Value::Object(safe_map) } serde_json::Value::Array(arr) => { - let safe_arr: Vec = arr.iter() - .map(|v| log_safe_config(v)) - .collect(); + let safe_arr: Vec = arr.iter().map(|v| log_safe_config(v)).collect(); serde_json::Value::Array(safe_arr) } _ => config.clone(),