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 {
fn with_scope(scope: ErrorScope, error: WeaveError) -> Self {
Self {
when: Utc::now(),
scope,
error,
tags: Vec::new(),
context: serde_json::Value::Null,
}
}
pub fn node<S: Into<String>>(kind: S, step: u64, error: WeaveError) -> Self {
Self::with_scope(
ErrorScope::Node {
kind: kind.into(),
step,
},
error,
)
}
pub fn scheduler(step: u64, error: WeaveError) -> Self {
Self::with_scope(ErrorScope::Scheduler { step }, error)
}
pub fn runner<S: Into<String>>(session: S, step: u64, error: WeaveError) -> Self {
Self::with_scope(
ErrorScope::Runner {
session: session.into(),
step,
},
error,
)
}
pub fn app(error: WeaveError) -> Self {
Self::with_scope(ErrorScope::App, error)
}
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, Default, 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 std::fmt::Display for WeaveError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(&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 {
Self {
message: m.into(),
..Default::default()
}
}
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
}
}
pub fn pretty_print_with_mode(events: &[ErrorEvent], mode: FormatterMode) -> String {
PlainFormatter::with_mode(mode)
.render_errors(events)
.into_iter()
.map(|r| r.join_lines())
.collect::<Vec<_>>()
.join("\n")
}
pub fn pretty_print(events: &[ErrorEvent]) -> String {
pretty_print_with_mode(events, FormatterMode::Auto)
}