413 lines
9.5 KiB
Markdown
413 lines
9.5 KiB
Markdown
|
|
# Observability Crate
|
||
|
|
|
||
|
|
Multi-backend observability framework providing unified support for logging, metrics, distributed tracing, health checks, and dashboard generation.
|
||
|
|
|
||
|
|
## Features
|
||
|
|
|
||
|
|
- **Logging**: Structured logging via `tracing` with configurable output formats (JSON, Pretty, Compact)
|
||
|
|
- **Metrics**: Prometheus pull-based metrics (default) + optional OpenTelemetry push
|
||
|
|
- **Distributed Tracing**: Optional OpenTelemetry support for distributed tracing
|
||
|
|
- **Health Checks**: Kubernetes-style liveness, readiness, and startup probes
|
||
|
|
- **Dashboards**: Predefined Grafana dashboard templates + programmatic dashboard generation
|
||
|
|
- **Multi-Backend**: Flexible architecture supporting multiple exporters simultaneously
|
||
|
|
|
||
|
|
## Cargo Features
|
||
|
|
|
||
|
|
```toml
|
||
|
|
[dependencies]
|
||
|
|
observability = { version = "0.1", features = ["full"] }
|
||
|
|
```
|
||
|
|
|
||
|
|
### Available Features
|
||
|
|
|
||
|
|
- **`logging`** (default): Structured logging with `tracing-subscriber`
|
||
|
|
- **`metrics-prometheus`** (default): Prometheus metrics exporter
|
||
|
|
- **`metrics-otlp`**: OpenTelemetry metrics push support
|
||
|
|
- **`tracing-otlp`**: Distributed tracing via OpenTelemetry
|
||
|
|
- **`health`** (default): HTTP health check endpoints
|
||
|
|
- **`dashboards`**: Grafana dashboard generation and templates
|
||
|
|
- **`full`**: All features enabled
|
||
|
|
|
||
|
|
## Quick Start
|
||
|
|
|
||
|
|
### Basic Initialization
|
||
|
|
|
||
|
|
```rust
|
||
|
|
use observability::init_from_env;
|
||
|
|
|
||
|
|
#[tokio::main]
|
||
|
|
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||
|
|
// Initialize observability with defaults
|
||
|
|
let _guard = observability::init_from_env("my-app", "1.0.0")?;
|
||
|
|
|
||
|
|
// Logging via tracing macros
|
||
|
|
tracing::info!("Application started");
|
||
|
|
tracing::warn!("Something warning-worthy happened");
|
||
|
|
|
||
|
|
// Metrics
|
||
|
|
observability::metrics::counter!("requests_total").increment();
|
||
|
|
observability::metrics::gauge!("active_connections").set(42.0);
|
||
|
|
observability::metrics::histogram!("request_duration_seconds").record(0.125);
|
||
|
|
|
||
|
|
// Health checks
|
||
|
|
// GET /healthz (liveness)
|
||
|
|
// GET /ready (readiness)
|
||
|
|
// GET /startup (startup)
|
||
|
|
|
||
|
|
Ok(())
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### Configuration from TOML
|
||
|
|
|
||
|
|
```rust
|
||
|
|
use observability::init_from_toml;
|
||
|
|
|
||
|
|
#[tokio::main]
|
||
|
|
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||
|
|
let _guard = observability::init_from_toml("config/observability.toml")?;
|
||
|
|
Ok(())
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### Configuration Structure
|
||
|
|
|
||
|
|
```toml
|
||
|
|
[context]
|
||
|
|
service_name = "my-app"
|
||
|
|
service_version = "1.0.0"
|
||
|
|
environment = "production"
|
||
|
|
|
||
|
|
[logging]
|
||
|
|
level = "info"
|
||
|
|
format = "json"
|
||
|
|
include_spans = true
|
||
|
|
outputs = ["stdout"]
|
||
|
|
|
||
|
|
[metrics]
|
||
|
|
enabled = true
|
||
|
|
export_interval_secs = 60
|
||
|
|
|
||
|
|
[metrics.prometheus]
|
||
|
|
port = 9090
|
||
|
|
path = "/metrics"
|
||
|
|
|
||
|
|
[health]
|
||
|
|
enabled = true
|
||
|
|
port = 8080
|
||
|
|
liveness_path = "/healthz"
|
||
|
|
readiness_path = "/ready"
|
||
|
|
|
||
|
|
[dashboards]
|
||
|
|
auto_generate = true
|
||
|
|
output_dir = "./dashboards"
|
||
|
|
```
|
||
|
|
|
||
|
|
## Logging
|
||
|
|
|
||
|
|
Structured logging via the `tracing` crate:
|
||
|
|
|
||
|
|
```rust
|
||
|
|
// Info level
|
||
|
|
tracing::info!("Starting service", port = 8080, host = "0.0.0.0");
|
||
|
|
|
||
|
|
// With structured data
|
||
|
|
tracing::warn!(
|
||
|
|
event = "high_latency",
|
||
|
|
duration_ms = 250,
|
||
|
|
threshold_ms = 100,
|
||
|
|
"Request exceeded latency threshold"
|
||
|
|
);
|
||
|
|
|
||
|
|
// Conditional logging
|
||
|
|
tracing::debug!("Debug information");
|
||
|
|
```
|
||
|
|
|
||
|
|
### Output Formats
|
||
|
|
|
||
|
|
- **JSON**: Structured JSON output for parsing/indexing
|
||
|
|
- **Pretty**: Human-readable colored output for development
|
||
|
|
- **Compact**: Single-line output with essential information
|
||
|
|
|
||
|
|
## Metrics
|
||
|
|
|
||
|
|
Collect and export application metrics:
|
||
|
|
|
||
|
|
```rust
|
||
|
|
use observability::metrics;
|
||
|
|
|
||
|
|
// Counter - monotonically increasing
|
||
|
|
metrics::counter!("http_requests_total").increment();
|
||
|
|
metrics::counter!("http_requests_total").add(10);
|
||
|
|
|
||
|
|
// Gauge - can increase or decrease
|
||
|
|
metrics::gauge!("active_connections").set(42.0);
|
||
|
|
|
||
|
|
// Histogram - measure distribution
|
||
|
|
metrics::histogram!("request_duration_seconds").record(0.250);
|
||
|
|
```
|
||
|
|
|
||
|
|
### Prometheus Metrics
|
||
|
|
|
||
|
|
Default exporter exposes metrics at `http://localhost:9090/metrics` in Prometheus text format:
|
||
|
|
|
||
|
|
```
|
||
|
|
# HELP http_requests_total Total HTTP requests
|
||
|
|
# TYPE http_requests_total counter
|
||
|
|
http_requests_total{method="GET",path="/api/users"} 150.0
|
||
|
|
http_requests_total{method="POST",path="/api/users"} 42.0
|
||
|
|
|
||
|
|
# HELP request_duration_seconds Request duration in seconds
|
||
|
|
# TYPE request_duration_seconds histogram
|
||
|
|
request_duration_seconds_bucket{le="0.005"} 10
|
||
|
|
request_duration_seconds_bucket{le="0.01"} 25
|
||
|
|
request_duration_seconds_bucket{le="0.025"} 50
|
||
|
|
```
|
||
|
|
|
||
|
|
### Optional: OpenTelemetry Push
|
||
|
|
|
||
|
|
Enable feature and configure to push metrics:
|
||
|
|
|
||
|
|
```toml
|
||
|
|
[metrics.otlp]
|
||
|
|
endpoint = "http://localhost:4317"
|
||
|
|
protocol = "grpc"
|
||
|
|
timeout_secs = 10
|
||
|
|
```
|
||
|
|
|
||
|
|
## Health Checks
|
||
|
|
|
||
|
|
Kubernetes-style health check endpoints:
|
||
|
|
|
||
|
|
- **Liveness** (`/healthz`): Indicates if service should be restarted
|
||
|
|
- **Readiness** (`/ready`): Indicates if service can handle traffic
|
||
|
|
- **Startup** (`/startup`): Indicates if initialization completed
|
||
|
|
|
||
|
|
Register custom health checks:
|
||
|
|
|
||
|
|
```rust
|
||
|
|
use observability::health::checks::{HealthCheckRegistry, HealthStatus};
|
||
|
|
|
||
|
|
let registry = HealthCheckRegistry::new();
|
||
|
|
|
||
|
|
// Register database health check
|
||
|
|
registry.register("database", || {
|
||
|
|
if db.is_connected() {
|
||
|
|
HealthStatus::Healthy
|
||
|
|
} else {
|
||
|
|
HealthStatus::Unhealthy
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
// Register cache health check with degradation
|
||
|
|
registry.register("cache", || {
|
||
|
|
if cache.is_responsive() {
|
||
|
|
HealthStatus::Healthy
|
||
|
|
} else {
|
||
|
|
HealthStatus::Degraded
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
// Run all checks
|
||
|
|
let response = registry.run_all();
|
||
|
|
// response.status: Healthy, Degraded, or Unhealthy
|
||
|
|
// response.checks: HashMap of individual results
|
||
|
|
```
|
||
|
|
|
||
|
|
## Dashboards
|
||
|
|
|
||
|
|
### Predefined Dashboards
|
||
|
|
|
||
|
|
Three predefined Grafana dashboards included:
|
||
|
|
|
||
|
|
1. **Overview** (`dashboards/overview.json`)
|
||
|
|
- Request rate, error rate
|
||
|
|
- Response times (p95)
|
||
|
|
- Active connections
|
||
|
|
- Memory and CPU usage
|
||
|
|
|
||
|
|
2. **Backup Operations** (`dashboards/backup.json`)
|
||
|
|
- Backup success rate
|
||
|
|
- Last backup status
|
||
|
|
- Duration trends
|
||
|
|
- Data processed
|
||
|
|
- Retention policy status
|
||
|
|
|
||
|
|
3. **Runtime Performance** (`dashboards/runtime.json`)
|
||
|
|
- Memory allocation
|
||
|
|
- CPU usage
|
||
|
|
- Goroutines count
|
||
|
|
- GC pause duration
|
||
|
|
- File descriptors
|
||
|
|
|
||
|
|
### Programmatic Dashboard Generation
|
||
|
|
|
||
|
|
Generate custom dashboards:
|
||
|
|
|
||
|
|
```rust
|
||
|
|
use observability::dashboards::{
|
||
|
|
DashboardBuilder, Panel, PanelType, GridPos, Target
|
||
|
|
};
|
||
|
|
|
||
|
|
let dashboard = DashboardBuilder::new("Custom Dashboard")
|
||
|
|
.with_description("Custom application monitoring")
|
||
|
|
.with_tag("custom")
|
||
|
|
.with_panel(Panel {
|
||
|
|
id: 1,
|
||
|
|
title: "Custom Metric".to_string(),
|
||
|
|
panel_type: PanelType::Graph,
|
||
|
|
targets: vec![
|
||
|
|
Target {
|
||
|
|
expr: "custom_metric".to_string(),
|
||
|
|
ref_id: "A".to_string(),
|
||
|
|
legend_format: None,
|
||
|
|
}
|
||
|
|
],
|
||
|
|
gridpos: GridPos { h: 8, w: 12, x: 0, y: 0 },
|
||
|
|
description: Some("My custom metric".to_string()),
|
||
|
|
unit: Some("short".to_string()),
|
||
|
|
decimals: Some(2),
|
||
|
|
})
|
||
|
|
.build();
|
||
|
|
|
||
|
|
// Serialize to JSON
|
||
|
|
let json = serde_json::to_string_pretty(&dashboard)?;
|
||
|
|
std::fs::write("custom-dashboard.json", json)?;
|
||
|
|
```
|
||
|
|
|
||
|
|
## Configuration File Examples
|
||
|
|
|
||
|
|
### Development
|
||
|
|
|
||
|
|
```toml
|
||
|
|
[context]
|
||
|
|
service_name = "my-app"
|
||
|
|
service_version = "0.1.0"
|
||
|
|
environment = "development"
|
||
|
|
|
||
|
|
[logging]
|
||
|
|
level = "debug"
|
||
|
|
format = "pretty"
|
||
|
|
include_spans = true
|
||
|
|
|
||
|
|
[metrics]
|
||
|
|
enabled = true
|
||
|
|
export_interval_secs = 30
|
||
|
|
|
||
|
|
[health]
|
||
|
|
enabled = true
|
||
|
|
port = 8080
|
||
|
|
|
||
|
|
[dashboards]
|
||
|
|
auto_generate = true
|
||
|
|
output_dir = "./dashboards"
|
||
|
|
```
|
||
|
|
|
||
|
|
### Production
|
||
|
|
|
||
|
|
```toml
|
||
|
|
[context]
|
||
|
|
service_name = "my-app"
|
||
|
|
service_version = "1.0.0"
|
||
|
|
environment = "production"
|
||
|
|
namespace = "production"
|
||
|
|
|
||
|
|
[logging]
|
||
|
|
level = "info"
|
||
|
|
format = "json"
|
||
|
|
include_spans = false
|
||
|
|
outputs = ["stdout"]
|
||
|
|
|
||
|
|
[metrics]
|
||
|
|
enabled = true
|
||
|
|
export_interval_secs = 60
|
||
|
|
|
||
|
|
[metrics.prometheus]
|
||
|
|
port = 9090
|
||
|
|
path = "/metrics"
|
||
|
|
|
||
|
|
[metrics.otlp]
|
||
|
|
endpoint = "http://otel-collector:4317"
|
||
|
|
protocol = "grpc"
|
||
|
|
timeout_secs = 30
|
||
|
|
|
||
|
|
[tracing]
|
||
|
|
enabled = true
|
||
|
|
sampling_rate = 0.1
|
||
|
|
otlp_endpoint = "http://otel-collector:4317"
|
||
|
|
|
||
|
|
[health]
|
||
|
|
enabled = true
|
||
|
|
port = 8080
|
||
|
|
liveness_path = "/healthz"
|
||
|
|
readiness_path = "/ready"
|
||
|
|
startup_path = "/startup"
|
||
|
|
|
||
|
|
[dashboards]
|
||
|
|
auto_generate = true
|
||
|
|
output_dir = "/etc/grafana/provisioning/dashboards"
|
||
|
|
grafana_url = "http://grafana:3000"
|
||
|
|
```
|
||
|
|
|
||
|
|
## Best Practices
|
||
|
|
|
||
|
|
1. **Initialization**: Initialize observability early in your application startup
|
||
|
|
2. **Logging**: Use structured logging with context-aware fields
|
||
|
|
3. **Metrics**: Use meaningful metric names and labels
|
||
|
|
4. **Health Checks**: Register checks for critical dependencies
|
||
|
|
5. **Dashboards**: Customize dashboards for your specific metrics
|
||
|
|
6. **Production**: Use JSON logging format in production for parsing/indexing
|
||
|
|
|
||
|
|
## Integration with Other Crates
|
||
|
|
|
||
|
|
### With Backup Module
|
||
|
|
|
||
|
|
Monitor backup operations via observability:
|
||
|
|
|
||
|
|
```rust
|
||
|
|
use observability::metrics;
|
||
|
|
use backup::BackupClient;
|
||
|
|
|
||
|
|
let client = BackupClient::new(config)?;
|
||
|
|
let result = client.backup()?;
|
||
|
|
|
||
|
|
if result.success {
|
||
|
|
metrics::counter!("backup_success_total").increment();
|
||
|
|
metrics::gauge!("backup_data_bytes").set(result.data_size as f64);
|
||
|
|
} else {
|
||
|
|
metrics::counter!("backup_failure_total").increment();
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### With Init-Servs Module
|
||
|
|
|
||
|
|
Monitor service management:
|
||
|
|
|
||
|
|
```rust
|
||
|
|
use observability::metrics;
|
||
|
|
use init_servs::ServiceManager;
|
||
|
|
|
||
|
|
let manager = ServiceManager::new()?;
|
||
|
|
manager.start_service("my-service")?;
|
||
|
|
|
||
|
|
metrics::counter!("service_start_total").increment();
|
||
|
|
```
|
||
|
|
|
||
|
|
## Environment Variables
|
||
|
|
|
||
|
|
- `RUST_LOG`: Control logging level (e.g., `RUST_LOG=debug`)
|
||
|
|
- `OBS_CONFIG`: Path to observability configuration file
|
||
|
|
- `OBS_SERVICE_NAME`: Override service name
|
||
|
|
- `OBS_ENVIRONMENT`: Override environment (dev/staging/prod)
|
||
|
|
|
||
|
|
## Performance Considerations
|
||
|
|
|
||
|
|
- **Logging**: Minimal overhead with async I/O
|
||
|
|
- **Metrics**: Pre-aggregated, no cardinality explosion
|
||
|
|
- **Health Checks**: Lightweight, typically < 1ms per check
|
||
|
|
- **Dashboards**: Pure data structures, no runtime overhead
|
||
|
|
|
||
|
|
## License
|
||
|
|
|
||
|
|
MIT
|