pub mod analyzer_plugin;
pub mod cancellation_analyzer;
pub mod cancellation_debt_monitor;
pub mod cancellation_tracer;
pub mod cancellation_visualizer;
pub mod collector;
pub mod context;
pub mod debt_runtime_integration;
pub mod diagnostics;
pub mod entry;
#[cfg(feature = "metrics")]
pub mod histogram_conformance;
pub mod level;
pub mod metrics;
pub mod obligation_tracker;
#[cfg(feature = "metrics")]
pub mod otel;
pub mod otel_structured_concurrency;
pub mod performance_budget_monitor;
pub mod resource_accounting;
pub mod runtime_integration;
pub mod spectral_health;
pub mod structured_cancellation_analyzer;
pub mod task_inspector;
pub use analyzer_plugin::{
ANALYZER_PLUGIN_CONTRACT_VERSION, AggregatedAnalyzerFinding, AnalyzerCapability,
AnalyzerFinding, AnalyzerOutput, AnalyzerPlugin, AnalyzerPluginDescriptor,
AnalyzerPluginPackReport, AnalyzerPluginRegistry, AnalyzerPluginRunError, AnalyzerRequest,
AnalyzerSandboxPolicy, AnalyzerSchemaVersion, AnalyzerSeverity, PluginExecutionRecord,
PluginExecutionState, PluginLifecycleEvent, PluginLifecyclePhase, PluginRegistrationError,
SchemaDecision, SchemaNegotiation, negotiate_schema_version, run_analyzer_plugin_pack_smoke,
};
pub use cancellation_analyzer::{
AnalyzerConfig as CancellationAnalyzerConfig, BottleneckAnalysis, CancellationAnalyzer,
CleanupEfficiency, CleanupTimingAnalysis, DistributionStats, EntityPerformance,
ImplementationComplexity, OptimizationRecommendation, PerformanceAnalysis,
PerformanceRegression, RecommendationPriority, ThroughputMetrics, TrendAnalysis,
TrendDirection,
};
pub use cancellation_debt_monitor::{
CancellationDebtConfig, CancellationDebtMonitor, DebtAlert, DebtAlertLevel, DebtSnapshot,
PendingWork, WorkType,
};
pub use cancellation_tracer::{
CancellationAnalysis, CancellationTrace, CancellationTraceStep, CancellationTracer,
CancellationTracerConfig, CancellationTracerStats, CancellationTracerStatsSnapshot, EntityType,
PropagationAnomaly, TraceId, analyze_cancellation_patterns,
};
pub use cancellation_visualizer::{
AnomalyInfo, AnomalySeverity, BottleneckInfo, CancellationDashboard, CancellationTreeNode,
CancellationVisualizer, ThroughputStats, TimingFormat, VisualizerConfig,
};
pub use collector::LogCollector;
pub use context::{DiagnosticContext, Span, SpanId};
pub use debt_runtime_integration::{DebtHealthReport, DebtRuntimeIntegration};
pub use diagnostics::{
BlockReason, CancelReasonInfo, CancellationExplanation, CancellationStep, DeadlockCycle,
DeadlockSeverity, Diagnostics, DirectionalDeadlockReport, ObligationLeak, Reason,
RegionOpenExplanation, TAIL_LATENCY_TAXONOMY_CONTRACT_VERSION, TailLatencyLogFieldSpec,
TailLatencySignalSpec, TailLatencyTaxonomyContract, TailLatencyTermSpec,
TaskBlockedExplanation, tail_latency_taxonomy_contract,
};
pub use entry::LogEntry;
pub use level::LogLevel;
pub use metrics::{
Counter, Gauge, Histogram, MetricValue, Metrics, MetricsProvider, NoOpMetrics, OutcomeKind,
};
pub use obligation_tracker::{
ObligationInfo, ObligationStateInfo, ObligationSummary, ObligationTracker,
ObligationTrackerConfig, TypeSummary,
};
#[cfg(feature = "metrics")]
pub use otel::{
CardinalityOverflow, ExportError, InMemoryExporter, MetricsConfig, MetricsExporter,
MetricsSnapshot, MultiExporter, NullExporter, OtelMetrics, SamplingConfig, StdoutExporter,
};
pub use otel_structured_concurrency::{
EntityId, OtelStructuredConcurrencyConfig, SpanStorage, SpanType,
};
pub use performance_budget_monitor::{
BudgetAlert, BudgetDirection, BudgetEvaluation, BudgetSample, BudgetSeverity,
PerformanceBudget, PerformanceBudgetMonitor, PerformanceBudgetSnapshot,
};
pub use resource_accounting::{
AdmissionKindStats, ObligationKindStats, ResourceAccounting, ResourceAccountingSnapshot,
};
pub use structured_cancellation_analyzer::{
AlertSeverity, AlertType, CancellationAlert, LabRuntimeIntegration, RealTimeStats,
StructuredCancellationAnalyzer, StructuredCancellationConfig,
};
pub use task_inspector::{
TASK_CONSOLE_WIRE_SCHEMA_V1, TaskConsoleWireSnapshot, TaskDetails, TaskDetailsWire,
TaskInspector, TaskInspectorConfig, TaskRegionCountWire, TaskStateInfo, TaskSummary,
TaskSummaryWire,
};
#[allow(clippy::cast_precision_loss)]
fn sample_unit_interval(key: u64) -> f64 {
const TWO_POW_53_F64: f64 = 9_007_199_254_740_992.0;
let bits = splitmix64(key) >> 11;
bits as f64 / TWO_POW_53_F64
}
fn splitmix64(mut state: u64) -> u64 {
state = state.wrapping_add(0x9e37_79b9_7f4a_7c15);
let mut z = state;
z = (z ^ (z >> 30)).wrapping_mul(0xbf58_476d_1ce4_e5b9);
z = (z ^ (z >> 27)).wrapping_mul(0x94d0_49bb_1331_11eb);
z ^ (z >> 31)
}
#[derive(Debug, Clone)]
pub struct ObservabilityConfig {
log_level: LogLevel,
trace_all_symbols: bool,
sample_rate: f64,
max_spans: usize,
max_log_entries: usize,
include_timestamps: bool,
metrics_enabled: bool,
}
impl ObservabilityConfig {
#[must_use]
pub fn new() -> Self {
Self {
log_level: LogLevel::Info,
trace_all_symbols: false,
sample_rate: 1.0,
max_spans: 1000,
max_log_entries: 10000,
include_timestamps: true,
metrics_enabled: true,
}
}
#[must_use]
pub fn with_log_level(mut self, level: LogLevel) -> Self {
self.log_level = level;
self
}
#[must_use]
pub fn with_trace_all_symbols(mut self, trace: bool) -> Self {
self.trace_all_symbols = trace;
self
}
#[must_use]
pub fn with_sample_rate(mut self, rate: f64) -> Self {
assert!(
(0.0..=1.0).contains(&rate),
"sample_rate must be between 0.0 and 1.0"
);
self.sample_rate = rate;
self
}
#[must_use]
pub fn with_max_spans(mut self, max: usize) -> Self {
self.max_spans = max;
self
}
#[must_use]
pub fn with_max_log_entries(mut self, max: usize) -> Self {
self.max_log_entries = max;
self
}
#[must_use]
pub fn with_include_timestamps(mut self, include: bool) -> Self {
self.include_timestamps = include;
self
}
#[must_use]
pub fn with_metrics_enabled(mut self, enabled: bool) -> Self {
self.metrics_enabled = enabled;
self
}
#[must_use]
pub const fn log_level(&self) -> LogLevel {
self.log_level
}
#[must_use]
pub const fn trace_all_symbols(&self) -> bool {
self.trace_all_symbols
}
#[must_use]
pub const fn sample_rate(&self) -> f64 {
self.sample_rate
}
#[must_use]
pub const fn max_spans(&self) -> usize {
self.max_spans
}
#[must_use]
pub const fn max_log_entries(&self) -> usize {
self.max_log_entries
}
#[must_use]
pub const fn include_timestamps(&self) -> bool {
self.include_timestamps
}
#[must_use]
pub const fn metrics_enabled(&self) -> bool {
self.metrics_enabled
}
#[must_use]
pub fn create_collector(&self) -> LogCollector {
LogCollector::new(self.max_log_entries).with_min_level(self.log_level)
}
#[must_use]
pub fn create_diagnostic_context(&self) -> DiagnosticContext {
DiagnosticContext::new().with_max_completed(self.max_spans)
}
#[must_use]
pub fn create_metrics(&self) -> Option<Metrics> {
if self.metrics_enabled {
Some(Metrics::new())
} else {
None
}
}
#[must_use]
pub fn should_sample(&self, key: u64) -> bool {
if self.sample_rate >= 1.0 {
return true;
}
if self.sample_rate <= 0.0 {
return false;
}
sample_unit_interval(key) < self.sample_rate
}
#[must_use]
pub fn development() -> Self {
Self::new()
.with_log_level(LogLevel::Debug)
.with_trace_all_symbols(true)
.with_sample_rate(1.0)
}
#[must_use]
pub fn production() -> Self {
Self::new()
.with_log_level(LogLevel::Warn)
.with_trace_all_symbols(false)
.with_sample_rate(0.01)
}
#[must_use]
pub fn testing() -> Self {
Self::new()
.with_log_level(LogLevel::Trace)
.with_trace_all_symbols(true)
.with_sample_rate(1.0)
.with_max_spans(10_000)
.with_max_log_entries(100_000)
}
}
impl Default for ObservabilityConfig {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn config_default() {
let config = ObservabilityConfig::default();
assert_eq!(config.log_level(), LogLevel::Info);
assert!(!config.trace_all_symbols());
assert!((config.sample_rate() - 1.0).abs() < f64::EPSILON);
assert!(config.metrics_enabled());
}
#[test]
fn config_builder() {
let config = ObservabilityConfig::new()
.with_log_level(LogLevel::Debug)
.with_trace_all_symbols(true)
.with_sample_rate(0.5)
.with_max_spans(500)
.with_max_log_entries(5000)
.with_include_timestamps(false)
.with_metrics_enabled(false);
assert_eq!(config.log_level(), LogLevel::Debug);
assert!(config.trace_all_symbols());
assert!((config.sample_rate() - 0.5).abs() < f64::EPSILON);
assert_eq!(config.max_spans(), 500);
assert_eq!(config.max_log_entries(), 5000);
assert!(!config.include_timestamps());
assert!(!config.metrics_enabled());
}
#[test]
fn config_presets() {
let dev = ObservabilityConfig::development();
assert_eq!(dev.log_level(), LogLevel::Debug);
assert!(dev.trace_all_symbols());
let prod = ObservabilityConfig::production();
assert_eq!(prod.log_level(), LogLevel::Warn);
assert!(!prod.trace_all_symbols());
assert!(prod.sample_rate() < 0.1);
let test = ObservabilityConfig::testing();
assert_eq!(test.log_level(), LogLevel::Trace);
assert!(test.trace_all_symbols());
}
#[test]
fn config_create_collector() {
let config = ObservabilityConfig::new()
.with_log_level(LogLevel::Warn)
.with_max_log_entries(100);
let collector = config.create_collector();
assert_eq!(collector.min_level(), LogLevel::Warn);
assert_eq!(collector.capacity(), 100);
}
#[test]
fn config_create_metrics() {
let enabled = ObservabilityConfig::new().with_metrics_enabled(true);
assert!(enabled.create_metrics().is_some());
let disabled = ObservabilityConfig::new().with_metrics_enabled(false);
assert!(disabled.create_metrics().is_none());
}
#[test]
fn config_sampling() {
let full = ObservabilityConfig::new().with_sample_rate(1.0);
assert!(full.should_sample(0));
assert!(full.should_sample(u64::MAX));
let none = ObservabilityConfig::new().with_sample_rate(0.0);
assert!(!none.should_sample(0));
assert!(!none.should_sample(u64::MAX));
let half = ObservabilityConfig::new().with_sample_rate(0.5);
let result1 = half.should_sample(12345);
let result2 = half.should_sample(12345);
assert_eq!(result1, result2);
}
#[test]
fn config_sampling_hashes_low_sequential_keys() {
let half = ObservabilityConfig::new().with_sample_rate(0.5);
let sampled = (0..64).filter(|&key| half.should_sample(key)).count();
assert!(
sampled > 0,
"hashed sampling should accept some low sequential keys"
);
assert!(
sampled < 64,
"hashed sampling should not deterministically accept every low sequential key"
);
}
#[test]
#[should_panic(expected = "sample_rate must be between 0.0 and 1.0")]
fn config_invalid_sample_rate_high() {
let _ = ObservabilityConfig::new().with_sample_rate(1.5);
}
#[test]
#[should_panic(expected = "sample_rate must be between 0.0 and 1.0")]
fn config_invalid_sample_rate_negative() {
let _ = ObservabilityConfig::new().with_sample_rate(-0.1);
}
}