mod config;
pub mod context;
mod exporters;
pub mod logging;
pub mod metrics;
pub mod tracing;
use crate::config::{ExporterType, ObservabilityConfig};
use crate::context::initialize_global_context;
use crate::exporters::stdout_exporter::{StdoutExporter, StdoutFormat};
use crate::logging::{LogLevel, LoggerBuilder};
use crate::metrics::MetricsBuilder;
use crate::tracing::TracingBuilder;
pub use chrono;
pub use serde_json;
pub fn initialize_observability(config_path: &str) -> Result<(), Box<dyn std::error::Error>> {
let config = ObservabilityConfig::from_file(config_path)?;
initialize_global_context(
config.project.name.clone(),
config.project.app_name.clone(),
config.project.service_name.clone(),
);
if config.logging.enabled {
let mut log_builder = LoggerBuilder::new();
log_builder = log_builder.with_log_level(match config.logging.level.as_str() {
"DEBUG" => LogLevel::DEBUG,
"INFO" => LogLevel::INFO,
"WARN" => LogLevel::WARN,
"ERROR" => LogLevel::ERROR,
_ => LogLevel::INFO,
});
match config.logging.exporter.r#type {
ExporterType::Stdout => {
let format = match config
.logging
.exporter
.properties
.get("format")
.and_then(|v| v.as_str())
{
Some("minimal") => StdoutFormat::Minimal,
_ => StdoutFormat::Extended,
};
log_builder = log_builder.with_exporter(Box::new(StdoutExporter::new(format)));
} }
log_builder.initialize();
}
if config.tracing.enabled {
let mut trace_builder = TracingBuilder::new();
match config.tracing.exporter.r#type {
ExporterType::Stdout => {
let format = match config
.tracing
.exporter
.properties
.get("format")
.and_then(|v| v.as_str())
{
Some("minimal") => StdoutFormat::Minimal,
_ => StdoutFormat::Extended,
};
trace_builder = trace_builder.with_exporter(Box::new(StdoutExporter::new(format)));
} }
trace_builder.initialize();
}
if config.metrics.enabled {
let mut metrics_builder = MetricsBuilder::new();
metrics_builder = metrics_builder.with_interval_secs(config.metrics.interval_secs);
match config.metrics.exporter.r#type {
ExporterType::Stdout => {
let format = match config
.metrics
.exporter
.properties
.get("format")
.and_then(|v| v.as_str())
{
Some("minimal") => StdoutFormat::Minimal,
_ => StdoutFormat::Extended,
};
metrics_builder =
metrics_builder.with_exporter(Box::new(StdoutExporter::new(format)));
} }
if config.metrics.http.enabled {
metrics_builder = metrics_builder.with_http_metrics();
}
if config.metrics.grpc.enabled {
metrics_builder = metrics_builder.with_grpc_metrics();
}
if config.metrics.websocket.enabled {
metrics_builder = metrics_builder.with_websocket_metrics();
}
if config.metrics.system.enabled {
metrics_builder = metrics_builder.with_system_metrics();
}
if config.metrics.process.enabled {
metrics_builder = metrics_builder.with_process_metrics();
}
metrics_builder.initialize();
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::config::ObservabilityConfig;
use crate::logging::{LogLevel, LogMessage};
use crate::metrics::{Metrics, SystemMetrics};
use crate::tracing::{SpanStatus, SpanType, TraceContext};
use chrono::Utc;
use std::collections::HashMap;
#[test]
fn test_config_parsing() {
let config_str = r#"
[project]
name = "test_project"
app_name = "test_app"
service_name = "test_service"
[logging]
enabled = true
level = "INFO"
[logging.exporter]
type = "stdout"
format = "extended"
[tracing]
enabled = true
[tracing.exporter]
type = "stdout"
format = "extended"
[metrics]
enabled = true
interval_secs = 10
[metrics.exporter]
type = "stdout"
format = "extended"
[metrics.http]
enabled = true
[metrics.grpc]
enabled = false
[metrics.websocket]
enabled = false
[metrics.system]
enabled = true
[metrics.process]
enabled = true
"#;
let config: ObservabilityConfig = toml::from_str(config_str).unwrap();
assert_eq!(config.project.name, "test_project");
assert_eq!(config.project.app_name, "test_app");
assert_eq!(config.project.service_name, "test_service");
assert!(config.logging.enabled);
assert_eq!(config.logging.level, "INFO");
assert!(config.tracing.enabled);
assert!(config.metrics.enabled);
assert_eq!(config.metrics.interval_secs, 10);
assert!(config.metrics.http.enabled);
assert!(!config.metrics.grpc.enabled);
assert!(!config.metrics.websocket.enabled);
assert!(config.metrics.system.enabled);
assert!(config.metrics.process.enabled);
}
#[test]
fn test_log_message_creation() {
let log_message = LogMessage {
timestamp: Utc::now(),
level: LogLevel::INFO,
project_name: "test_project".to_string(),
app_name: "test_app".to_string(),
service_name: "test_service".to_string(),
replica_id: "test_replica".to_string(),
target: "test_target",
message: "Test log message".into(),
context: None,
attributes: HashMap::new(),
};
assert_eq!(log_message.level, LogLevel::INFO);
assert_eq!(log_message.project_name, "test_project");
assert_eq!(log_message.message, "Test log message");
}
#[test]
fn test_metrics_creation() {
let metrics = Metrics {
timestamp: 0,
interval_secs: 10,
project_name: "test_project".to_string(),
app_name: "test_app".to_string(),
service_name: "test_service".to_string(),
replica_id: "test_replica".to_string(),
system_metrics: Some(SystemMetrics {
cpu_usage: 50.0,
cpu_cores_usage: vec![40.0, 60.0].into(),
memory_total: 16000000000,
memory_used: 8000000000,
swap_total: 8000000000,
swap_used: 1000000000,
disk_total_space: 500000000000,
disk_available_space: 250000000000,
}),
process_metrics: None,
http_metrics: None,
grpc_metrics: None,
websocket_metrics: None,
};
assert_eq!(metrics.interval_secs, 10);
assert_eq!(metrics.project_name, "test_project");
assert!(metrics.system_metrics.is_some());
let system_metrics = metrics.system_metrics.unwrap();
assert_eq!(system_metrics.cpu_usage, 50.0);
assert_eq!(system_metrics.memory_total, 16000000000);
}
#[test]
fn test_trace_context_creation() {
let trace_context = TraceContext::new(SpanType::Internal, "test_operation".to_string());
assert_eq!(trace_context.span_type, SpanType::Internal.as_str());
assert_eq!(trace_context.operation_name, "test_operation");
assert!(trace_context.end_ts.is_none());
assert_eq!(
*trace_context.span_status.lock().unwrap(),
SpanStatus::InProgress
);
}
#[test]
fn test_trace_context_end_span() {
let mut trace_context = TraceContext::new(SpanType::Internal, "test_operation".to_string());
trace_context.end_span();
assert!(trace_context.end_ts.is_some());
assert_eq!(*trace_context.span_status.lock().unwrap(), SpanStatus::OK);
}
#[test]
fn test_trace_context_signal_error() {
let trace_context = TraceContext::new(SpanType::Internal, "test_operation".to_string());
trace_context.signal_error();
assert_eq!(
*trace_context.span_status.lock().unwrap(),
SpanStatus::Error
);
}
}