#[cfg(feature = "tracing")]
#[path = "trace/tracing.rs"]
mod tracing;
use alloc::string::ToString;
use alloc::vec::Vec;
use core::error::Error;
use core::fmt::{Debug, Display, Formatter};
use ref_str::RefStr;
use crate::render::DiagnosticIr;
use crate::render_impl::{
build_ctx_and_attachments, build_diag_src_errs_val, build_display_causes, build_error_value,
build_origin_src_errs_val, build_stack_trace_value, build_trace_value,
};
use crate::report::{
AttachmentValue, ErrorCode, HasSeverity, Report, Severity, SeverityState, TraceEvent,
TraceEventLevel,
};
#[cfg(feature = "tracing")]
pub use tracing::TracingExporter;
fn error_code_value(value: &ErrorCode) -> AttachmentValue {
match value {
ErrorCode::Integer(v) => AttachmentValue::Integer(*v),
ErrorCode::String(v) => AttachmentValue::String(v.clone()),
}
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "json", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "json", serde(bound(deserialize = "'de: 'a")))]
pub struct TracingField<'a> {
pub key: RefStr<'a>,
pub value: AttachmentValue,
}
impl<State> DiagnosticIr<'_, State>
where
State: SeverityState,
{
pub fn to_tracing_fields(&self) -> Vec<TracingField<'_>> {
let mut fields = Vec::new();
self.tracing_error(&mut fields);
self.tracing_meta(&mut fields);
self.tracing_stack_causes(&mut fields);
#[cfg(feature = "trace")]
self.tracing_trace(&mut fields);
self.tracing_ctx_attrs(&mut fields);
fields
}
fn tracing_error(&self, fields: &mut Vec<TracingField<'_>>) {
fields.push(TracingField {
key: "error".into(),
value: build_error_value(&self.error),
});
}
fn tracing_meta(&self, fields: &mut Vec<TracingField<'_>>) {
if let Some(error_code) = self.metadata.error_code() {
fields.push(TracingField {
key: "metadata.error_code".into(),
value: error_code_value(error_code),
});
}
if let Some(severity) = self.metadata.severity() {
fields.push(TracingField {
key: "metadata.severity".into(),
value: AttachmentValue::String(severity.to_string().into()),
});
}
if let Some(category) = self.metadata.category() {
fields.push(TracingField {
key: "metadata.category".into(),
value: AttachmentValue::String(category.to_string().into()),
});
}
if let Some(retryable) = self.metadata.retryable() {
fields.push(TracingField {
key: "metadata.retryable".into(),
value: AttachmentValue::Bool(retryable),
});
}
}
#[cfg(feature = "trace")]
fn tracing_trace(&self, fields: &mut Vec<TracingField<'_>>) {
if self.trace.is_empty() {
return;
}
let trace_value = build_trace_value(self.trace, &self.error);
fields.push(TracingField {
key: "trace".into(),
value: trace_value,
});
}
fn tracing_stack_causes(&self, fields: &mut Vec<TracingField<'_>>) {
if let Some(stack_trace) = self.metadata.stack_trace() {
fields.push(TracingField {
key: "diagnostic_bag.stack_trace".into(),
value: build_stack_trace_value(stack_trace),
});
}
if !self.display_causes.is_empty() {
fields.push(TracingField {
key: "diagnostic_bag.display_causes".into(),
value: build_display_causes(self.display_causes, self.display_causes_state),
});
}
if let Some(source_errors) = self.origin_source_errors.as_ref() {
fields.push(TracingField {
key: "diagnostic_bag.origin_source_errors".into(),
value: build_origin_src_errs_val(source_errors),
});
}
if let Some(source_errors) = self.diagnostic_source_errors.as_ref() {
fields.push(TracingField {
key: "diagnostic_bag.diagnostic_source_errors".into(),
value: build_diag_src_errs_val(source_errors),
});
}
}
fn tracing_ctx_attrs(&self, fields: &mut Vec<TracingField<'_>>) {
let (context_value, system_value, attachment_items): (
AttachmentValue,
AttachmentValue,
Vec<AttachmentValue>,
) = build_ctx_and_attachments(self.context, self.system, self.attachments);
if !self.context.is_empty() {
fields.push(TracingField {
key: "context".into(),
value: context_value,
});
}
if !self.system.is_empty() {
fields.push(TracingField {
key: "system".into(),
value: system_value,
});
}
if !attachment_items.is_empty() {
fields.push(TracingField {
key: "attachments".into(),
value: AttachmentValue::Array(attachment_items),
});
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PreparedTracingLevel {
Trace,
Debug,
Info,
Warn,
Error,
}
impl Display for PreparedTracingLevel {
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
let label = match self {
Self::Trace => "trace",
Self::Debug => "debug",
Self::Info => "info",
Self::Warn => "warn",
Self::Error => "error",
};
f.write_str(label)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct EmitStats {
pub report_events_emitted: usize,
pub trace_events_emitted: usize,
}
impl EmitStats {
pub const fn total_events_emitted(self) -> usize {
self.report_events_emitted + self.trace_events_emitted
}
}
pub struct PreparedTracingEmission<'a> {
ir: DiagnosticIr<'a, HasSeverity>,
report_level: PreparedTracingLevel,
trace_event_levels: Vec<PreparedTracingLevel>,
}
impl Debug for PreparedTracingEmission<'_> {
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
f.debug_struct("PreparedTracingEmission")
.field("report_level", &self.report_level)
.field("trace_event_levels", &self.trace_event_levels)
.field("stats", &self.stats())
.finish_non_exhaustive()
}
}
impl<'a> PreparedTracingEmission<'a> {
fn prepare(ir: DiagnosticIr<'a, HasSeverity>) -> Self {
let report_level = severity_to_level(ir.metadata.required_severity());
let trace_event_levels = ir
.trace
.events()
.map(|events| {
events
.iter()
.map(|trace_event| {
trace_level_to_prepared(trace_event.level).unwrap_or(report_level)
})
.collect()
})
.unwrap_or_default();
Self {
ir,
report_level,
trace_event_levels,
}
}
pub fn ir(&self) -> &DiagnosticIr<'a, HasSeverity> {
&self.ir
}
pub fn report_level(&self) -> PreparedTracingLevel {
self.report_level
}
pub fn trace_event_level(&self, index: usize) -> Option<PreparedTracingLevel> {
self.trace_event_levels.get(index).copied()
}
pub fn trace_event_levels(&self) -> &[PreparedTracingLevel] {
self.trace_event_levels.as_slice()
}
pub fn trace_events(&self) -> impl Iterator<Item = PreparedTraceEvent<'_>> + '_ {
let events = self.ir.trace.events().unwrap_or(&[]);
events
.iter()
.enumerate()
.zip(self.trace_event_levels.iter().copied())
.map(|((index, event), level)| PreparedTraceEvent {
index,
event,
level,
})
}
pub fn stats(&self) -> EmitStats {
EmitStats {
report_events_emitted: 1,
trace_events_emitted: self.trace_event_levels.len(),
}
}
#[cfg(feature = "tracing")]
pub fn emit(self) -> EmitStats {
TracingExporter.export_prepared(self)
}
pub fn emit_with<TExporter>(self, exporter: &TExporter) -> EmitStats
where
TExporter: TracingExporterTrait,
{
exporter.export_prepared(self)
}
}
#[derive(Clone, Copy)]
pub struct PreparedTraceEvent<'a> {
index: usize,
event: &'a TraceEvent,
level: PreparedTracingLevel,
}
impl<'a> PreparedTraceEvent<'a> {
pub fn index(&self) -> usize {
self.index
}
pub fn event(&self) -> &'a TraceEvent {
self.event
}
pub fn level(&self) -> PreparedTracingLevel {
self.level
}
}
pub trait TracingExporterTrait {
fn export_prepared(&self, emission: PreparedTracingEmission<'_>) -> EmitStats;
}
impl DiagnosticIr<'_, HasSeverity> {
pub fn prepare_tracing(&self) -> PreparedTracingEmission<'_> {
PreparedTracingEmission::prepare(self.clone())
}
#[cfg(feature = "tracing")]
pub fn emit_tracing(&self) -> EmitStats {
self.prepare_tracing().emit()
}
pub fn emit_tracing_with<TExporter>(&self, exporter: &TExporter) -> EmitStats
where
TExporter: TracingExporterTrait,
{
self.prepare_tracing().emit_with(exporter)
}
}
impl<E> Report<E, HasSeverity>
where
E: Error,
{
pub fn prepare_tracing(&self) -> PreparedTracingEmission<'_> {
PreparedTracingEmission::prepare(self.to_diagnostic_ir())
}
#[cfg(feature = "tracing")]
pub fn emit_tracing(&self) -> EmitStats {
self.prepare_tracing().emit()
}
pub fn emit_tracing_with<TExporter>(&self, exporter: &TExporter) -> EmitStats
where
TExporter: TracingExporterTrait,
{
self.prepare_tracing().emit_with(exporter)
}
}
fn severity_to_level(level: Severity) -> PreparedTracingLevel {
match level {
Severity::Trace => PreparedTracingLevel::Trace,
Severity::Debug => PreparedTracingLevel::Debug,
Severity::Info => PreparedTracingLevel::Info,
Severity::Warn => PreparedTracingLevel::Warn,
Severity::Error | Severity::Fatal => PreparedTracingLevel::Error,
}
}
fn trace_level_to_prepared(level: Option<TraceEventLevel>) -> Option<PreparedTracingLevel> {
match level {
Some(TraceEventLevel::Trace) => Some(PreparedTracingLevel::Trace),
Some(TraceEventLevel::Debug) => Some(PreparedTracingLevel::Debug),
Some(TraceEventLevel::Info) => Some(PreparedTracingLevel::Info),
Some(TraceEventLevel::Warn) => Some(PreparedTracingLevel::Warn),
Some(TraceEventLevel::Error) => Some(PreparedTracingLevel::Error),
None => None,
}
}