Skip to content

Commit 05cc637

Browse files
feat(Mountain/Telemetry): Implement comprehensive observability and feature flag subsystem
This commit establishes the foundational telemetry infrastructure for the `Mountain` backend, enabling advanced observability and dynamic feature management. It creates a modular `Source/Telemetry` package that supports the previously integrated OpenTelemetry instrumentation in the `Vine` gRPC layer. **New Modules:** - **`Tracing`**: Implements `instrument_rpc` and `instrument_command` helpers that leverage `tracing-opentelemetry` to automatically create spans for `Vine` protocol operations, track execution latency, and log errors. Includes `measure_time` macro for performance profiling. - **`Metrics`**: Provides a thread-safe, in-memory `MetricsRegistry` (using `parking_lot::RwLock`) to record performance counters, gauges, and histograms. Includes a `Timer` helper for measuring operation durations. - **`Gates`**: Defines build-time and runtime feature gates that bridge Cargo feature flags (e.g., `Telemetry`, `Debug`) with runtime logic, allowing conditional execution of observability code. - **`FeatureFlags`**: Implements a dynamic `FeatureFlagRegistry` (via `lazy_static`) for runtime toggling of capabilities like `ipc-compression` or `experimental-webgl` without recompilation. This system allows `Mountain` to monitor RPC health, optimize performance, and roll out features safely across the `Cocoon` extension host and `Wind` frontend environments.
1 parent 29b2d28 commit 05cc637

5 files changed

Lines changed: 1008 additions & 0 deletions

File tree

