use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use crate::telemetry::{FormatterMode, PlainFormatter, TelemetryFormatter};
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
pub struct ErrorEvent {
#[serde(default = "chrono::Utc::now")]
pub when: DateTime<Utc>,
#[serde(default)]
pub scope: ErrorScope,
#[serde(default)]
pub error: WeaveError,
#[serde(default)]
pub tags: Vec<String>,
#[serde(default)]
pub context: serde_json::Value,
}
impl ErrorEvent {
pub fn node<S: Into<String>>(kind: S, step: u64, error: WeaveError) -> Self {
Self {
when: Utc::now(),
scope: ErrorScope::Node {
kind: kind.into(),
step,
},
error,
tags: Vec::new(),
context: serde_json::Value::Null,
}
}
pub fn scheduler(step: u64, error: WeaveError) -> Self {
Self {
when: Utc::now(),
scope: ErrorScope::Scheduler { step },
error,
tags: Vec::new(),
context: serde_json::Value::Null,
}
}
pub fn runner<S: Into<String>>(session: S, step: u64, error: WeaveError) -> Self {
Self {
when: Utc::now(),
scope: ErrorScope::Runner {
session: session.into(),
step,
},
error,
tags: Vec::new(),
context: serde_json::Value::Null,
}
}
pub fn app(error: WeaveError) -> Self {
Self {
when: Utc::now(),
scope: ErrorScope::App,
error,
tags: Vec::new(),
context: serde_json::Value::Null,
}
}
pub fn with_tags(mut self, tags: Vec<String>) -> Self {
self.tags = tags;
self
}
pub fn with_tag<S: Into<String>>(mut self, tag: S) -> Self {
self.tags.push(tag.into());
self
}
pub fn with_context(mut self, context: serde_json::Value) -> Self {
self.context = context;
self
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
#[serde(tag = "scope", rename_all = "snake_case")]
pub enum ErrorScope {
Node {
kind: String,
step: u64,
},
Scheduler {
step: u64,
},
Runner {
session: String,
step: u64,
},
#[default]
App,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct WeaveError {
pub message: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub cause: Option<Box<WeaveError>>,
#[serde(default)]
pub details: serde_json::Value,
}
impl Default for WeaveError {
fn default() -> Self {
WeaveError {
message: String::new(),
cause: None,
details: serde_json::Value::Null,
}
}
}
impl std::fmt::Display for WeaveError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.message)
}
}
impl std::error::Error for WeaveError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
self.cause.as_ref().map(|c| c as &dyn std::error::Error)
}
}
impl WeaveError {
pub fn msg<M: Into<String>>(m: M) -> Self {
WeaveError {
message: m.into(),
cause: None,
details: serde_json::Value::Null,
}
}
pub fn with_details(mut self, details: serde_json::Value) -> Self {
self.details = details;
self
}
pub fn with_cause(mut self, cause: WeaveError) -> Self {
self.cause = Some(Box::new(cause));
self
}
}
#[deprecated(
since = "0.3.0",
note = "Use WeaveError instead; this alias is removed in 0.4.0"
)]
pub type LadderError = WeaveError;
pub fn pretty_print_with_mode(events: &[ErrorEvent], mode: FormatterMode) -> String {
let formatter = PlainFormatter::with_mode(mode);
let renders = formatter.render_errors(events);
let mut out = String::new();
for (idx, render) in renders.into_iter().enumerate() {
if idx > 0 {
out.push('\n');
}
for line in render.lines {
out.push_str(&line);
}
}
out
}
pub fn pretty_print(events: &[ErrorEvent]) -> String {
pretty_print_with_mode(events, FormatterMode::Auto)
}