use rhai::{Dynamic, Engine};
use std::cell::RefCell;
use std::collections::{HashMap, HashSet};
use std::path::Path;
#[derive(Debug, Clone, Default)]
pub struct TrackingSnapshot {
pub user: HashMap<String, Dynamic>,
pub internal: HashMap<String, Dynamic>,
}
impl TrackingSnapshot {
pub fn from_parts(user: HashMap<String, Dynamic>, internal: HashMap<String, Dynamic>) -> Self {
Self { user, internal }
}
}
thread_local! {
pub static THREAD_TRACKING_STATE: RefCell<TrackingSnapshot> = RefCell::new(TrackingSnapshot::default());
}
pub fn get_thread_snapshot() -> TrackingSnapshot {
THREAD_TRACKING_STATE.with(|state| state.borrow().clone())
}
pub fn with_user_tracking<F, R>(f: F) -> R
where
F: FnOnce(&mut HashMap<String, Dynamic>) -> R,
{
THREAD_TRACKING_STATE.with(|state| {
let mut snapshot = state.borrow_mut();
f(&mut snapshot.user)
})
}
pub fn with_internal_tracking<F, R>(f: F) -> R
where
F: FnOnce(&mut HashMap<String, Dynamic>) -> R,
{
THREAD_TRACKING_STATE.with(|state| {
let mut snapshot = state.borrow_mut();
f(&mut snapshot.internal)
})
}
pub fn set_thread_tracking_state(metrics: &HashMap<String, Dynamic>) {
THREAD_TRACKING_STATE.with(|state| {
let mut snapshot = state.borrow_mut();
snapshot.user = metrics.clone();
});
}
pub fn get_thread_tracking_state() -> HashMap<String, Dynamic> {
THREAD_TRACKING_STATE.with(|state| state.borrow().user.clone())
}
pub fn set_thread_internal_state(metrics: &HashMap<String, Dynamic>) {
THREAD_TRACKING_STATE.with(|state| {
let mut snapshot = state.borrow_mut();
snapshot.internal = metrics.clone();
});
}
pub fn get_thread_internal_state() -> HashMap<String, Dynamic> {
THREAD_TRACKING_STATE.with(|state| state.borrow().internal.clone())
}
fn merge_numeric(existing: Option<Dynamic>, new_value: Dynamic) -> Dynamic {
let new_is_float = new_value.is_float();
if let Some(current) = existing {
let current_is_float = current.is_float();
if current_is_float || new_is_float {
let current_total = if current_is_float {
current.as_float().unwrap_or(0.0)
} else {
current.as_int().unwrap_or(0) as f64
};
let incoming = if new_is_float {
new_value.as_float().unwrap_or(0.0)
} else {
new_value.as_int().unwrap_or(0) as f64
};
Dynamic::from(current_total + incoming)
} else {
let current_total = current.as_int().unwrap_or(0);
let incoming = new_value.as_int().unwrap_or(0);
Dynamic::from(current_total + incoming)
}
} else {
new_value
}
}
fn format_error_location(
line_num: Option<usize>,
filename: Option<&str>,
input_files: &[String],
) -> String {
match (line_num, filename) {
(Some(line), Some(fname)) => {
if input_files.is_empty() || input_files.len() == 1 {
format!("line {}", line)
} else {
let basenames: HashSet<_> = input_files
.iter()
.map(|f| {
Path::new(f)
.file_name()
.and_then(|n| n.to_str())
.unwrap_or("unknown")
})
.collect();
if basenames.len() == input_files.len() {
let basename = Path::new(fname)
.file_name()
.and_then(|n| n.to_str())
.unwrap_or("unknown");
format!("{}:{}", basename, line)
} else {
format!("{}:{}", fname, line)
}
}
}
(Some(line), None) => format!("line {}", line),
_ => "unknown".to_string(),
}
}
#[allow(clippy::too_many_arguments)]
pub fn track_error(
error_type: &str,
line_num: Option<usize>,
message: &str,
original_line: Option<&str>,
filename: Option<&str>,
verbose: u8,
quiet_level: u8,
config: Option<&crate::pipeline::PipelineConfig>,
) {
with_internal_tracking(|state| {
let count_key = format!("__kelora_error_count_{}", error_type);
let current_count = state
.get(&count_key)
.cloned()
.unwrap_or(Dynamic::from(0i64));
let new_count = current_count.as_int().unwrap_or(0) + 1;
state.insert(count_key.clone(), Dynamic::from(new_count));
state.insert(format!("__op_{}", count_key), Dynamic::from("count"));
if verbose > 0 && quiet_level == 0 {
let color_mode = config
.map(|c| &c.color_mode)
.unwrap_or(&crate::config::ColorMode::Auto);
let use_colors = crate::tty::should_use_colors_with_mode(color_mode);
let no_emoji = if let Some(cfg) = config {
cfg.no_emoji || std::env::var("NO_EMOJI").is_ok()
} else {
std::env::var("NO_EMOJI").is_ok()
};
let use_emoji = use_colors && !no_emoji;
let prefix = if use_emoji { "⚠️ " } else { "kelora: " };
let input_files = config.map(|c| c.input_files.as_slice()).unwrap_or(&[]);
let location = format_error_location(line_num, filename, input_files);
let formatted_error = if error_type == "parse" {
if !location.is_empty() && location != "unknown" {
format!("{}{}: {}", prefix, location, message)
} else {
format!("{}{}", prefix, message)
}
} else {
if !location.is_empty() && location != "unknown" {
format!("{}{}: {} - {}", prefix, location, error_type, message)
} else {
format!("{}{} - {}", prefix, error_type, message)
}
};
if crate::rhai_functions::strings::is_parallel_mode() {
crate::rhai_functions::strings::capture_stderr(formatted_error.clone());
if verbose >= 2 && error_type == "parse" {
if let Some(line) = original_line {
crate::rhai_functions::strings::capture_stderr(format!(" {}", line));
if verbose >= 3 {
let non_ascii_count = line.chars().filter(|c| !c.is_ascii()).count();
let control_char_count = line
.chars()
.filter(|c| {
c.is_control() && *c != '\t' && *c != '\n' && *c != '\r'
})
.count();
let line_info = format!(" (length: {} chars, non_ascii: {}, control_chars: {}, starts: {:?}, ends: {:?})",
line.len(),
non_ascii_count,
control_char_count,
line.chars().next().unwrap_or('\0'),
line.chars().last().unwrap_or('\0')
);
crate::rhai_functions::strings::capture_stderr(line_info);
}
}
}
} else {
crate::rhai_functions::strings::capture_stderr(formatted_error.clone());
eprintln!("{}", formatted_error);
if verbose >= 2 && error_type == "parse" {
if let Some(line) = original_line {
let indented_line = format!(" {}", line);
crate::rhai_functions::strings::capture_stderr(indented_line.clone());
eprintln!("{}", indented_line);
if verbose >= 3 {
let non_ascii_count = line.chars().filter(|c| !c.is_ascii()).count();
let control_char_count = line
.chars()
.filter(|c| {
c.is_control() && *c != '\t' && *c != '\n' && *c != '\r'
})
.count();
let line_info = format!(" (length: {} chars, non_ascii: {}, control_chars: {}, starts: {:?}, ends: {:?})",
line.len(),
non_ascii_count,
control_char_count,
line.chars().next().unwrap_or('\0'),
line.chars().last().unwrap_or('\0')
);
crate::rhai_functions::strings::capture_stderr(line_info.clone());
eprintln!("{}", line_info);
}
}
}
}
}
let samples_key = format!("__kelora_error_samples_{}", error_type);
let current_samples = state
.get(&samples_key)
.cloned()
.unwrap_or_else(|| Dynamic::from(rhai::Array::new()));
if let Ok(mut arr) = current_samples.into_array() {
if arr.len() < 3 {
let mut sample_obj = rhai::Map::new();
sample_obj.insert("error_type".into(), Dynamic::from(error_type.to_string()));
sample_obj.insert(
"line_num".into(),
Dynamic::from(line_num.unwrap_or(0) as i64),
);
sample_obj.insert("message".into(), Dynamic::from(message.to_string()));
if let Some(line) = original_line {
sample_obj.insert("original_line".into(), Dynamic::from(line.to_string()));
}
if let Some(filename) = filename {
sample_obj.insert("filename".into(), Dynamic::from(filename.to_string()));
}
arr.push(Dynamic::from(sample_obj));
}
state.insert(samples_key.clone(), Dynamic::from(arr));
state.insert(format!("__op_{}", samples_key), Dynamic::from("unique"));
}
});
}
#[allow(dead_code)] pub fn has_errors_in_tracking(snapshot: &TrackingSnapshot) -> bool {
for (key, value) in &snapshot.internal {
if let Some(_error_type) = key.strip_prefix("__kelora_error_count_") {
if let Ok(count) = value.as_int() {
if count > 0 {
return true;
}
}
}
}
false
}
#[allow(dead_code)] pub fn extract_error_summary_from_tracking(
snapshot: &TrackingSnapshot,
verbose: u8,
_config: Option<&crate::config::KeloraConfig>,
) -> Option<String> {
let mut total_errors = 0;
let mut error_types = Vec::new();
let mut sample_objects: Vec<rhai::Map> = Vec::new();
for (key, value) in &snapshot.internal {
if let Some(error_type) = key.strip_prefix("__kelora_error_count_") {
if let Ok(count) = value.as_int() {
if count > 0 {
total_errors += count;
error_types.push((error_type.to_string(), count));
}
}
}
}
if total_errors == 0 {
return None;
}
for (key, value) in &snapshot.internal {
if let Some(_error_type) = key.strip_prefix("__kelora_error_samples_") {
if let Ok(sample_array) = value.clone().into_array() {
for sample in sample_array {
if let Some(sample_map) = sample.try_cast::<rhai::Map>() {
sample_objects.push(sample_map);
}
}
}
}
}
let mut summary = String::new();
let primary_error_type = if error_types.len() == 1 {
&error_types[0].0
} else {
"mixed"
};
if primary_error_type == "mixed" {
summary.push_str(&format!("Mixed errors: {} total", total_errors));
} else {
summary.push_str(&format!(
"{}{} errors: {} total",
primary_error_type.chars().next().unwrap().to_uppercase(),
&primary_error_type[1..],
total_errors
));
}
let mut shown_samples = 0;
for sample_obj in &sample_objects {
if shown_samples >= 3 {
break;
}
let line_num = sample_obj
.get("line_num")
.and_then(|v| v.as_int().ok())
.unwrap_or(0);
let message = sample_obj
.get("message")
.and_then(|v| v.clone().into_string().ok())
.unwrap_or_else(|| "unknown error".to_string());
let filename = sample_obj
.get("filename")
.and_then(|v| v.clone().into_string().ok())
.unwrap_or_else(|| "stdin".to_string());
let original_line = sample_obj
.get("original_line")
.and_then(|v| v.clone().into_string().ok());
let input_files = &[];
let location = format_error_location(Some(line_num as usize), Some(&filename), input_files);
summary.push_str(&format!("\n {}: {}", location, message));
if verbose > 0 {
if let Some(orig_line) = original_line {
let display_line = if orig_line.len() > 100 {
format!("{}...", &orig_line[..97])
} else {
orig_line
};
summary.push_str(&format!("\n {}", display_line));
}
}
shown_samples += 1;
}
if total_errors as usize > shown_samples {
let remaining = total_errors as usize - shown_samples;
let message = if verbose > 0 {
"Each error shown above. Use -q to suppress."
} else {
"Use -v to see each error or -q to suppress."
};
summary.push_str(&format!("\n [+{} more. {}]", remaining, message));
}
Some(summary)
}
pub fn register_functions(engine: &mut Engine) {
engine.register_fn("track_count", |key: &str| {
with_user_tracking(|state| {
let updated = merge_numeric(state.get(key).cloned(), Dynamic::from(1_i64));
state.insert(key.to_string(), updated);
state.insert(format!("__op_{}", key), Dynamic::from("count"));
});
});
engine.register_fn("track_sum", |key: &str, value: i64| {
with_user_tracking(|state| {
let updated = merge_numeric(state.get(key).cloned(), Dynamic::from(value));
state.insert(key.to_string(), updated);
state.insert(format!("__op_{}", key), Dynamic::from("sum"));
});
});
engine.register_fn("track_sum", |key: &str, value: i32| {
with_user_tracking(|state| {
let updated = merge_numeric(state.get(key).cloned(), Dynamic::from(value));
state.insert(key.to_string(), updated);
state.insert(format!("__op_{}", key), Dynamic::from("sum"));
});
});
engine.register_fn("track_sum", |key: &str, value: f64| {
with_user_tracking(|state| {
let updated = merge_numeric(state.get(key).cloned(), Dynamic::from(value));
state.insert(key.to_string(), updated);
state.insert(format!("__op_{}", key), Dynamic::from("sum"));
});
});
engine.register_fn("track_sum", |key: &str, value: f32| {
with_user_tracking(|state| {
let updated = merge_numeric(state.get(key).cloned(), Dynamic::from(value));
state.insert(key.to_string(), updated);
state.insert(format!("__op_{}", key), Dynamic::from("sum"));
});
});
engine.register_fn("track_min", |key: &str, value: i64| {
with_user_tracking(|state| {
let current = state
.get(key)
.cloned()
.unwrap_or(Dynamic::from(f64::INFINITY));
let current_val = if current.is_int() {
current.as_int().unwrap_or(i64::MAX) as f64
} else {
current.as_float().unwrap_or(f64::INFINITY)
};
let value_f64 = value as f64;
if value_f64 < current_val {
state.insert(key.to_string(), Dynamic::from(value));
state.insert(format!("__op_{}", key), Dynamic::from("min"));
}
});
});
engine.register_fn("track_min", |key: &str, value: i32| {
with_user_tracking(|state| {
let current = state
.get(key)
.cloned()
.unwrap_or(Dynamic::from(f64::INFINITY));
let current_val = if current.is_int() {
current.as_int().unwrap_or(i64::MAX) as f64
} else {
current.as_float().unwrap_or(f64::INFINITY)
};
let value_f64 = value as f64;
if value_f64 < current_val {
state.insert(key.to_string(), Dynamic::from(value));
state.insert(format!("__op_{}", key), Dynamic::from("min"));
}
});
});
engine.register_fn("track_min", |key: &str, value: f64| {
with_user_tracking(|state| {
let current = state
.get(key)
.cloned()
.unwrap_or(Dynamic::from(f64::INFINITY));
let current_val = if current.is_int() {
current.as_int().unwrap_or(i64::MAX) as f64
} else {
current.as_float().unwrap_or(f64::INFINITY)
};
if value < current_val {
state.insert(key.to_string(), Dynamic::from(value));
state.insert(format!("__op_{}", key), Dynamic::from("min"));
}
});
});
engine.register_fn("track_min", |key: &str, value: f32| {
with_user_tracking(|state| {
let current = state
.get(key)
.cloned()
.unwrap_or(Dynamic::from(f64::INFINITY));
let current_val = if current.is_int() {
current.as_int().unwrap_or(i64::MAX) as f64
} else {
current.as_float().unwrap_or(f64::INFINITY)
};
let value_f64 = value as f64;
if value_f64 < current_val {
state.insert(key.to_string(), Dynamic::from(value));
state.insert(format!("__op_{}", key), Dynamic::from("min"));
}
});
});
engine.register_fn("track_max", |key: &str, value: i64| {
with_user_tracking(|state| {
let current = state
.get(key)
.cloned()
.unwrap_or(Dynamic::from(f64::NEG_INFINITY));
let current_val = if current.is_int() {
current.as_int().unwrap_or(i64::MIN) as f64
} else {
current.as_float().unwrap_or(f64::NEG_INFINITY)
};
let value_f64 = value as f64;
if value_f64 > current_val {
state.insert(key.to_string(), Dynamic::from(value));
state.insert(format!("__op_{}", key), Dynamic::from("max"));
}
});
});
engine.register_fn("track_max", |key: &str, value: i32| {
with_user_tracking(|state| {
let current = state
.get(key)
.cloned()
.unwrap_or(Dynamic::from(f64::NEG_INFINITY));
let current_val = if current.is_int() {
current.as_int().unwrap_or(i64::MIN) as f64
} else {
current.as_float().unwrap_or(f64::NEG_INFINITY)
};
let value_f64 = value as f64;
if value_f64 > current_val {
state.insert(key.to_string(), Dynamic::from(value));
state.insert(format!("__op_{}", key), Dynamic::from("max"));
}
});
});
engine.register_fn("track_max", |key: &str, value: f64| {
with_user_tracking(|state| {
let current = state
.get(key)
.cloned()
.unwrap_or(Dynamic::from(f64::NEG_INFINITY));
let current_val = if current.is_int() {
current.as_int().unwrap_or(i64::MIN) as f64
} else {
current.as_float().unwrap_or(f64::NEG_INFINITY)
};
if value > current_val {
state.insert(key.to_string(), Dynamic::from(value));
state.insert(format!("__op_{}", key), Dynamic::from("max"));
}
});
});
engine.register_fn("track_max", |key: &str, value: f32| {
with_user_tracking(|state| {
let current = state
.get(key)
.cloned()
.unwrap_or(Dynamic::from(f64::NEG_INFINITY));
let current_val = if current.is_int() {
current.as_int().unwrap_or(i64::MIN) as f64
} else {
current.as_float().unwrap_or(f64::NEG_INFINITY)
};
let value_f64 = value as f64;
if value_f64 > current_val {
state.insert(key.to_string(), Dynamic::from(value));
state.insert(format!("__op_{}", key), Dynamic::from("max"));
}
});
});
engine.register_fn("track_unique", |key: &str, value: &str| {
with_user_tracking(|state| {
let current = state.get(key).cloned().unwrap_or_else(|| {
Dynamic::from(rhai::Array::new())
});
if let Ok(mut arr) = current.into_array() {
let value_dynamic = Dynamic::from(value.to_string());
if !arr
.iter()
.any(|v| v.clone().into_string().unwrap_or_default() == value)
{
arr.push(value_dynamic);
}
state.insert(key.to_string(), Dynamic::from(arr));
state.insert(format!("__op_{}", key), Dynamic::from("unique"));
}
});
});
engine.register_fn("track_unique", |key: &str, value: i64| {
with_user_tracking(|state| {
let current = state.get(key).cloned().unwrap_or_else(|| {
Dynamic::from(rhai::Array::new())
});
if let Ok(mut arr) = current.into_array() {
let value_dynamic = Dynamic::from(value);
if !arr.iter().any(|v| v.as_int().unwrap_or(i64::MIN) == value) {
arr.push(value_dynamic);
}
state.insert(key.to_string(), Dynamic::from(arr));
state.insert(format!("__op_{}", key), Dynamic::from("unique"));
}
});
});
engine.register_fn("track_unique", |key: &str, value: i32| {
with_user_tracking(|state| {
let current = state.get(key).cloned().unwrap_or_else(|| {
Dynamic::from(rhai::Array::new())
});
if let Ok(mut arr) = current.into_array() {
let value_dynamic = Dynamic::from(value);
if !arr
.iter()
.any(|v| v.as_int().unwrap_or(i64::MIN) == (value as i64))
{
arr.push(value_dynamic);
}
state.insert(key.to_string(), Dynamic::from(arr));
state.insert(format!("__op_{}", key), Dynamic::from("unique"));
}
});
});
engine.register_fn("track_unique", |key: &str, value: f64| {
with_user_tracking(|state| {
let current = state.get(key).cloned().unwrap_or_else(|| {
Dynamic::from(rhai::Array::new())
});
if let Ok(mut arr) = current.into_array() {
let value_dynamic = Dynamic::from(value);
if !arr
.iter()
.any(|v| v.as_float().unwrap_or(f64::NAN) == value)
{
arr.push(value_dynamic);
}
state.insert(key.to_string(), Dynamic::from(arr));
state.insert(format!("__op_{}", key), Dynamic::from("unique"));
}
});
});
engine.register_fn("track_unique", |key: &str, value: f32| {
with_user_tracking(|state| {
let current = state.get(key).cloned().unwrap_or_else(|| {
Dynamic::from(rhai::Array::new())
});
if let Ok(mut arr) = current.into_array() {
let value_dynamic = Dynamic::from(value);
if !arr
.iter()
.any(|v| v.as_float().unwrap_or(f64::NAN) == (value as f64))
{
arr.push(value_dynamic);
}
state.insert(key.to_string(), Dynamic::from(arr));
state.insert(format!("__op_{}", key), Dynamic::from("unique"));
}
});
});
engine.register_fn("track_bucket", |key: &str, bucket: &str| {
with_user_tracking(|state| {
let current = state
.get(key)
.cloned()
.unwrap_or_else(|| Dynamic::from(rhai::Map::new()));
if let Some(mut map) = current.try_cast::<rhai::Map>() {
let count = map.get(bucket).cloned().unwrap_or(Dynamic::from(0i64));
let new_count = count.as_int().unwrap_or(0) + 1;
map.insert(bucket.into(), Dynamic::from(new_count));
state.insert(key.to_string(), Dynamic::from(map));
state.insert(format!("__op_{}", key), Dynamic::from("bucket"));
}
});
});
engine.register_fn("track_bucket", |key: &str, bucket: i64| {
with_user_tracking(|state| {
let current = state
.get(key)
.cloned()
.unwrap_or_else(|| Dynamic::from(rhai::Map::new()));
if let Some(mut map) = current.try_cast::<rhai::Map>() {
let bucket_str = bucket.to_string();
let count = map
.get(bucket_str.as_str())
.cloned()
.unwrap_or(Dynamic::from(0i64));
let new_count = count.as_int().unwrap_or(0) + 1;
map.insert(bucket_str.into(), Dynamic::from(new_count));
state.insert(key.to_string(), Dynamic::from(map));
state.insert(format!("__op_{}", key), Dynamic::from("bucket"));
}
});
});
engine.register_fn("track_bucket", |key: &str, bucket: i32| {
with_user_tracking(|state| {
let current = state
.get(key)
.cloned()
.unwrap_or_else(|| Dynamic::from(rhai::Map::new()));
if let Some(mut map) = current.try_cast::<rhai::Map>() {
let bucket_str = bucket.to_string();
let count = map
.get(bucket_str.as_str())
.cloned()
.unwrap_or(Dynamic::from(0i64));
let new_count = count.as_int().unwrap_or(0) + 1;
map.insert(bucket_str.into(), Dynamic::from(new_count));
state.insert(key.to_string(), Dynamic::from(map));
state.insert(format!("__op_{}", key), Dynamic::from("bucket"));
}
});
});
engine.register_fn("track_bucket", |key: &str, bucket: f64| {
with_user_tracking(|state| {
let current = state
.get(key)
.cloned()
.unwrap_or_else(|| Dynamic::from(rhai::Map::new()));
if let Some(mut map) = current.try_cast::<rhai::Map>() {
let bucket_str = bucket.to_string();
let count = map
.get(bucket_str.as_str())
.cloned()
.unwrap_or(Dynamic::from(0i64));
let new_count = count.as_int().unwrap_or(0) + 1;
map.insert(bucket_str.into(), Dynamic::from(new_count));
state.insert(key.to_string(), Dynamic::from(map));
state.insert(format!("__op_{}", key), Dynamic::from("bucket"));
}
});
});
engine.register_fn("track_bucket", |key: &str, bucket: f32| {
with_user_tracking(|state| {
let current = state
.get(key)
.cloned()
.unwrap_or_else(|| Dynamic::from(rhai::Map::new()));
if let Some(mut map) = current.try_cast::<rhai::Map>() {
let bucket_str = bucket.to_string();
let count = map
.get(bucket_str.as_str())
.cloned()
.unwrap_or(Dynamic::from(0i64));
let new_count = count.as_int().unwrap_or(0) + 1;
map.insert(bucket_str.into(), Dynamic::from(new_count));
state.insert(key.to_string(), Dynamic::from(map));
state.insert(format!("__op_{}", key), Dynamic::from("bucket"));
}
});
});
}
#[allow(dead_code)] pub fn merge_thread_tracking_to_context(ctx: &mut crate::pipeline::PipelineContext) {
let snapshot = get_thread_snapshot();
for (key, value) in snapshot.user {
ctx.tracker.insert(key, value);
}
for (key, value) in snapshot.internal {
ctx.internal_tracker.insert(key, value);
}
}
#[allow(dead_code)] fn get_metrics_map() -> Dynamic {
Dynamic::from(rhai::Map::new()) }
#[allow(dead_code)] pub fn format_metrics_output(metrics: &HashMap<String, Dynamic>) -> String {
let mut output = String::new();
let mut user_values: Vec<_> = metrics
.iter()
.filter(|(k, _)| !k.starts_with("__op_") && !k.starts_with("__kelora_stats_"))
.collect();
if user_values.is_empty() {
return "No metrics tracked".to_string();
}
user_values.sort_by_key(|(k, _)| k.as_str());
for (key, value) in user_values {
if value.is::<rhai::Map>() {
if let Some(map) = value.clone().try_cast::<rhai::Map>() {
if let (Some(count), Some(sample)) = (map.get("count"), map.get("sample")) {
if let Ok(sample_array) = sample.clone().into_array() {
let sample_strings: Vec<String> = sample_array
.iter()
.take(3) .map(|v| format!("\"{}\"", v.clone().into_string().unwrap_or_default()))
.collect();
output.push_str(&format!(
"{:<12} = {{ count: {}, sample: [{}] }}\n",
key,
count.as_int().unwrap_or(0),
sample_strings.join(", ")
));
continue;
}
}
}
}
if value.is_int() {
output.push_str(&format!("{:<12} = {}\n", key, value.as_int().unwrap_or(0)));
} else if value.is_float() {
output.push_str(&format!(
"{:<12} = {}\n",
key,
value.as_float().unwrap_or(0.0)
));
} else {
output.push_str(&format!("{:<12} = {}\n", key, value));
}
}
output.trim_end().to_string()
}
#[allow(dead_code)] pub fn format_metrics_json(
metrics: &HashMap<String, Dynamic>,
) -> Result<String, serde_json::Error> {
let mut json_obj = serde_json::Map::new();
for (key, value) in metrics.iter() {
if key.starts_with("__op_")
|| key.starts_with("__kelora_stats_")
|| key.starts_with("__kelora_error_")
{
continue;
}
if value.is::<rhai::Map>() {
if let Some(map) = value.clone().try_cast::<rhai::Map>() {
if let (Some(count), Some(sample)) = (map.get("count"), map.get("sample")) {
let mut unique_obj = serde_json::Map::new();
unique_obj.insert(
"count".to_string(),
serde_json::Value::Number(serde_json::Number::from(
count.as_int().unwrap_or(0),
)),
);
if let Ok(sample_array) = sample.clone().into_array() {
let sample_values: Vec<serde_json::Value> = sample_array
.iter()
.map(|v| {
serde_json::Value::String(
v.clone().into_string().unwrap_or_default(),
)
})
.collect();
unique_obj.insert(
"sample".to_string(),
serde_json::Value::Array(sample_values),
);
}
json_obj.insert(key.clone(), serde_json::Value::Object(unique_obj));
continue;
}
}
}
if value.is_int() {
json_obj.insert(
key.clone(),
serde_json::Value::Number(serde_json::Number::from(value.as_int().unwrap_or(0))),
);
} else if value.is_float() {
if let Some(num) = serde_json::Number::from_f64(value.as_float().unwrap_or(0.0)) {
json_obj.insert(key.clone(), serde_json::Value::Number(num));
}
} else {
json_obj.insert(key.clone(), serde_json::Value::String(value.to_string()));
}
}
serde_json::to_string_pretty(&json_obj)
}
#[allow(dead_code)] pub fn extract_error_summary(metrics: &HashMap<String, Dynamic>) -> Option<String> {
let mut has_errors = false;
let mut summary = serde_json::Map::new();
let mut error_types: std::collections::HashSet<String> = std::collections::HashSet::new();
for key in metrics.keys() {
if let Some(suffix) = key.strip_prefix("__kelora_error_count_") {
error_types.insert(suffix.to_string());
}
}
for error_type in error_types {
let count_key = format!("__kelora_error_count_{}", error_type);
let examples_key = format!("__kelora_error_examples_{}", error_type);
if let Some(count_value) = metrics.get(&count_key) {
let count = count_value.as_int().unwrap_or(0);
if count > 0 {
has_errors = true;
let mut error_obj = serde_json::Map::new();
error_obj.insert(
"count".to_string(),
serde_json::Value::Number(serde_json::Number::from(count)),
);
if let Some(examples_value) = metrics.get(&examples_key) {
if let Ok(examples_array) = examples_value.clone().into_array() {
let examples: Vec<serde_json::Value> = examples_array
.iter()
.map(|v| {
serde_json::Value::String(
v.clone().into_string().unwrap_or_default(),
)
})
.collect();
error_obj
.insert("examples".to_string(), serde_json::Value::Array(examples));
}
}
summary.insert(error_type, serde_json::Value::Object(error_obj));
}
}
}
if has_errors {
Some(
serde_json::to_string_pretty(&summary)
.unwrap_or_else(|_| "Error serializing summary".to_string()),
)
} else {
None
}
}