use std::time::Duration;
use crate::component::log::ExchangeFormatter;
use crate::context::CamelContext;
use crate::message::Exchange;
use crate::value::Value;
pub const MESSAGE_HISTORY: &str = "CamelMessageHistory";
#[derive(Clone, Debug, PartialEq)]
pub struct MessageHistoryEntry {
pub route_id: String,
pub node_id: String,
pub label: String,
pub elapsed: u64,
pub timestamp: i64,
}
impl CamelContext {
pub fn set_message_history(&self, enabled: bool) {
crate::management::state(self).history_enabled.store(enabled, std::sync::atomic::Ordering::Relaxed);
}
pub fn is_message_history(&self) -> bool {
crate::management::state(self).history_enabled.load(std::sync::atomic::Ordering::Relaxed)
}
}
pub(crate) fn begin(ex: &mut Exchange, route_id: &str, node_id: &str, label: &str) -> usize {
let entry = Value::object([
("routeId", Value::from(route_id)),
("nodeId", Value::from(node_id)),
("label", Value::from(label)),
("elapsed", Value::Int(0)),
("timestamp", Value::Int(crate::management::now_millis())),
]);
let slot = ex.properties.entry(MESSAGE_HISTORY.to_string()).or_insert_with(|| Value::Array(Vec::new()));
if !matches!(slot, Value::Array(_)) {
*slot = Value::Array(Vec::new());
}
match slot {
Value::Array(a) => {
a.push(entry);
a.len() - 1
}
_ => unreachable!(),
}
}
pub(crate) fn end(ex: &mut Exchange, index: usize, node_id: &str, elapsed: Duration) {
if let Some(Value::Array(a)) = ex.properties.get_mut(MESSAGE_HISTORY) {
if let Some(Value::Object(o)) = a.get_mut(index) {
if o.get("nodeId").and_then(|v| v.as_str()) == Some(node_id) {
o.insert("elapsed".into(), Value::Int(elapsed.as_millis() as i64));
}
}
}
}
pub fn message_history(ex: &Exchange) -> Vec<MessageHistoryEntry> {
let Some(Value::Array(a)) = ex.property(MESSAGE_HISTORY) else { return Vec::new() };
a.iter()
.map(|v| {
let s = |k: &str| v.get(k).map(|x| x.to_string()).unwrap_or_default();
MessageHistoryEntry {
route_id: s("routeId"),
node_id: s("nodeId"),
label: s("label"),
elapsed: v.get("elapsed").and_then(|x| x.as_i64()).unwrap_or(0).max(0) as u64,
timestamp: v.get("timestamp").and_then(|x| x.as_i64()).unwrap_or(0),
}
})
.collect()
}
const LINE: &str = "---------------------------------------------------------------------------------------------------------------------------------------";
fn cell(s: &str, width: usize) -> String {
let s: String = s.replace(['\n', '\r'], " ");
if s.chars().count() > width {
s.chars().take(width).collect()
} else {
format!("{:<width$}", s, width = width)
}
}
pub fn format_message_history(ex: &Exchange) -> String {
let mut out = String::new();
let entries = message_history(ex);
if ex.property(MESSAGE_HISTORY).is_none() {
out.push_str("Message History (message history is disabled)\n");
out.push_str(LINE);
out.push('\n');
} else {
out.push_str("Message History\n");
out.push_str(LINE);
out.push('\n');
out.push_str(&format!("{:<20} {:<20} {:<80} {}\n", "RouteId", "ProcessorId", "Processor", "Elapsed (ms)"));
for e in &entries {
out.push_str(&format!("[{}] [{}] [{}] [{:>10}]\n", cell(&e.route_id, 18), cell(&e.node_id, 18), cell(&e.label, 78), e.elapsed));
}
}
out.push_str("\nExchange\n");
out.push_str(LINE);
out.push('\n');
let fmt = ExchangeFormatter { show_exchange_id: true, show_headers: true, show_exception: false, max_chars: 1000, ..Default::default() };
out.push_str(&fmt.format(ex));
out.push('\n');
let error = ex.exception.clone().or_else(|| {
ex.property(crate::message::props::EXCEPTION_CAUGHT).map(|v| crate::error::CamelError::new("Exception", v.to_string()))
});
if let Some(e) = error {
out.push_str("\nStacktrace\n");
out.push_str(LINE);
out.push('\n');
out.push_str(&e.to_string());
out.push('\n');
}
out
}