use chrono::{Local, TimeZone};
use console::style;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::fmt::{Display, Formatter, Result};
pub const PROTOCOL_ID: &str = "trace-v1";
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TraceEvent {
#[serde(alias = "eventTimestamp")]
pub timestamp: i64,
pub outcome: String,
pub logs: Vec<LogItem>,
pub exceptions: Vec<ExceptionItem>,
pub event: EventItem,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EventItem {
pub request: Option<RequestItem>,
pub cron: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RequestItem {
pub url: String,
pub method: String,
pub cf: Option<CfMetadata>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CfMetadata {
pub colo: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LogItem {
pub level: String,
pub message: Value,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ExceptionItem {
pub name: String,
pub message: String,
}
impl Display for TraceEvent {
fn fmt(&self, f: &mut Formatter) -> Result {
let timestamp = style(
Local
.timestamp_millis(self.timestamp)
.format("%Y-%m-%d %H:%M:%S"),
)
.dim();
let outcome = match self.outcome.as_ref() {
"ok" => style("Ok").green(),
"canceled" => style("Canceled").yellow(),
"exception" => style("Error").red(),
"exceededCpu" => style("Exceeded Limit").red(),
_ => style("System Error").red(),
};
match self.event.request.clone() {
Some(request) => {
let colo = style(
request
.cf
.map(|cf| cf.colo)
.unwrap_or_else(|| "?".to_owned()),
)
.dim();
let method = style(request.method);
let url = style(request.url).bold();
write!(
f,
"[{}] [{}] [{}] {} {}",
timestamp, colo, outcome, method, url
)
}
_ => match self.event.cron.clone() {
Some(cron) => write!(f, "[{}] [?] [{}] {}", timestamp, outcome, cron),
_ => write!(f, "[{}] [?] [{}] <unknown event>", timestamp, outcome),
},
}?;
for log in self.logs.iter() {
let prefix = style("|").dim();
write!(f, "\n {} {}", prefix, log)?;
}
for err in self.exceptions.iter() {
let prefix = style("!").dim();
write!(f, "\n {} {}", prefix, err)?;
}
Ok(())
}
}
impl Display for LogItem {
fn fmt(&self, f: &mut Formatter) -> Result {
let level = match self.level.as_ref() {
"debug" => style("Debug").blue(),
"warn" => style("Warn").yellow(),
"error" => style("Error").red(),
_ => style("Info").dim(),
};
write!(f, "[{}] ", level)?;
match &self.message {
Value::Array(values) => {
for value in values.iter() {
match value {
Value::String(s) => write!(f, "{}", s),
v => write!(f, "{}", v),
}?;
write!(f, " ")?;
}
Ok(())
}
Value::String(v) => write!(f, "{}", v),
v => write!(f, "{}", v),
}?;
Ok(())
}
}
impl Display for ExceptionItem {
fn fmt(&self, f: &mut Formatter) -> Result {
let name = style(&self.name).red().bold();
let message = style(&self.message).red();
write!(f, "[{}] {}", name, message)
}
}