Lines changed: 280 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,280 @@
1+
//! # Runtime Feature Flags
2+
//!
3+
//! This module provides runtime feature flag management for the Mountain application.
4+
//! Feature flags allow enabling or disabling features without recompiling the application.
5+
//!
6+
//! ## Feature Flag Categories
7+
//!
8+
//! - **Experimental Features**: New features under development
9+
//! - **Legacy Features**: Old features that can be disabled
10+
//! - **Performance Features**: Features that impact performance
11+
//! - **User-facing Features**: Features visible to end users
12+
//!
13+
//! ## Usage Example
14+
//!
15+
//! ```rust
16+
//! use Mountain::Telemetry::FeatureFlags;
17+
//!
18+
//! // Check if a feature is enabled
19+
//! if FeatureFlags::is_enabled("experimental-ui") {
20+
//! show_experimental_ui();
21+
//! }
22+
//!
23+
//! // Enable a feature
24+
//! FeatureFlags::enable_feature("custom-editor", "User preference");
25+
//! ```
26+
27+
use std::sync::Arc;
28+
use parking_lot::RwLock;
29+
use std::collections::HashMap;
30+
31+
// ============================================================================
32+
// Feature Flag Data Structures
33+
// ============================================================================
34+
35+
/// A feature flag with metadata
36+
#[derive(Debug, Clone)]
37+
pub struct FeatureFlag {
38+
pub name: String,
39+
pub enabled: bool,
40+
pub description: String,
41+
pub category: FlagCategory,
42+
pub reason: String,
43+
}
44+
45+
/// Categories of feature flags
46+
#[derive(Debug, Clone, PartialEq)]
47+
pub enum FlagCategory {
48+
/// Experimental features (may change or be removed)
49+
Experimental,
50+
/// Legacy features (can be disabled)
51+
Legacy,
52+
/// Performance-sensitive features
53+
Performance,
54+
/// User-facing features
55+
UserFacing,
56+
/// Internal/developer features
57+
Internal,
58+
}
59+
60+
// ============================================================================
61+
// Feature Flag Registry
62+
// ============================================================================
63+
64+
/// Central registry for feature flags
65+
#[derive(Debug)]
66+
pub struct FeatureFlagRegistry {
67+
flags: Arc<RwLock<HashMap<String, FeatureFlag>>>,
68+
}
69+
70+
impl FeatureFlagRegistry {
71+
/// Create a new registry with default feature flags
72+
pub fn new() -> Self {
73+
let mut flags = HashMap::new();
74+
75+
// Add default feature flags
76+
flags.insert(
77+
"ipc-compression".to_string(),
78+
FeatureFlag {
79+
name: "ipc-compression".to_string(),
80+
enabled: true,
81+
description: "Enable IPC message compression".to_string(),
82+
category: FlagCategory::Performance,
83+
reason: "Default: improves IPC performance".to_string(),
84+
}
85+
);
86+
87+
flags.insert(
88+
"experimental-webgl".to_string(),
89+
FeatureFlag {
90+
name: "experimental-webgl".to_string(),
91+
enabled: false,
92+
description: "Experimental WebGL rendering".to_string(),
93+
category: FlagCategory::Experimental,
94+
reason: "Not ready for production".to_string(),
95+
}
96+
);
97+
98+
flags.insert(
99+
"extension-hot-reload".to_string(),
100+
FeatureFlag {
101+
name: "extension-hot-reload".to_string(),
102+
enabled: false,
103+
description: "Enable extension hot-reload".to_string(),
104+
category: FlagCategory::Performance,
105+
reason: "Performance impact".to_string(),
106+
}
107+
);
108+
109+
flags.insert(
110+
"debug-diagnostics".to_string(),
111+
FeatureFlag {
112+
name: "debug-diagnostics".to_string(),
113+
enabled: false,
114+
description: "Enable detailed debug diagnostics".to_string(),
115+
category: FlagCategory::Internal,
116+
reason: "Development only".to_string(),
117+
}
118+
);
119+
120+
Self {
121+
flags: Arc::new(RwLock::new(flags)),
122+
}
123+
}
124+
125+
/// Check if a feature is enabled
126+
pub fn is_enabled(&self, flag_name: &str) -> bool {
127+
self.flags.read()
128+
.get(flag_name)
129+
.map(|f| f.enabled)
130+
.unwrap_or(false)
131+
}
132+
133+
/// Enable a feature flag
134+
pub fn enable(&self, flag_name: &str, reason: &str) -> Result<(), FeatureFlagError> {
135+
let mut flags = self.flags.write();
136+
137+
if let Some(flag) = flags.get_mut(flag_name) {
138+
flag.enabled = true;
139+
flag.reason = reason.to_string();
140+
Ok(())
141+
} else {
142+
Err(FeatureFlagError::NotFound(flag_name.to_string()))
143+
}
144+
}
145+
146+
/// Disable a feature flag
147+
pub fn disable(&self, flag_name: &str, reason: &str) -> Result<(), FeatureFlagError> {
148+
let mut flags = self.flags.write();
149+
150+
if let Some(flag) = flags.get_mut(flag_name) {
151+
flag.enabled = false;
152+
flag.reason = reason.to_string();
153+
Ok(())
154+
} else {
155+
Err(FeatureFlagError::NotFound(flag_name.to_string()))
156+
}
157+
}
158+
159+
/// Add a new feature flag
160+
pub fn add_flag(&self, flag: FeatureFlag) {
161+
let mut flags = self.flags.write();
162+
flags.insert(flag.name.clone(), flag);
163+
}
164+
165+
/// Get all feature flags
166+
pub fn get_all_flags(&self) -> Vec<FeatureFlag> {
167+
self.flags.read()
168+
.values()
169+
.cloned()
170+
.collect()
171+
}
172+
173+
/// Get flags by category
174+
pub fn get_flags_by_category(&self, category: FlagCategory) -> Vec<FeatureFlag> {
175+
self.flags.read()
176+
.values()
177+
.filter(|f| f.category == category)
178+
.cloned()
179+
.collect()
180+
}
181+
}
182+
183+
/// Error types for feature flag operations
184+
#[derive(Debug, thiserror::Error)]
185+
pub enum FeatureFlagError {
186+
#[error("Feature flag not found: {0}")]
187+
NotFound(String),
188+
#[error("Feature flag already exists: {0}")]
189+
AlreadyExists(String),
190+
#[error("Feature flag error: {0}")]
191+
Other(String),
192+
}
193+
194+
/// Global feature flag registry instance
195+
lazy_static::lazy_static! {
196+
static ref GLOBAL_REGISTRY: Arc<FeatureFlagRegistry> =
197+
Arc::new(FeatureFlagRegistry::new());
198+
}
199+
200+
// ============================================================================
201+
// Convenience Functions
202+
// ============================================================================
203+
204+
/// Check if a feature flag is enabled
205+
pub fn is_enabled(flag_name: &str) -> bool {
206+
GLOBAL_REGISTRY.is_enabled(flag_name)
207+
}
208+
209+
/// Enable a feature flag
210+
pub fn enable(flag_name: &str, reason: &str) -> Result<(), FeatureFlagError> {
211+
GLOBAL_REGISTRY.enable(flag_name, reason)
212+
}
213+
214+
/// Disable a feature flag
215+
pub fn disable(flag_name: &str, reason: &str) -> Result<(), FeatureFlagError> {
216+
GLOBAL_REGISTRY.disable(flag_name, reason)
217+
}
218+
219+
/// Get all feature flags
220+
pub fn get_all_flags() -> Vec<FeatureFlag> {
221+
GLOBAL_REGISTRY.get_all_flags()
222+
}
223+
224+
// ============================================================================
225+
// Initialization
226+
// ============================================================================
227+
228+
/// Initialize feature flags from configuration
229+
pub fn initialize_feature_flags() -> Result<(), FeatureFlagError> {
230+
log::debug!("Feature flags system initialized");
231+
Ok(())
232+
}
233+
234+
#[cfg(test)]
235+
mod tests {
236+
use super::*;
237+
238+
#[test]
239+
fn test_registry_creation() {
240+
let registry = FeatureFlagRegistry::new();
241+
assert!(registry.is_enabled("ipc-compression"));
242+
}
243+
244+
#[test]
245+
fn test_enable_disable() {
246+
let registry = FeatureFlagRegistry::new();
247+
248+
registry.enable("ipc-compression", "Test").unwrap();
249+
assert!(registry.is_enabled("ipc-compression"));
250+
251+
registry.disable("ipc-compression", "Test").unwrap();
252+
assert!(!registry.is_enabled("ipc-compression"));
253+
}
254+
255+
#[test]
256+
fn test_not_found_error() {
257+
let registry = FeatureFlagRegistry::new();
258+
let result = registry.enable("nonexistent", "Test");
259+
assert!(result.is_err());
260+
}
261+
262+
#[test]
263+
fn test_add_flag() {
264+
let registry = FeatureFlagRegistry::new();
265+
266+
let flag = FeatureFlag {
267+
name: "test-flag".to_string(),
268+
enabled: false,
269+
description: "Test flag".to_string(),
270+
category: FlagCategory::Experimental,
271+
reason: "Testing".to_string(),
272+
};
273+
274+
registry.add_flag(flag);
275+
assert!(!registry.is_enabled("test-flag"));
276+
277+
registry.enable("test-flag", "Test").unwrap();
278+
assert!(registry.is_enabled("test-flag"));
279+
}
280+
}

