use crate::error::ObservabilityResult;
#[cfg(feature = "structured-logging")]
use crate::traits::StructuredLogger;
use crate::traits::{LogLevel, MetricsCollector, ObservabilityPlugin, SpanGuard, SpanStatus};
use std::collections::HashMap;
use std::sync::Arc;
use web_time::Duration;
#[cfg(feature = "structured-logging")]
use serde_json::Value as JsonValue;
pub struct NoOpObservabilityPlugin;
impl NoOpObservabilityPlugin {
#[inline]
pub fn new() -> Self {
Self
}
#[inline]
pub fn shared() -> Arc<Self> {
Arc::new(Self)
}
}
impl Default for NoOpObservabilityPlugin {
#[inline]
fn default() -> Self {
Self::new()
}
}
impl ObservabilityPlugin for NoOpObservabilityPlugin {
#[inline]
fn start_span(&self, _name: &str, _attributes: &[(&str, &str)]) -> SpanGuard {
SpanGuard::no_op()
}
#[inline]
fn end_span(&self, _span_id: &str) {
}
#[inline]
fn add_span_attribute(&self, _span_id: &str, _key: &str, _value: &str) {
}
#[inline]
fn set_span_status(&self, _span_id: &str, _status: SpanStatus) {
}
#[inline]
fn record_metric(&self, _name: &str, _value: f64, _labels: &[(&str, &str)]) {
}
#[cfg(feature = "structured-logging")]
#[inline]
fn log_structured(&self, _level: LogLevel, _message: &str, _fields: &JsonValue) {
}
#[inline]
fn write_log(&self, _message: &str) {
#[cfg(debug_assertions)]
{
}
}
#[inline]
fn flush(&self) -> ObservabilityResult<()> {
Ok(())
}
#[inline]
fn is_enabled(&self) -> bool {
false
}
#[inline]
fn plugin_type(&self) -> &'static str {
"noop"
}
}
pub struct NoOpMetricsCollector;
impl NoOpMetricsCollector {
#[inline]
pub fn new() -> Self {
Self
}
}
impl Default for NoOpMetricsCollector {
#[inline]
fn default() -> Self {
Self::new()
}
}
impl MetricsCollector for NoOpMetricsCollector {
#[inline]
fn register_counter(
&mut self,
_name: &str,
_description: &str,
_labels: &[&str],
) -> ObservabilityResult<()> {
Ok(())
}
#[inline]
fn register_histogram(
&mut self,
_name: &str,
_description: &str,
_buckets: &[f64],
_labels: &[&str],
) -> ObservabilityResult<()> {
Ok(())
}
#[inline]
fn register_gauge(
&mut self,
_name: &str,
_description: &str,
_labels: &[&str],
) -> ObservabilityResult<()> {
Ok(())
}
#[inline]
fn record_counter(
&self,
_name: &str,
_value: f64,
_labels: &HashMap<String, String>,
) -> ObservabilityResult<()> {
Ok(())
}
#[inline]
fn record_histogram(
&self,
_name: &str,
_value: f64,
_labels: &HashMap<String, String>,
) -> ObservabilityResult<()> {
Ok(())
}
#[inline]
fn set_gauge(
&self,
_name: &str,
_value: f64,
_labels: &HashMap<String, String>,
) -> ObservabilityResult<()> {
Ok(())
}
#[inline]
fn get_metrics(&self) -> HashMap<String, f64> {
HashMap::new()
}
#[inline]
fn clear(&mut self) {
}
}
#[cfg(feature = "structured-logging")]
pub struct NoOpStructuredLogger {
level: LogLevel,
}
#[cfg(feature = "structured-logging")]
impl NoOpStructuredLogger {
#[inline]
pub fn new() -> Self {
Self {
level: LogLevel::Info,
}
}
#[inline]
pub fn with_level(level: LogLevel) -> Self {
Self { level }
}
}
#[cfg(feature = "structured-logging")]
impl Default for NoOpStructuredLogger {
#[inline]
fn default() -> Self {
Self::new()
}
}
#[cfg(feature = "structured-logging")]
impl StructuredLogger for NoOpStructuredLogger {
#[inline]
fn log_with_trace(
&self,
_level: LogLevel,
_message: &str,
_fields: &JsonValue,
_trace_id: Option<&str>,
_span_id: Option<&str>,
) {
}
#[inline]
fn log_performance(
&self,
_operation: &str,
_duration: Duration,
_success: bool,
_additional_fields: &JsonValue,
) {
}
#[inline]
fn log_error(&self, _error: &dyn std::error::Error, _context: &JsonValue) {
}
#[inline]
fn set_level(&mut self, level: LogLevel) {
self.level = level;
}
#[inline]
fn is_level_enabled(&self, level: LogLevel) -> bool {
level <= self.level
}
}
#[inline]
pub fn create_noop_plugin() -> Box<dyn ObservabilityPlugin> {
Box::new(NoOpObservabilityPlugin::new())
}
#[inline]
pub fn create_shared_noop_plugin() -> Arc<dyn ObservabilityPlugin> {
Arc::new(NoOpObservabilityPlugin::new())
}
#[macro_export]
macro_rules! observability_plugin {
($plugin_expr:expr_2021) => {
#[cfg(feature = "observability")]
{
$plugin_expr
}
#[cfg(not(feature = "observability"))]
{
$crate::noop::create_noop_plugin()
}
};
}
#[macro_export]
macro_rules! shared_observability_plugin {
($plugin_expr:expr_2021) => {
#[cfg(feature = "observability")]
{
$plugin_expr
}
#[cfg(not(feature = "observability"))]
{
$crate::noop::create_shared_noop_plugin()
}
};
}
#[macro_export]
macro_rules! observability_span {
($plugin:expr_2021, $name:expr_2021, $($attr_key:expr_2021 => $attr_val:expr_2021),*) => {
{
#[cfg(feature = "observability")]
{
$plugin.start_span($name, &[$(($attr_key, $attr_val)),*])
}
#[cfg(not(feature = "observability"))]
{
$crate::SpanGuard::no_op()
}
}
};
($plugin:expr_2021, $name:expr_2021) => {
{
#[cfg(feature = "observability")]
{
$plugin.start_span($name, &[])
}
#[cfg(not(feature = "observability"))]
{
$crate::SpanGuard::no_op()
}
}
};
}
#[macro_export]
macro_rules! observability_metric {
($plugin:expr_2021, $name:expr_2021, $value:expr_2021, $($label_key:expr_2021 => $label_val:expr_2021),*) => {
#[cfg(feature = "observability")]
{
$plugin.record_metric($name, $value, &[$(($label_key, $label_val)),*]);
}
#[cfg(not(feature = "observability"))]
{
}
};
($plugin:expr_2021, $name:expr_2021, $value:expr_2021) => {
#[cfg(feature = "observability")]
{
$plugin.record_metric($name, $value, &[]);
}
#[cfg(not(feature = "observability"))]
{
}
};
}
#[macro_export]
macro_rules! observability_log {
($plugin:expr_2021, $level:expr_2021, $message:expr_2021) => {
#[cfg(feature = "observability")]
{
$plugin.log($level, $message);
}
#[cfg(not(feature = "observability"))]
{
#[cfg(debug_assertions)]
{
println!("[{}] {}", $level.as_str(), $message);
}
}
};
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_noop_plugin_zero_cost() {
let plugin = NoOpObservabilityPlugin::new();
let _span = plugin.start_span("test", &[("key", "value")]);
plugin.record_metric("test_metric", 1.0, &[("label", "value")]);
plugin.log(LogLevel::Info, "test message");
assert!(!plugin.is_enabled());
assert_eq!(plugin.plugin_type(), "noop");
assert!(plugin.flush().is_ok());
}
#[test]
fn test_noop_metrics_collector() {
let mut collector = NoOpMetricsCollector::new();
assert!(
collector
.register_counter("test", "description", &["label"])
.is_ok()
);
assert!(
collector
.record_counter("test", 1.0, &HashMap::new())
.is_ok()
);
assert!(collector.get_metrics().is_empty());
collector.clear(); }
#[cfg(feature = "structured-logging")]
#[test]
fn test_noop_structured_logger() {
let mut logger = NoOpStructuredLogger::new();
use serde_json::json;
logger.log_with_trace(LogLevel::Info, "test", &json!({}), None, None);
logger.log_performance("test_op", Duration::from_millis(100), true, &json!({}));
logger.set_level(LogLevel::Debug);
assert!(logger.is_level_enabled(LogLevel::Info));
assert!(!logger.is_level_enabled(LogLevel::Trace));
}
#[test]
fn test_macros_compile() {
let _plugin = create_noop_plugin();
let _span = observability_span!(_plugin, "test_span", "key" => "value");
observability_metric!(_plugin, "test_metric", 1.0, "label" => "value");
observability_log!(_plugin, LogLevel::Info, "test message");
}
}