pub use tracing::{debug, error, info, warn};
use tracing_subscriber::{EnvFilter, fmt, layer::SubscriberExt, util::SubscriberInitExt};
#[derive(Debug, Clone)]
pub enum TracingBackend {
Console,
CloudWatch,
#[cfg(feature = "sentry")]
Sentry,
}
use std::str::FromStr;
impl FromStr for TracingBackend {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
TracingBackend::parse(s)
}
}
impl TracingBackend {
fn parse(backend: &str) -> Result<Self, String> {
match backend.to_lowercase().as_str() {
"console" => Ok(Self::Console),
"cloudwatch" => Ok(Self::CloudWatch),
#[cfg(feature = "sentry")]
"sentry" => Ok(Self::Sentry),
#[cfg(not(feature = "sentry"))]
"sentry" => Err(
"Sentry feature not enabled. Enable the 'sentry' feature to use this backend."
.to_string(),
),
other => Err(format!(
"Invalid tracing backend '{}'. Supported backends: console, cloudwatch{}",
other,
if cfg!(feature = "sentry") {
", sentry"
} else {
""
}
)),
}
}
}
impl std::fmt::Display for TracingBackend {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Console => write!(f, "console"),
Self::CloudWatch => write!(f, "cloudwatch"),
#[cfg(feature = "sentry")]
Self::Sentry => write!(f, "sentry"),
}
}
}
#[derive(Debug, Clone)]
pub struct TracingConfig {
pub backend: TracingBackend,
pub log_level: Option<String>,
pub json_format: bool,
pub include_timestamp: bool,
pub include_target: bool,
pub include_ansi: bool,
}
impl Default for TracingConfig {
fn default() -> Self {
Self {
backend: TracingBackend::Console,
log_level: None,
json_format: false,
include_timestamp: true,
include_target: true,
include_ansi: true,
}
}
}
pub struct Tracing {
config: TracingConfig,
}
impl Default for Tracing {
fn default() -> Self {
Self::new()
}
}
impl Tracing {
pub fn new() -> Self {
Self {
config: TracingConfig::default(),
}
}
pub fn with_backend(mut self, backend: impl AsRef<str>) -> Result<Self, String> {
self.config.backend = TracingBackend::from_str(backend.as_ref())?;
Ok(self)
}
pub fn with_backend_enum(mut self, backend: TracingBackend) -> Self {
self.config.backend = backend;
self
}
pub fn with_log_level(mut self, level: impl Into<String>) -> Self {
self.config.log_level = Some(level.into());
self
}
pub fn with_json_format(mut self, json: bool) -> Self {
self.config.json_format = json;
self
}
pub fn with_timestamp(mut self, timestamp: bool) -> Self {
self.config.include_timestamp = timestamp;
self
}
pub fn with_target(mut self, target: bool) -> Self {
self.config.include_target = target;
self
}
pub fn with_ansi(mut self, ansi: bool) -> Self {
self.config.include_ansi = ansi;
self
}
fn init_console(&self) -> Result<(), String> {
let env_filter = if let Some(ref level) = self.config.log_level {
EnvFilter::try_new(level).map_err(|e| format!("Invalid log level: {e}"))?
} else {
EnvFilter::from_default_env()
};
if self.config.json_format {
if self.config.include_timestamp {
let json_layer = fmt::layer()
.json()
.with_ansi(self.config.include_ansi)
.with_target(self.config.include_target);
tracing_subscriber::registry()
.with(json_layer)
.with(env_filter)
.try_init()
.map_err(|e| format!("Failed to initialize tracing: {e}"))?;
} else {
let json_layer = fmt::layer()
.json()
.with_ansi(self.config.include_ansi)
.with_target(self.config.include_target)
.without_time();
tracing_subscriber::registry()
.with(json_layer)
.with(env_filter)
.try_init()
.map_err(|e| format!("Failed to initialize tracing: {e}"))?;
}
} else if self.config.include_timestamp {
let layer = fmt::layer()
.with_ansi(self.config.include_ansi)
.with_target(self.config.include_target);
tracing_subscriber::registry()
.with(layer)
.with(env_filter)
.try_init()
.map_err(|e| format!("Failed to initialize tracing: {e}"))?;
} else {
let layer = fmt::layer()
.with_ansi(self.config.include_ansi)
.with_target(self.config.include_target)
.without_time();
tracing_subscriber::registry()
.with(layer)
.with(env_filter)
.try_init()
.map_err(|e| format!("Failed to initialize tracing: {e}"))?;
}
Ok(())
}
fn init_cloudwatch(&self) -> Result<(), String> {
let aws_layer = fmt::layer()
.json()
.with_current_span(false)
.with_ansi(false)
.without_time()
.with_target(false);
let env_filter = if let Some(ref level) = self.config.log_level {
EnvFilter::try_new(level).map_err(|e| format!("Invalid log level: {e}"))?
} else {
EnvFilter::from_default_env()
};
tracing_subscriber::registry()
.with(aws_layer)
.with(env_filter)
.try_init()
.map_err(|e| format!("Failed to initialize CloudWatch tracing: {e}"))?;
Ok(())
}
#[cfg(feature = "sentry")]
fn init_sentry(&self) -> Result<(), String> {
use crate::sentry::guard;
guard();
let sentry_layer = sentry_tracing::layer();
let env_filter = if let Some(ref level) = self.config.log_level {
EnvFilter::try_new(level).map_err(|e| format!("Invalid log level: {e}"))?
} else {
EnvFilter::from_default_env()
};
tracing_subscriber::registry()
.with(sentry_layer)
.with(env_filter)
.try_init()
.map_err(|e| format!("Failed to initialize Sentry tracing: {e}"))?;
Ok(())
}
pub fn build(self) -> Result<(), String> {
match self.config.backend {
TracingBackend::Console => {
self.init_console()?;
info!(
"Console tracing initialized with backend: {}",
self.config.backend
);
}
TracingBackend::CloudWatch => {
self.init_cloudwatch()?;
info!(
"CloudWatch tracing initialized with backend: {}",
self.config.backend
);
}
#[cfg(feature = "sentry")]
TracingBackend::Sentry => {
self.init_sentry()?;
info!(
"Sentry tracing initialized with backend: {}",
self.config.backend
);
}
}
Ok(())
}
pub fn build_or_panic(self) {
if let Err(e) = self.build() {
panic!("Failed to initialize tracing: {e}");
}
}
pub fn console() -> Result<(), String> {
Self::new().build()
}
pub fn cloudwatch() -> Result<(), String> {
Self::new()
.with_backend_enum(TracingBackend::CloudWatch)
.build()
}
pub fn console_json() -> Result<(), String> {
Self::new().with_json_format(true).build()
}
pub fn from_env() -> Result<(), String> {
let backend = std::env::var("TRACING_BACKEND").unwrap_or_else(|_| "console".to_string());
let mut tracer = Self::new().with_backend(&backend)?;
if std::env::var("TRACING_JSON").unwrap_or_default() == "true" {
tracer = tracer.with_json_format(true);
}
if std::env::var("NO_COLOR").is_ok() {
tracer = tracer.with_ansi(false);
}
tracer.build()
}
}