Skip to main content

ri/c/
observability.rs

1//! Copyright © 2025-2026 Wenze Wei. All Rights Reserved.
2//!
3//! This file is part of Ri.
4//! The Ri project belongs to the Dunimd Team.
5//!
6//! Licensed under the Apache License, Version 2.0 (the "License");
7//! You may not use this file except in compliance with the License.
8//! You may obtain a copy of the License at
9//!
10//!     http://www.apache.org/licenses/LICENSE-2.0
11//!
12//! Unless required by applicable law or agreed to in writing, software
13//! distributed under the License is distributed on an "AS IS" BASIS,
14//! WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15//! See the License for the specific language governing permissions and
16//! limitations under the License.
17
18//! # Observability Module C API
19//!
20//! This module provides C language bindings for Ri's observability infrastructure. The observability
21//! module delivers comprehensive system monitoring capabilities including distributed tracing, metrics
22//! collection, and health checking. This C API enables C/C++ applications to leverage Ri's
23//! observability features for understanding system behavior, debugging issues, and monitoring performance
24//! in production environments.
25//!
26//! ## Module Architecture
27//!
28//! The observability module comprises three primary components that together provide complete
29//! monitoring and tracing capabilities:
30//!
31//! - **RiObservabilityConfig**: Configuration container for observability infrastructure parameters
32//!   including tracing settings, metrics collection options, export destinations, and sampling
33//!   configurations. The configuration object controls resource allocation, export strategies, and
34//!   behavioral characteristics for all observability features.
35//!
36//! - **RiTracer**: Distributed tracing interface for creating and managing trace spans, propagating
37//!   context across service boundaries, and exporting trace data to analysis systems. The tracer
38//!   implements OpenTelemetry-compatible tracing with automatic instrumentation support.
39//!
40//! - **RiMetricsRegistry**: Metrics collection and aggregation system supporting multiple metric types
41//!   including counters, gauges, histograms, and summaries. The registry manages metric lifecycle,
42//!   provides dimensional labeling, and exports metrics to monitoring backends.
43//!
44//! ## Distributed Tracing
45//!
46//! The tracing system implements comprehensive distributed tracing capabilities:
47//!
48//! - **Span Creation**: Create trace spans with parent-child relationships to model request flows
49//!   across service boundaries. Spans capture timing, status, attributes, and events.
50//!
51//! - **Context Propagation**: Propagate trace context across process boundaries using W3C Trace Context
52//!   standard. Supports propagation via HTTP headers, gRPC metadata, and message queue properties.
53//!
54//! - **Automatic Instrumentation**: Built-in instrumentation for common frameworks and libraries
55//!   including HTTP servers/clients, database drivers, and message queues.
56//!
57//! - **Sampling Strategies**: Configurable sampling to balance observability with performance impact.
58//!   Supports rate-based, probabilistic, and tail-based sampling strategies.
59//!
60//! - **Span Attributes**: Attach key-value attributes to spans for filtering and aggregating traces.
61//!   Supports automatic attributes (HTTP method, status code) and custom application attributes.
62//!
63//! - **Span Events**: Record timestamped events within spans for debugging and audit trails.
64//!   Events capture discrete occurrences during span execution.
65//!
66//! - **Error Recording**: Automatic error capturing with stack traces and error attributes.
67//!   Errors are marked on spans with full exception information.
68//!
69//! ## Metrics System
70//!
71//! The metrics system provides comprehensive performance and operational monitoring:
72//!
73//! - **Counter Metrics**: Increment-only metrics for counting events like requests, errors, or
74//!   operations. Useful for tracking cumulative totals and rates.
75//!
76//! - **Gauge Metrics**: Arbitrary value metrics that can increase or decrease over time.
77//!   Suitable for tracking current values like queue depth, memory usage, or active connections.
78//!
79//! - **Histogram Metrics**: Statistical distribution metrics that bucket values into configurable
80//!   quantiles. Useful for tracking latency distributions, request sizes, and response times.
81//!
82//! - **Summary Metrics**: Client-side calculated quantiles with optional sum tracking.
83//!   Provides pre-computed percentiles for high-cardinality metrics.
84//!
85//! - **Dimensional Labels**: Attach multiple key-value labels to metrics for flexible filtering
86//!   and aggregation. Labels enable drill-down analysis across service versions, regions, or
87//!   deployment environments.
88//!
89//! - **Metric Views**: Define custom aggregations and label combinations to control storage
90//!   costs and query performance.
91//!
92//! ## Health Checking
93//!
94//! The observability module includes health check infrastructure:
95//!
96//! - **Liveness Probes**: Indicate whether the service is running. Used by orchestrators like
97//!   Kubernetes to restart unhealthy containers.
98//!
99//! - **Readiness Probes**: Indicate whether the service is ready to accept traffic. Prevents
100//!   routing requests to services that are still initializing or overloaded.
101//!
102//! - **Startup Probes**: Slow-startup detection for applications with long initialization times.
103//!   Allows services time to become ready before liveness checks begin.
104//!
105//! - **Custom Health Checks**: Register application-specific health check functions that examine
106//!   dependencies like database connectivity, cache availability, or external services.
107//!
108//! ## Export Pipelines
109//!
110//! The observability system supports multiple export destinations:
111//!
112//! - **OpenTelemetry Protocol**: Standardized export format for compatibility with observability
113//!   backends. Supports both push and pull-based export models.
114//!
115//! - **Prometheus**: Pull-based metrics export compatible with Prometheus and related tools.
116//!   Supports both metrics and trace exposition endpoints.
117//!
118//! - **Jaeger**: Direct export to Jaeger tracing backend for distributed tracing visualization.
119//!
120//! - **Zipkin**: Export to Zipkin tracing backend for distributed tracing analysis.
121//!
122//! - **Logging Export**: Export traces and metrics to application logs for unified log analysis.
123//!
124//! - **Custom Exporters**: User-defined exporters for integration with proprietary monitoring
125//!   systems or specialized analysis pipelines.
126//!
127//! ## Performance Characteristics
128//!
129//! Observability operations are designed for minimal performance impact:
130//!
131//! - **Async Export**: Non-blocking metric and trace export using background threads.
132//!   Producer-consumer patterns prevent slow exporters from impacting application performance.
133//!
134//! - **Sampling**: Configurable sampling reduces trace volume for high-traffic services.
135//!   Default conservative sampling minimizes overhead while preserving important traces.
136//!
137//! - **Batching**: Multiple metrics and traces batched together for efficient network export.
138//!   Reduces connection overhead and improves throughput.
139//!
140//! - **Lazy Initialization**: Observability infrastructure initialized on first use when possible.
141//!   Reduces startup time and memory footprint for unused features.
142//!
143//! - **Memory Bounds**: Internal buffers and queues have configurable size limits to prevent
144//!   unbounded memory growth under high load or exporter failures.
145//!
146//! ## Memory Management
147//!
148//! All C API objects use opaque pointers with manual memory management:
149//!
150//! - Constructor functions allocate new instances on the heap
151//! - Destructor functions must be called to release memory
152//! - Tracer instances should be properly shut down before freeing
153//! - Metrics registries can be shared across components
154//!
155//! ## Thread Safety
156//!
157//! The underlying implementations are thread-safe:
158//!
159//! - Concurrent span creation from multiple threads is supported
160//! - Metric recording operations are lock-free for performance
161//! - Export pipelines handle concurrent data from multiple threads
162//! - Configuration can be modified at runtime for some parameters
163//!
164//! ## Usage Example
165//!
166//! ```c
167//! // Create observability configuration
168//! RiObservabilityConfig* config = ri_observability_config_new();
169//! if (config == NULL) {
170//!     fprintf(stderr, "Failed to create observability config\n");
171//!     return ERROR_INIT;
172//! }
173//!
174//! // Configure tracing
175//! ri_observability_config_set_tracing_enabled(config, true);
176//! ri_observability_config_set_tracing_samplerate(config, 0.1);  // 10% sampling
177//! ri_observability_config_set_tracing_exporter(config, "otlp");
178//!
179//! // Configure metrics
180//! ri_observability_config_set_metrics_enabled(config, true);
181//! ri_observability_config_set_metrics_export_interval(config, 60000);  // 60 seconds
182//!
183//! // Configure health checks
184//! ri_observability_config_set_health_check_enabled(config, true);
185//!
186//! // Create tracer instance
187//! RiTracer* tracer = ri_tracer_new(config);
188//! if (tracer == NULL) {
189//!     fprintf(stderr, "Failed to create tracer\n");
190//!     ri_observability_config_free(config);
191//!     return ERROR_INIT;
192//! }
193//!
194//! // Create metrics registry
195//! RiMetricsRegistry* metrics = ri_metrics_registry_new(config);
196//! if (metrics == NULL) {
197//!     fprintf(stderr, "Failed to create metrics registry\n");
198//!     ri_tracer_free(tracer);
199//!     ri_observability_config_free(config);
200//!     return ERROR_INIT;
201//! }
202//!
203//! // Create a span for tracing
204//! RiTraceSpan* span = ri_tracer_start_span(tracer, "handle_request");
205//! ri_trace_span_set_attribute(span, "http.method", "GET");
206//! ri_trace_span_set_attribute(span, "http.url", "/api/users");
207//!
208//! // Record metrics
209//! ri_metrics_registry_counter_increment(metrics, "http_requests_total",
210//!     1,  // value
211//!     2,  // label count
212//!     "method", "GET",       // label name, value
213//!     "path", "/api/users"  // label name, value
214//! );
215//!
216//! // Simulate work
217//! // ... application logic ...
218//!
219//! // End span
220//! ri_trace_span_end(span);
221//!
222//! // Graceful shutdown
223//! ri_tracer_shutdown(tracer);  // Flush remaining traces
224//! ri_tracer_free(tracer);
225//! ri_metrics_registry_free(metrics);
226//! ri_observability_config_free(config);
227//! ```
228//!
229//! ## Dependencies
230//!
231//! This module depends on the following Ri components:
232//!
233//! - `crate::observability`: Rust observability module implementation
234//! - `crate::prelude`: Common types and traits
235//! - OpenTelemetry SDK for tracing
236//! - Metrics library for metric collection
237//!
238//! ## Feature Flags
239//!
240//! The observability module is enabled by the "observability" feature flag.
241//! Disable this feature to reduce binary size when observability is not required.
242//!
243//! Additional features:
244//!
245//! - observability-tracing: Enable distributed tracing
246//! - observability-metrics: Enable metrics collection
247//! - observability-health: Enable health check endpoints
248//! - observability-opentelemetry: Enable OpenTelemetry export
249//! - observability-prometheus: Enable Prometheus export
250
251use crate::observability::{RiMetricsRegistry, RiObservabilityConfig, RiTracer, RiSpanKind, RiSpanStatus, RiSpanId, RiTraceId};
252
253
254c_wrapper!(CRiObservabilityConfig, RiObservabilityConfig);
255c_wrapper!(CRiTracer, RiTracer);
256c_wrapper!(CRiMetricsRegistry, RiMetricsRegistry);
257c_wrapper!(CRiSpanId, RiSpanId);
258c_wrapper!(CRiTraceId, RiTraceId);
259
260// RiObservabilityConfig constructors and destructors
261c_constructor!(
262    ri_observability_config_new,
263    CRiObservabilityConfig,
264    RiObservabilityConfig,
265    RiObservabilityConfig::default()
266);
267c_destructor!(ri_observability_config_free, CRiObservabilityConfig);
268
269// RiTracer C bindings
270#[no_mangle]
271pub extern "C" fn ri_tracer_new(sampling_rate: f64) -> *mut CRiTracer {
272    Box::into_raw(Box::new(CRiTracer::new(RiTracer::new(sampling_rate))))
273}
274c_destructor!(ri_tracer_free, CRiTracer);
275
276#[no_mangle]
277pub extern "C" fn ri_tracer_start_trace(
278    tracer: *mut CRiTracer,
279    name: *const std::ffi::c_char,
280) -> *mut std::ffi::c_char {
281    if tracer.is_null() || name.is_null() {
282        return std::ptr::null_mut();
283    }
284    unsafe {
285        let name_str = match std::ffi::CStr::from_ptr(name).to_str() {
286            Ok(s) => s.to_string(),
287            Err(_) => return std::ptr::null_mut(),
288        };
289        match (*tracer).inner.start_trace(name_str) {
290            Some(trace_id) => match std::ffi::CString::new(trace_id.as_str()) {
291                Ok(c_str) => c_str.into_raw(),
292                Err(_) => std::ptr::null_mut(),
293            },
294            None => std::ptr::null_mut(),
295        }
296    }
297}
298
299#[no_mangle]
300pub extern "C" fn ri_tracer_start_span(
301    tracer: *mut CRiTracer,
302    name: *const std::ffi::c_char,
303    kind: std::ffi::c_int,
304) -> *mut std::ffi::c_char {
305    if tracer.is_null() || name.is_null() {
306        return std::ptr::null_mut();
307    }
308    unsafe {
309        let name_str = match std::ffi::CStr::from_ptr(name).to_str() {
310            Ok(s) => s.to_string(),
311            Err(_) => return std::ptr::null_mut(),
312        };
313        let span_kind = match kind {
314            0 => RiSpanKind::Server,
315            1 => RiSpanKind::Client,
316            2 => RiSpanKind::Producer,
317            3 => RiSpanKind::Consumer,
318            _ => RiSpanKind::Internal,
319        };
320        match (*tracer).inner.start_span_from_context(name_str, span_kind) {
321            Some(span_id) => match std::ffi::CString::new(span_id.as_str()) {
322                Ok(c_str) => c_str.into_raw(),
323                Err(_) => std::ptr::null_mut(),
324            },
325            None => std::ptr::null_mut(),
326        }
327    }
328}
329
330#[no_mangle]
331pub extern "C" fn ri_tracer_end_span(
332    tracer: *mut CRiTracer,
333    span_id: *const std::ffi::c_char,
334    status: std::ffi::c_int,
335) -> std::ffi::c_int {
336    if tracer.is_null() || span_id.is_null() {
337        return -1;
338    }
339    unsafe {
340        let span_id_str = match std::ffi::CStr::from_ptr(span_id).to_str() {
341            Ok(s) => s,
342            Err(_) => return -2,
343        };
344        let span_status = match status {
345            0 => RiSpanStatus::Ok,
346            1 => RiSpanStatus::Error("C API error".to_string()),
347            _ => RiSpanStatus::Unset,
348        };
349        let span_id_obj = RiSpanId::from_string(span_id_str.to_string());
350        match (*tracer).inner.end_span(&span_id_obj, span_status) {
351            Ok(_) => 0,
352            Err(_) => -3,
353        }
354    }
355}
356
357#[no_mangle]
358pub extern "C" fn ri_tracer_set_attribute(
359    tracer: *mut CRiTracer,
360    span_id: *const std::ffi::c_char,
361    key: *const std::ffi::c_char,
362    value: *const std::ffi::c_char,
363) -> std::ffi::c_int {
364    if tracer.is_null() || span_id.is_null() || key.is_null() || value.is_null() {
365        return -1;
366    }
367    unsafe {
368        let span_id_str = match std::ffi::CStr::from_ptr(span_id).to_str() {
369            Ok(s) => s,
370            Err(_) => return -2,
371        };
372        let key_str = match std::ffi::CStr::from_ptr(key).to_str() {
373            Ok(s) => s.to_string(),
374            Err(_) => return -2,
375        };
376        let value_str = match std::ffi::CStr::from_ptr(value).to_str() {
377            Ok(s) => s.to_string(),
378            Err(_) => return -2,
379        };
380        let span_id_obj = RiSpanId::from_string(span_id_str.to_string());
381        match (*tracer).inner.span_mut(&span_id_obj, |span| {
382            span.set_attribute(key_str, value_str);
383        }) {
384            Ok(_) => 0,
385            Err(_) => -3,
386        }
387    }
388}
389
390#[no_mangle]
391pub extern "C" fn ri_tracer_get_active_trace_count(tracer: *mut CRiTracer) -> usize {
392    if tracer.is_null() {
393        return 0;
394    }
395    unsafe { (*tracer).inner.active_trace_count() }
396}
397
398#[no_mangle]
399pub extern "C" fn ri_tracer_get_active_span_count(tracer: *mut CRiTracer) -> usize {
400    if tracer.is_null() {
401        return 0;
402    }
403    unsafe { (*tracer).inner.active_span_count() }
404}
405
406// RiMetricsRegistry C bindings
407#[no_mangle]
408pub extern "C" fn ri_metrics_registry_new() -> *mut CRiMetricsRegistry {
409    Box::into_raw(Box::new(CRiMetricsRegistry::new(RiMetricsRegistry::new())))
410}
411c_destructor!(ri_metrics_registry_free, CRiMetricsRegistry);
412
413#[no_mangle]
414pub extern "C" fn ri_metrics_registry_get_metric_count(registry: *mut CRiMetricsRegistry) -> usize {
415    if registry.is_null() {
416        return 0;
417    }
418    unsafe { (*registry).inner.get_all_metrics().len() }
419}
420
421#[no_mangle]
422pub extern "C" fn ri_metrics_registry_get_metric_value(
423    registry: *mut CRiMetricsRegistry,
424    name: *const std::ffi::c_char,
425) -> f64 {
426    if registry.is_null() || name.is_null() {
427        return 0.0;
428    }
429    unsafe {
430        let name_str = match std::ffi::CStr::from_ptr(name).to_str() {
431            Ok(s) => s,
432            Err(_) => return 0.0,
433        };
434        match (*registry).inner.get_metric(name_str) {
435            Some(metric) => metric.get_value(),
436            None => 0.0,
437        }
438    }
439}
440
441#[no_mangle]
442pub extern "C" fn ri_metrics_registry_export_prometheus(
443    registry: *mut CRiMetricsRegistry,
444) -> *mut std::ffi::c_char {
445    if registry.is_null() {
446        return std::ptr::null_mut();
447    }
448    #[allow(unused_unsafe)]
449    unsafe {
450        #[cfg(feature = "observability")]
451        {
452            let output = (*registry).inner.export_prometheus();
453            match std::ffi::CString::new(output) {
454                Ok(c_str) => c_str.into_raw(),
455                Err(_) => std::ptr::null_mut(),
456            }
457        }
458        #[cfg(not(feature = "observability"))]
459        {
460            match std::ffi::CString::new("# Observability feature not enabled") {
461                Ok(c_str) => c_str.into_raw(),
462                Err(_) => std::ptr::null_mut(),
463            }
464        }
465    }
466}
467
468// RiSystemMetrics C bindings (if system_info feature is enabled)
469#[cfg(feature = "system_info")]
470use crate::observability::{RiSystemMetrics, RiSystemMetricsCollector};
471
472#[cfg(feature = "system_info")]
473c_wrapper!(CRiSystemMetrics, RiSystemMetrics);
474
475#[cfg(feature = "system_info")]
476#[no_mangle]
477pub extern "C" fn ri_system_metrics_new() -> *mut CRiSystemMetrics {
478    Box::into_raw(Box::new(CRiSystemMetrics::new(RiSystemMetrics::default())))
479}
480
481#[cfg(feature = "system_info")]
482c_destructor!(ri_system_metrics_free, CRiSystemMetrics);
483
484#[cfg(feature = "system_info")]
485#[no_mangle]
486pub extern "C" fn ri_system_metrics_get_cpu_usage(metrics: *mut CRiSystemMetrics) -> f64 {
487    if metrics.is_null() {
488        return 0.0;
489    }
490    unsafe { (*metrics).inner.cpu.total_usage_percent }
491}
492
493#[cfg(feature = "system_info")]
494#[no_mangle]
495pub extern "C" fn ri_system_metrics_get_memory_used(metrics: *mut CRiSystemMetrics) -> u64 {
496    if metrics.is_null() {
497        return 0;
498    }
499    unsafe { (*metrics).inner.memory.used_bytes }
500}
501
502#[cfg(feature = "system_info")]
503#[no_mangle]
504pub extern "C" fn ri_system_metrics_get_memory_total(metrics: *mut CRiSystemMetrics) -> u64 {
505    if metrics.is_null() {
506        return 0;
507    }
508    unsafe { (*metrics).inner.memory.total_bytes }
509}
510
511#[cfg(feature = "system_info")]
512#[no_mangle]
513pub extern "C" fn ri_system_metrics_get_disk_used(metrics: *mut CRiSystemMetrics) -> u64 {
514    if metrics.is_null() {
515        return 0;
516    }
517    unsafe { (*metrics).inner.disk.used_bytes }
518}
519
520#[cfg(feature = "system_info")]
521#[no_mangle]
522pub extern "C" fn ri_system_metrics_get_disk_total(metrics: *mut CRiSystemMetrics) -> u64 {
523    if metrics.is_null() {
524        return 0;
525    }
526    unsafe { (*metrics).inner.disk.total_bytes }
527}
528
529#[cfg(feature = "system_info")]
530c_wrapper!(CRiSystemMetricsCollector, RiSystemMetricsCollector);
531
532#[cfg(feature = "system_info")]
533#[no_mangle]
534pub extern "C" fn ri_system_metrics_collector_new() -> *mut CRiSystemMetricsCollector {
535    Box::into_raw(Box::new(CRiSystemMetricsCollector::new(RiSystemMetricsCollector::new())))
536}
537
538#[cfg(feature = "system_info")]
539c_destructor!(ri_system_metrics_collector_free, CRiSystemMetricsCollector);
540
541#[cfg(feature = "system_info")]
542#[no_mangle]
543pub extern "C" fn ri_system_metrics_collector_collect(
544    collector: *mut CRiSystemMetricsCollector,
545    out_metrics: *mut *mut CRiSystemMetrics,
546) -> std::ffi::c_int {
547    if collector.is_null() || out_metrics.is_null() {
548        return -1;
549    }
550    unsafe {
551        let metrics = (*collector).inner.collect();
552        *out_metrics = Box::into_raw(Box::new(CRiSystemMetrics::new(metrics)));
553        0
554    }
555}