Source/Telemetry/Gates/mod.rs

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
//! # Telemetry Gates
2+
//!
3+
//! This module provides compile-time and runtime feature gates for controlling
4+
//! telemetry visibility and behavior across different build configurations.
5+
6+
use std::sync::OnceLock;
7+
use std::collections::HashSet;
8+
9+
/// Static set of runtime-enabled feature gates
10+
static RUNTIME_GATES: OnceLock<HashSet<String>> = OnceLock::new();
11+
12+
/// Check if Debug build features are enabled (compile-time)
13+
#[inline]
14+
pub const fn is_debug_build() -> bool {
15+
cfg!(debug_assertions)
16+
}
17+
18+
/// Check if Development build features are enabled (compile-time)
19+
#[inline]
20+
pub const fn is_development_build() -> bool {
21+
cfg!(feature = "Development") || cfg!(debug_assertions)
22+
}
23+
24+
/// Check if Telemetry is enabled (compile-time)
25+
#[inline]
26+
pub const fn is_telemetry_enabled() -> bool {
27+
cfg!(feature = "Telemetry")
28+
}
29+
30+
/// Check if metrics collection is enabled (compile-time)
31+
#[inline]
32+
pub const fn is_metrics_enabled() -> bool {
33+
cfg!(feature = "MetricsCollection")
34+
}
35+
36+
/// Check if distributed tracing is enabled (compile-time)
37+
#[inline]
38+
pub const fn is_distributed_tracing_enabled() -> bool {
39+
cfg!(feature = "DistributedTracing")
40+
}
41+
42+
/// Check if feature flags are enabled at compile-time
43+
#[inline]
44+
pub const fn is_feature_flags_enabled() -> bool {
45+
cfg!(feature = "RuntimeFeatureFlags")
46+
}
47+
48+
/// Get the runtime gates set
49+
pub fn get_runtime_gates() -> &'static HashSet<String> {
50+
RUNTIME_GATES.get_or_init(|| {
51+
let mut gates = HashSet::new();
52+
53+
#[cfg(feature = "Debug")]
54+
{
55+
gates.insert("verbose-logging".to_string());
56+
gates.insert("performance-profiling".to_string());
57+
gates.insert("detailed-error-messages".to_string());
58+
gates.insert("experimental-features".to_string());
59+
}
60+
61+
#[cfg(feature = "Development")]
62+
{
63+
gates.insert("development-tools".to_string());
64+
gates.insert("workspace-diagnostics".to_string());
65+
gates.insert("extension-hot-reload".to_string());
66+
}
67+
68+
#[cfg(feature = "Telemetry")]
69+
{
70+
gates.insert("tracing".to_string());
71+
gates.insert("metrics".to_string());
72+
gates.insert("performance-monitoring".to_string());
73+
}
74+
75+
gates
76+
})
77+
}
78+
79+
/// Check if a runtime gate is enabled
80+
pub fn runtime_gate_enabled(gate_name: &str) -> bool {
81+
get_runtime_gates().contains(gate_name)
82+
}
83+
84+
/// Enable a runtime gate
85+
pub fn enable_runtime_gate(gate_name: String) -> Result<(), String> {
86+
RUNTIME_GATES.get_or_init(|| HashSet::new())
87+
.insert(gate_name);
88+
Ok(())
89+
}
90+
91+
/// List all enabled runtime gates
92+
pub fn list_enabled_gates() -> Vec<String> {
93+
get_runtime_gates().iter().cloned().collect()
94+
}
95+
96+
/// Validate required gates for a feature
97+
pub fn validate_required_gates(
98+
feature_name: &str,
99+
required_gates: &[&str]
100+
) -> Result<(), String> {
101+
let enabled = get_runtime_gates();
102+
103+
let missing: Vec<_> = required_gates
104+
.iter()
105+
.filter(|gate| !enabled.contains(*gate))
106+
.collect();
107+
108+
if !missing.is_empty() {
109+
Err(format!(
110+
"Feature '{}' requires gates: {:?}",
111+
feature_name,
112+
missing
113+
))
114+
} else {
115+
Ok(())
116+
}
117+
}

0 commit comments

Comments
 (0)