1use crate::error::ObservabilityResult;
4#[cfg(feature = "structured-logging")]
5use crate::traits::StructuredLogger;
6use crate::traits::{LogLevel, MetricsCollector, ObservabilityPlugin, SpanGuard, SpanStatus};
7use std::collections::HashMap;
8use std::sync::Arc;
9use web_time::Duration;
10
11#[cfg(feature = "structured-logging")]
12use serde_json::Value as JsonValue;
13
14pub struct NoOpObservabilityPlugin;
19
20impl NoOpObservabilityPlugin {
21 #[inline]
23 pub fn new() -> Self {
24 Self
25 }
26
27 #[inline]
29 pub fn shared() -> Arc<Self> {
30 Arc::new(Self)
31 }
32}
33
34impl Default for NoOpObservabilityPlugin {
35 #[inline]
36 fn default() -> Self {
37 Self::new()
38 }
39}
40
41impl ObservabilityPlugin for NoOpObservabilityPlugin {
42 #[inline]
43 fn start_span(&self, _name: &str, _attributes: &[(&str, &str)]) -> SpanGuard {
44 SpanGuard::no_op()
45 }
46
47 #[inline]
48 fn end_span(&self, _span_id: &str) {
49 }
51
52 #[inline]
53 fn add_span_attribute(&self, _span_id: &str, _key: &str, _value: &str) {
54 }
56
57 #[inline]
58 fn set_span_status(&self, _span_id: &str, _status: SpanStatus) {
59 }
61
62 #[inline]
63 fn record_metric(&self, _name: &str, _value: f64, _labels: &[(&str, &str)]) {
64 }
66
67 #[cfg(feature = "structured-logging")]
68 #[inline]
69 fn log_structured(&self, _level: LogLevel, _message: &str, _fields: &JsonValue) {
70 }
72
73 #[inline]
74 fn write_log(&self, _message: &str) {
75 #[cfg(debug_assertions)]
77 {
78 }
81 }
82
83 #[inline]
84 fn flush(&self) -> ObservabilityResult<()> {
85 Ok(())
86 }
87
88 #[inline]
89 fn is_enabled(&self) -> bool {
90 false
91 }
92
93 #[inline]
94 fn plugin_type(&self) -> &'static str {
95 "noop"
96 }
97}
98
99pub struct NoOpMetricsCollector;
101
102impl NoOpMetricsCollector {
103 #[inline]
104 pub fn new() -> Self {
105 Self
106 }
107}
108
109impl Default for NoOpMetricsCollector {
110 #[inline]
111 fn default() -> Self {
112 Self::new()
113 }
114}
115
116impl MetricsCollector for NoOpMetricsCollector {
117 #[inline]
118 fn register_counter(
119 &mut self,
120 _name: &str,
121 _description: &str,
122 _labels: &[&str],
123 ) -> ObservabilityResult<()> {
124 Ok(())
125 }
126
127 #[inline]
128 fn register_histogram(
129 &mut self,
130 _name: &str,
131 _description: &str,
132 _buckets: &[f64],
133 _labels: &[&str],
134 ) -> ObservabilityResult<()> {
135 Ok(())
136 }
137
138 #[inline]
139 fn register_gauge(
140 &mut self,
141 _name: &str,
142 _description: &str,
143 _labels: &[&str],
144 ) -> ObservabilityResult<()> {
145 Ok(())
146 }
147
148 #[inline]
149 fn record_counter(
150 &self,
151 _name: &str,
152 _value: f64,
153 _labels: &HashMap<String, String>,
154 ) -> ObservabilityResult<()> {
155 Ok(())
156 }
157
158 #[inline]
159 fn record_histogram(
160 &self,
161 _name: &str,
162 _value: f64,
163 _labels: &HashMap<String, String>,
164 ) -> ObservabilityResult<()> {
165 Ok(())
166 }
167
168 #[inline]
169 fn set_gauge(
170 &self,
171 _name: &str,
172 _value: f64,
173 _labels: &HashMap<String, String>,
174 ) -> ObservabilityResult<()> {
175 Ok(())
176 }
177
178 #[inline]
179 fn get_metrics(&self) -> HashMap<String, f64> {
180 HashMap::new()
181 }
182
183 #[inline]
184 fn clear(&mut self) {
185 }
187}
188
189#[cfg(feature = "structured-logging")]
191pub struct NoOpStructuredLogger {
192 level: LogLevel,
193}
194
195#[cfg(feature = "structured-logging")]
196impl NoOpStructuredLogger {
197 #[inline]
198 pub fn new() -> Self {
199 Self {
200 level: LogLevel::Info,
201 }
202 }
203
204 #[inline]
205 pub fn with_level(level: LogLevel) -> Self {
206 Self { level }
207 }
208}
209
210#[cfg(feature = "structured-logging")]
211impl Default for NoOpStructuredLogger {
212 #[inline]
213 fn default() -> Self {
214 Self::new()
215 }
216}
217
218#[cfg(feature = "structured-logging")]
219impl StructuredLogger for NoOpStructuredLogger {
220 #[inline]
221 fn log_with_trace(
222 &self,
223 _level: LogLevel,
224 _message: &str,
225 _fields: &JsonValue,
226 _trace_id: Option<&str>,
227 _span_id: Option<&str>,
228 ) {
229 }
231
232 #[inline]
233 fn log_performance(
234 &self,
235 _operation: &str,
236 _duration: Duration,
237 _success: bool,
238 _additional_fields: &JsonValue,
239 ) {
240 }
242
243 #[inline]
244 fn log_error(&self, _error: &dyn std::error::Error, _context: &JsonValue) {
245 }
247
248 #[inline]
249 fn set_level(&mut self, level: LogLevel) {
250 self.level = level;
251 }
252
253 #[inline]
254 fn is_level_enabled(&self, level: LogLevel) -> bool {
255 level <= self.level
256 }
257}
258
259#[inline]
261pub fn create_noop_plugin() -> Box<dyn ObservabilityPlugin> {
262 Box::new(NoOpObservabilityPlugin::new())
263}
264
265#[inline]
267pub fn create_shared_noop_plugin() -> Arc<dyn ObservabilityPlugin> {
268 Arc::new(NoOpObservabilityPlugin::new())
269}
270
271#[macro_export]
273macro_rules! observability_plugin {
274 ($plugin_expr:expr_2021) => {
275 #[cfg(feature = "observability")]
276 {
277 $plugin_expr
278 }
279 #[cfg(not(feature = "observability"))]
280 {
281 $crate::noop::create_noop_plugin()
282 }
283 };
284}
285
286#[macro_export]
288macro_rules! shared_observability_plugin {
289 ($plugin_expr:expr_2021) => {
290 #[cfg(feature = "observability")]
291 {
292 $plugin_expr
293 }
294 #[cfg(not(feature = "observability"))]
295 {
296 $crate::noop::create_shared_noop_plugin()
297 }
298 };
299}
300
301#[macro_export]
303macro_rules! observability_span {
304 ($plugin:expr_2021, $name:expr_2021, $($attr_key:expr_2021 => $attr_val:expr_2021),*) => {
305 {
306 #[cfg(feature = "observability")]
307 {
308 $plugin.start_span($name, &[$(($attr_key, $attr_val)),*])
309 }
310 #[cfg(not(feature = "observability"))]
311 {
312 $crate::SpanGuard::no_op()
313 }
314 }
315 };
316 ($plugin:expr_2021, $name:expr_2021) => {
317 {
318 #[cfg(feature = "observability")]
319 {
320 $plugin.start_span($name, &[])
321 }
322 #[cfg(not(feature = "observability"))]
323 {
324 $crate::SpanGuard::no_op()
325 }
326 }
327 };
328}
329
330#[macro_export]
332macro_rules! observability_metric {
333 ($plugin:expr_2021, $name:expr_2021, $value:expr_2021, $($label_key:expr_2021 => $label_val:expr_2021),*) => {
334 #[cfg(feature = "observability")]
335 {
336 $plugin.record_metric($name, $value, &[$(($label_key, $label_val)),*]);
337 }
338 #[cfg(not(feature = "observability"))]
339 {
340 }
342 };
343 ($plugin:expr_2021, $name:expr_2021, $value:expr_2021) => {
344 #[cfg(feature = "observability")]
345 {
346 $plugin.record_metric($name, $value, &[]);
347 }
348 #[cfg(not(feature = "observability"))]
349 {
350 }
352 };
353}
354
355#[macro_export]
357macro_rules! observability_log {
358 ($plugin:expr_2021, $level:expr_2021, $message:expr_2021) => {
359 #[cfg(feature = "observability")]
360 {
361 $plugin.log($level, $message);
362 }
363 #[cfg(not(feature = "observability"))]
364 {
365 #[cfg(debug_assertions)]
367 {
368 println!("[{}] {}", $level.as_str(), $message);
369 }
370 }
371 };
372}
373
374#[cfg(test)]
375mod tests {
376 use super::*;
377
378 #[test]
379 fn test_noop_plugin_zero_cost() {
380 let plugin = NoOpObservabilityPlugin::new();
381
382 let _span = plugin.start_span("test", &[("key", "value")]);
384 plugin.record_metric("test_metric", 1.0, &[("label", "value")]);
385 plugin.log(LogLevel::Info, "test message");
386
387 assert!(!plugin.is_enabled());
388 assert_eq!(plugin.plugin_type(), "noop");
389 assert!(plugin.flush().is_ok());
390 }
391
392 #[test]
393 fn test_noop_metrics_collector() {
394 let mut collector = NoOpMetricsCollector::new();
395
396 assert!(
398 collector
399 .register_counter("test", "description", &["label"])
400 .is_ok()
401 );
402 assert!(
403 collector
404 .record_counter("test", 1.0, &HashMap::new())
405 .is_ok()
406 );
407 assert!(collector.get_metrics().is_empty());
408
409 collector.clear(); }
411
412 #[cfg(feature = "structured-logging")]
413 #[test]
414 fn test_noop_structured_logger() {
415 let mut logger = NoOpStructuredLogger::new();
416
417 use serde_json::json;
419 logger.log_with_trace(LogLevel::Info, "test", &json!({}), None, None);
420 logger.log_performance("test_op", Duration::from_millis(100), true, &json!({}));
421
422 logger.set_level(LogLevel::Debug);
423 assert!(logger.is_level_enabled(LogLevel::Info));
424 assert!(!logger.is_level_enabled(LogLevel::Trace));
425 }
426
427 #[test]
428 fn test_macros_compile() {
429 let _plugin = create_noop_plugin();
430
431 let _span = observability_span!(_plugin, "test_span", "key" => "value");
433 observability_metric!(_plugin, "test_metric", 1.0, "label" => "value");
434 observability_log!(_plugin, LogLevel::Info, "test message");
435 }
436}