wire_framework/vlog/logs/
mod.rs1use std::{backtrace::Backtrace, str::FromStr};
2
3use serde::Deserialize;
4use tracing_subscriber::{EnvFilter, Layer, fmt, registry::LookupSpan};
5
6mod layer;
7
8#[derive(Debug, Clone, Copy, Default, Deserialize)]
10#[serde(rename_all = "lowercase")]
11pub enum LogFormat {
12 #[default]
13 Plain,
14 Json,
15}
16
17impl FromStr for LogFormat {
18 type Err = LogFormatError;
19
20 fn from_str(s: &str) -> Result<Self, Self::Err> {
21 match s {
22 "plain" => Ok(Self::Plain),
23 "json" => Ok(Self::Json),
24 _ => Err(LogFormatError::InvalidFormat),
25 }
26 }
27}
28
29#[derive(Debug, thiserror::Error)]
30#[non_exhaustive]
31pub enum LogFormatError {
32 #[error("Invalid log format")]
33 InvalidFormat,
34}
35
36#[derive(Debug, Default)]
37pub struct Logs {
38 format: LogFormat,
39 log_directives: Option<String>,
40 disable_default_logs: bool,
41}
42
43impl From<LogFormat> for Logs {
44 fn from(format: LogFormat) -> Self {
45 Self {
46 format,
47 log_directives: None,
48 disable_default_logs: false,
49 }
50 }
51}
52
53impl Logs {
54 pub fn new(format: &str) -> Result<Self, LogFormatError> {
55 Ok(Self {
56 format: format.parse()?,
57 log_directives: None,
58 disable_default_logs: false,
59 })
60 }
61
62 pub(super) fn build_filter(&self) -> EnvFilter {
75 let mut directives = if self.disable_default_logs {
76 String::new()
77 } else {
78 format!("info,wire_framework=warn,")
79 };
80 if let Some(log_directives) = &self.log_directives {
81 directives.push_str(log_directives);
82 } else if let Ok(env_directives) = std::env::var(EnvFilter::DEFAULT_ENV) {
83 directives.push_str(&env_directives);
84 };
85 EnvFilter::new(directives)
86 }
87
88 pub fn with_log_directives(mut self, log_directives: Option<String>) -> Self {
89 self.log_directives = log_directives;
90 self
91 }
92
93 pub fn install_panic_hook(&self) {
94 if matches!(self.format, LogFormat::Json) {
99 let _ = std::panic::take_hook();
101 std::panic::set_hook(Box::new(json_panic_handler));
103 };
104 }
105
106 pub fn into_layer<S>(self) -> impl Layer<S>
107 where
108 S: tracing::Subscriber + for<'span> LookupSpan<'span> + Send + Sync,
109 {
110 let filter = self.build_filter();
111 let layer = match self.format {
112 LogFormat::Plain => layer::LogsLayer::Plain(fmt::Layer::new()),
113 LogFormat::Json => {
114 let timer = tracing_subscriber::fmt::time::UtcTime::rfc_3339();
115 let json_layer = fmt::Layer::default()
116 .with_file(true)
117 .with_line_number(true)
118 .with_timer(timer)
119 .json();
120 layer::LogsLayer::Json(json_layer)
121 }
122 };
123 layer.with_filter(filter)
124 }
125}
126
127#[allow(deprecated)] fn json_panic_handler(panic_info: &std::panic::PanicInfo) {
129 let backtrace = Backtrace::force_capture();
130 let timestamp = chrono::Utc::now();
131 let panic_message = if let Some(s) = panic_info.payload().downcast_ref::<String>() {
132 s.as_str()
133 } else if let Some(s) = panic_info.payload().downcast_ref::<&str>() {
134 s
135 } else {
136 "Panic occurred without additional info"
137 };
138
139 let panic_location = panic_info
140 .location()
141 .map(|val| val.to_string())
142 .unwrap_or_else(|| "Unknown location".to_owned());
143
144 let backtrace_str = backtrace.to_string();
145 let timestamp_str = timestamp.format("%Y-%m-%dT%H:%M:%S%.fZ").to_string();
146
147 println!(
148 "{}",
149 serde_json::json!({
150 "timestamp": timestamp_str,
151 "level": "CRITICAL",
152 "fields": {
153 "message": panic_message,
154 "location": panic_location,
155 "backtrace": backtrace_str,
156 }
157 })
158 );
159}