use serde::{Deserialize, Serialize};
use std::path::PathBuf;
#[derive(Debug, Clone, Copy, Serialize, Deserialize, Default, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
pub enum LogFormat {
#[default]
Pretty,
Json,
Compact,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, Default, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
pub enum LogLevel {
Trace,
Debug,
#[default]
Info,
Warn,
Error,
}
impl From<LogLevel> for tracing::Level {
fn from(level: LogLevel) -> Self {
match level {
LogLevel::Trace => tracing::Level::TRACE,
LogLevel::Debug => tracing::Level::DEBUG,
LogLevel::Info => tracing::Level::INFO,
LogLevel::Warn => tracing::Level::WARN,
LogLevel::Error => tracing::Level::ERROR,
}
}
}
impl From<LogLevel> for tracing_subscriber::filter::LevelFilter {
fn from(level: LogLevel) -> Self {
match level {
LogLevel::Trace => tracing_subscriber::filter::LevelFilter::TRACE,
LogLevel::Debug => tracing_subscriber::filter::LevelFilter::DEBUG,
LogLevel::Info => tracing_subscriber::filter::LevelFilter::INFO,
LogLevel::Warn => tracing_subscriber::filter::LevelFilter::WARN,
LogLevel::Error => tracing_subscriber::filter::LevelFilter::ERROR,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LoggingConfig {
#[serde(default)]
pub level: LogLevel,
#[serde(default)]
pub format: LogFormat,
#[serde(default)]
pub file: Option<FileLoggingConfig>,
#[serde(default = "default_true")]
pub include_location: bool,
#[serde(default = "default_true")]
pub include_target: bool,
#[serde(default)]
pub filter_directives: Option<String>,
}
fn default_true() -> bool {
true
}
impl Default for LoggingConfig {
fn default() -> Self {
Self {
level: LogLevel::Info,
format: LogFormat::Pretty,
file: None,
include_location: true,
include_target: true,
filter_directives: None,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FileLoggingConfig {
pub directory: PathBuf,
#[serde(default = "default_prefix")]
pub prefix: String,
#[serde(default)]
pub rotation: RotationStrategy,
#[serde(default = "default_max_files")]
pub max_files: Option<usize>,
}
#[allow(clippy::unnecessary_wraps)]
fn default_max_files() -> Option<usize> {
Some(7)
}
fn default_prefix() -> String {
"zlayer".to_string()
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, Default, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
pub enum RotationStrategy {
#[default]
Daily,
Hourly,
Never,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LogOutputConfig {
#[serde(default)]
pub destination: LogDestination,
#[serde(default = "default_max_log_size")]
pub max_size_bytes: u64,
#[serde(default = "default_log_retention_secs")]
pub retention_secs: u64,
}
fn default_max_log_size() -> u64 {
100 * 1024 * 1024 }
fn default_log_retention_secs() -> u64 {
7 * 24 * 60 * 60 }
impl Default for LogOutputConfig {
fn default() -> Self {
Self {
destination: LogDestination::default(),
max_size_bytes: default_max_log_size(),
retention_secs: default_log_retention_secs(),
}
}
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, Default, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
pub enum LogDestination {
#[default]
Disk,
Memory,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MetricsConfig {
#[serde(default = "default_true")]
pub enabled: bool,
#[serde(default = "default_metrics_path")]
pub path: String,
#[serde(default)]
pub port: Option<u16>,
}
fn default_metrics_path() -> String {
"/metrics".to_string()
}
impl Default for MetricsConfig {
fn default() -> Self {
Self {
enabled: true,
path: default_metrics_path(),
port: None,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TracingConfig {
#[serde(default)]
pub enabled: bool,
#[serde(default)]
pub otlp_endpoint: Option<String>,
#[serde(default = "default_service_name")]
pub service_name: String,
#[serde(default = "default_sampling_ratio")]
pub sampling_ratio: f64,
#[serde(default)]
pub environment: Option<String>,
#[serde(default)]
pub batch: BatchConfig,
#[serde(default = "default_true")]
pub use_grpc: bool,
}
fn default_service_name() -> String {
"zlayer".to_string()
}
fn default_sampling_ratio() -> f64 {
1.0
}
fn default_max_queue_size() -> usize {
2048
}
fn default_scheduled_delay() -> u64 {
5000
}
fn default_max_export_batch_size() -> usize {
512
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BatchConfig {
#[serde(default = "default_max_queue_size")]
pub max_queue_size: usize,
#[serde(default = "default_scheduled_delay")]
pub scheduled_delay_ms: u64,
#[serde(default = "default_max_export_batch_size")]
pub max_export_batch_size: usize,
}
impl Default for BatchConfig {
fn default() -> Self {
Self {
max_queue_size: default_max_queue_size(),
scheduled_delay_ms: default_scheduled_delay(),
max_export_batch_size: default_max_export_batch_size(),
}
}
}
impl Default for TracingConfig {
fn default() -> Self {
Self {
enabled: false,
otlp_endpoint: None,
service_name: default_service_name(),
sampling_ratio: default_sampling_ratio(),
environment: None,
batch: BatchConfig::default(),
use_grpc: true,
}
}
}
impl TracingConfig {
#[must_use]
pub fn from_env() -> Self {
Self {
enabled: std::env::var("OTEL_TRACES_ENABLED")
.map(|v| v == "true" || v == "1")
.unwrap_or(false),
otlp_endpoint: std::env::var("OTEL_EXPORTER_OTLP_ENDPOINT").ok(),
service_name: std::env::var("OTEL_SERVICE_NAME")
.unwrap_or_else(|_| "zlayer".to_string()),
sampling_ratio: std::env::var("OTEL_TRACES_SAMPLER_ARG")
.ok()
.and_then(|v| v.parse().ok())
.unwrap_or(1.0),
environment: std::env::var("DEPLOYMENT_ENVIRONMENT").ok(),
batch: BatchConfig::default(),
use_grpc: std::env::var("OTEL_EXPORTER_OTLP_PROTOCOL")
.map(|v| v != "http/protobuf")
.unwrap_or(true),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct ObservabilityConfig {
#[serde(default)]
pub logging: LoggingConfig,
#[serde(default)]
pub metrics: MetricsConfig,
#[serde(default)]
pub tracing: TracingConfig,
}