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}