Skip to main content

zlayer_observability/
config.rs

1//! Configuration types for observability
2
3use serde::{Deserialize, Serialize};
4use std::path::PathBuf;
5
6/// Log output format
7#[derive(Debug, Clone, Copy, Serialize, Deserialize, Default, PartialEq, Eq)]
8#[serde(rename_all = "lowercase")]
9pub enum LogFormat {
10    /// Human-readable pretty format
11    #[default]
12    Pretty,
13    /// JSON format for log aggregation
14    Json,
15    /// Compact format (single line)
16    Compact,
17}
18
19/// Log level
20#[derive(Debug, Clone, Copy, Serialize, Deserialize, Default, PartialEq, Eq)]
21#[serde(rename_all = "lowercase")]
22pub enum LogLevel {
23    Trace,
24    Debug,
25    #[default]
26    Info,
27    Warn,
28    Error,
29}
30
31impl From<LogLevel> for tracing::Level {
32    fn from(level: LogLevel) -> Self {
33        match level {
34            LogLevel::Trace => tracing::Level::TRACE,
35            LogLevel::Debug => tracing::Level::DEBUG,
36            LogLevel::Info => tracing::Level::INFO,
37            LogLevel::Warn => tracing::Level::WARN,
38            LogLevel::Error => tracing::Level::ERROR,
39        }
40    }
41}
42
43impl From<LogLevel> for tracing_subscriber::filter::LevelFilter {
44    fn from(level: LogLevel) -> Self {
45        match level {
46            LogLevel::Trace => tracing_subscriber::filter::LevelFilter::TRACE,
47            LogLevel::Debug => tracing_subscriber::filter::LevelFilter::DEBUG,
48            LogLevel::Info => tracing_subscriber::filter::LevelFilter::INFO,
49            LogLevel::Warn => tracing_subscriber::filter::LevelFilter::WARN,
50            LogLevel::Error => tracing_subscriber::filter::LevelFilter::ERROR,
51        }
52    }
53}
54
55/// Logging configuration
56#[derive(Debug, Clone, Serialize, Deserialize)]
57pub struct LoggingConfig {
58    /// Log level
59    #[serde(default)]
60    pub level: LogLevel,
61
62    /// Output format
63    #[serde(default)]
64    pub format: LogFormat,
65
66    /// Log to file (optional)
67    #[serde(default)]
68    pub file: Option<FileLoggingConfig>,
69
70    /// Include source code location in logs
71    #[serde(default = "default_true")]
72    pub include_location: bool,
73
74    /// Include target (module path) in logs
75    #[serde(default = "default_true")]
76    pub include_target: bool,
77
78    /// Per-crate tracing filter directives (e.g. "`runtime=info,zlayer_agent=warn,warn`")
79    /// When set, overrides the global `level` for `EnvFilter` construction.
80    #[serde(default)]
81    pub filter_directives: Option<String>,
82}
83
84fn default_true() -> bool {
85    true
86}
87
88impl Default for LoggingConfig {
89    fn default() -> Self {
90        Self {
91            level: LogLevel::Info,
92            format: LogFormat::Pretty,
93            file: None,
94            include_location: true,
95            include_target: true,
96            filter_directives: None,
97        }
98    }
99}
100
101/// File logging configuration
102#[derive(Debug, Clone, Serialize, Deserialize)]
103pub struct FileLoggingConfig {
104    /// Directory for log files
105    pub directory: PathBuf,
106
107    /// File name prefix
108    #[serde(default = "default_prefix")]
109    pub prefix: String,
110
111    /// Rotation strategy
112    #[serde(default)]
113    pub rotation: RotationStrategy,
114
115    /// Maximum number of rotated log files to keep. Oldest files beyond
116    /// this limit are deleted at startup. `None` means no limit.
117    #[serde(default = "default_max_files")]
118    pub max_files: Option<usize>,
119}
120
121#[allow(clippy::unnecessary_wraps)]
122fn default_max_files() -> Option<usize> {
123    Some(7)
124}
125
126fn default_prefix() -> String {
127    "zlayer".to_string()
128}
129
130/// Log file rotation strategy
131#[derive(Debug, Clone, Copy, Serialize, Deserialize, Default, PartialEq, Eq)]
132#[serde(rename_all = "lowercase")]
133pub enum RotationStrategy {
134    /// Rotate daily
135    #[default]
136    Daily,
137    /// Rotate hourly
138    Hourly,
139    /// Never rotate (single file)
140    Never,
141}
142
143/// Log output configuration for containers and executions.
144#[derive(Debug, Clone, Serialize, Deserialize)]
145pub struct LogOutputConfig {
146    /// Where to write logs.
147    #[serde(default)]
148    pub destination: LogDestination,
149
150    /// Maximum total log size in bytes before rotation/truncation.
151    /// Default: 100MB.
152    #[serde(default = "default_max_log_size")]
153    pub max_size_bytes: u64,
154
155    /// How long to retain log files. Default: 7 days for disk, 1 hour for memory.
156    #[serde(default = "default_log_retention_secs")]
157    pub retention_secs: u64,
158}
159
160fn default_max_log_size() -> u64 {
161    100 * 1024 * 1024 // 100MB
162}
163
164fn default_log_retention_secs() -> u64 {
165    7 * 24 * 60 * 60 // 7 days
166}
167
168impl Default for LogOutputConfig {
169    fn default() -> Self {
170        Self {
171            destination: LogDestination::default(),
172            max_size_bytes: default_max_log_size(),
173            retention_secs: default_log_retention_secs(),
174        }
175    }
176}
177
178/// Where to write container/execution logs.
179#[derive(Debug, Clone, Copy, Serialize, Deserialize, Default, PartialEq, Eq)]
180#[serde(rename_all = "lowercase")]
181pub enum LogDestination {
182    /// Write to disk as JSONL files (default).
183    #[default]
184    Disk,
185    /// Keep in memory with ring buffer (for short-lived executions).
186    Memory,
187}
188
189/// Metrics configuration
190#[derive(Debug, Clone, Serialize, Deserialize)]
191pub struct MetricsConfig {
192    /// Enable Prometheus metrics
193    #[serde(default = "default_true")]
194    pub enabled: bool,
195
196    /// Metrics endpoint path
197    #[serde(default = "default_metrics_path")]
198    pub path: String,
199
200    /// Port for standalone metrics server (if not using API)
201    #[serde(default)]
202    pub port: Option<u16>,
203}
204
205fn default_metrics_path() -> String {
206    "/metrics".to_string()
207}
208
209impl Default for MetricsConfig {
210    fn default() -> Self {
211        Self {
212            enabled: true,
213            path: default_metrics_path(),
214            port: None,
215        }
216    }
217}
218
219/// Tracing (OpenTelemetry) configuration
220#[derive(Debug, Clone, Serialize, Deserialize)]
221pub struct TracingConfig {
222    /// Enable distributed tracing
223    #[serde(default)]
224    pub enabled: bool,
225
226    /// OTLP endpoint (e.g., "<http://localhost:4317>")
227    #[serde(default)]
228    pub otlp_endpoint: Option<String>,
229
230    /// Service name for traces
231    #[serde(default = "default_service_name")]
232    pub service_name: String,
233
234    /// Sampling ratio (0.0 to 1.0)
235    #[serde(default = "default_sampling_ratio")]
236    pub sampling_ratio: f64,
237
238    /// Deployment environment (production, staging, development)
239    #[serde(default)]
240    pub environment: Option<String>,
241
242    /// Batch export configuration
243    #[serde(default)]
244    pub batch: BatchConfig,
245
246    /// Use gRPC (true) or HTTP (false) for OTLP
247    #[serde(default = "default_true")]
248    pub use_grpc: bool,
249}
250
251fn default_service_name() -> String {
252    "zlayer".to_string()
253}
254
255fn default_sampling_ratio() -> f64 {
256    1.0
257}
258
259fn default_max_queue_size() -> usize {
260    2048
261}
262
263fn default_scheduled_delay() -> u64 {
264    5000
265}
266
267fn default_max_export_batch_size() -> usize {
268    512
269}
270
271/// Batch export configuration for OpenTelemetry
272#[derive(Debug, Clone, Serialize, Deserialize)]
273pub struct BatchConfig {
274    /// Maximum queue size before dropping spans (default: 2048)
275    #[serde(default = "default_max_queue_size")]
276    pub max_queue_size: usize,
277
278    /// Scheduled delay for batch export in milliseconds (default: 5000)
279    #[serde(default = "default_scheduled_delay")]
280    pub scheduled_delay_ms: u64,
281
282    /// Maximum export batch size (default: 512)
283    #[serde(default = "default_max_export_batch_size")]
284    pub max_export_batch_size: usize,
285}
286
287impl Default for BatchConfig {
288    fn default() -> Self {
289        Self {
290            max_queue_size: default_max_queue_size(),
291            scheduled_delay_ms: default_scheduled_delay(),
292            max_export_batch_size: default_max_export_batch_size(),
293        }
294    }
295}
296
297impl Default for TracingConfig {
298    fn default() -> Self {
299        Self {
300            enabled: false,
301            otlp_endpoint: None,
302            service_name: default_service_name(),
303            sampling_ratio: default_sampling_ratio(),
304            environment: None,
305            batch: BatchConfig::default(),
306            use_grpc: true,
307        }
308    }
309}
310
311impl TracingConfig {
312    /// Load from environment variables with fallback to defaults
313    #[must_use]
314    pub fn from_env() -> Self {
315        Self {
316            enabled: std::env::var("OTEL_TRACES_ENABLED")
317                .map(|v| v == "true" || v == "1")
318                .unwrap_or(false),
319            otlp_endpoint: std::env::var("OTEL_EXPORTER_OTLP_ENDPOINT").ok(),
320            service_name: std::env::var("OTEL_SERVICE_NAME")
321                .unwrap_or_else(|_| "zlayer".to_string()),
322            sampling_ratio: std::env::var("OTEL_TRACES_SAMPLER_ARG")
323                .ok()
324                .and_then(|v| v.parse().ok())
325                .unwrap_or(1.0),
326            environment: std::env::var("DEPLOYMENT_ENVIRONMENT").ok(),
327            batch: BatchConfig::default(),
328            use_grpc: std::env::var("OTEL_EXPORTER_OTLP_PROTOCOL")
329                .map(|v| v != "http/protobuf")
330                .unwrap_or(true),
331        }
332    }
333}
334
335/// Complete observability configuration
336#[derive(Debug, Clone, Serialize, Deserialize, Default)]
337pub struct ObservabilityConfig {
338    /// Logging configuration
339    #[serde(default)]
340    pub logging: LoggingConfig,
341
342    /// Metrics configuration
343    #[serde(default)]
344    pub metrics: MetricsConfig,
345
346    /// Tracing configuration
347    #[serde(default)]
348    pub tracing: TracingConfig,
349}