pub mod catalog;
pub mod config;
mod error_schema;
pub mod operator_docs;
mod operator_docs_annotations;
pub use config::{ObservabilityConfig, ObservabilityConfigBuilder, OtelProtocol};
pub use error_schema::ErrorCategory;
use crate::error::{Error, Result};
#[cfg(feature = "observability")]
mod otel;
#[cfg(feature = "observability")]
pub use otel::{init, tracing_layer, ObservabilityGuard};
#[cfg(feature = "observability-testing")]
pub mod testing;
#[derive(Debug, Clone)]
pub enum AttrValue {
Str(String),
StaticStr(&'static str),
Int(i64),
Float(f64),
Bool(bool),
}
impl From<&'static str> for AttrValue {
fn from(v: &'static str) -> Self {
AttrValue::StaticStr(v)
}
}
impl From<String> for AttrValue {
fn from(v: String) -> Self {
AttrValue::Str(v)
}
}
impl From<i64> for AttrValue {
fn from(v: i64) -> Self {
AttrValue::Int(v)
}
}
impl From<u64> for AttrValue {
fn from(v: u64) -> Self {
AttrValue::Int(v as i64)
}
}
impl From<f64> for AttrValue {
fn from(v: f64) -> Self {
AttrValue::Float(v)
}
}
impl From<bool> for AttrValue {
fn from(v: bool) -> Self {
AttrValue::Bool(v)
}
}
pub type Attr = (&'static str, AttrValue);
#[cfg(feature = "observability")]
fn to_key_values(attrs: &[Attr]) -> Vec<opentelemetry::KeyValue> {
use opentelemetry::{KeyValue, Value};
attrs
.iter()
.map(|(k, v)| {
let value = match v {
AttrValue::Str(s) => Value::from(s.clone()),
AttrValue::StaticStr(s) => Value::from(*s),
AttrValue::Int(i) => Value::from(*i),
AttrValue::Float(f) => Value::from(*f),
AttrValue::Bool(b) => Value::from(*b),
};
KeyValue::new(*k, value)
})
.collect()
}
#[inline]
pub fn add_counter(name: &'static str, value: u64, attrs: &[Attr]) {
#[cfg(feature = "observability")]
{
if otel::metrics_active() {
otel::add_counter(name, value, &to_key_values(attrs));
} else {
let _ = (name, value, attrs);
}
}
#[cfg(not(feature = "observability"))]
{
let _ = (name, value, attrs);
}
}
#[inline]
pub fn record_histogram(name: &'static str, value: f64, attrs: &[Attr]) {
#[cfg(feature = "observability")]
{
if otel::metrics_active() {
otel::record_histogram(name, value, &to_key_values(attrs));
} else {
let _ = (name, value, attrs);
}
}
#[cfg(not(feature = "observability"))]
{
let _ = (name, value, attrs);
}
}
#[inline]
pub fn record_gauge(name: &'static str, value: i64, attrs: &[Attr]) {
#[cfg(feature = "observability")]
{
if otel::metrics_active() {
otel::record_gauge(name, value, &to_key_values(attrs));
} else {
let _ = (name, value, attrs);
}
}
#[cfg(not(feature = "observability"))]
{
let _ = (name, value, attrs);
}
}
#[inline]
pub fn record_error(err: &Error, subsystem: &'static str) {
#[cfg(feature = "observability")]
{
if otel::metrics_active() {
let attrs = [
opentelemetry::KeyValue::new(
catalog::attr::ERROR_CATEGORY,
err.obs_category().as_str(),
),
opentelemetry::KeyValue::new(catalog::attr::SUBSYSTEM, subsystem),
];
otel::add_counter(catalog::ERRORS_TOTAL, 1, &attrs);
otel::mark_span_error(err.obs_category());
} else {
let _ = (err, subsystem);
}
}
#[cfg(not(feature = "observability"))]
{
let _ = (err, subsystem);
}
}
#[inline]
pub fn record_result<T>(subsystem: &'static str, result: Result<T>) -> Result<T> {
if let Err(e) = &result {
record_error(e, subsystem);
}
result
}
#[inline]
pub fn set_span_parent_from_traceparent(span: &tracing::Span, traceparent: Option<&str>) {
#[cfg(feature = "observability")]
otel::set_span_parent_from_traceparent(span, traceparent);
#[cfg(not(feature = "observability"))]
{
let _ = (span, traceparent);
}
}
#[cfg(not(feature = "observability"))]
#[derive(Debug)]
pub struct ObservabilityGuard {
_private: (),
}
#[cfg(not(feature = "observability"))]
impl ObservabilityGuard {
pub fn is_active(&self) -> bool {
false
}
pub fn force_flush(&self) {}
}
#[cfg(not(feature = "observability"))]
#[inline]
pub fn init(cfg: ObservabilityConfig) -> Result<ObservabilityGuard> {
crate::storage::sstable::reader::presence_verification::apply_config(
cfg.verify_presence_oracle,
);
Ok(ObservabilityGuard { _private: () })
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn helpers_are_callable_in_any_build() {
add_counter(
catalog::READ_ROWS,
3,
&[(catalog::attr::SSTABLE_FORMAT, "bti".into())],
);
record_histogram(catalog::QUERY_DURATION, 0.001, &[]);
record_gauge(catalog::SSTABLES_OPEN, 2, &[]);
let err = Error::corruption("x");
record_error(&err, "reader");
let ok: Result<u8> = record_result("reader", Ok(1));
assert_eq!(ok.ok(), Some(1));
}
#[test]
fn init_returns_guard() {
let cfg = ObservabilityConfig::builder().enabled(false).build();
let guard = init(cfg).expect("init never fails for a disabled config");
assert!(!guard.is_active());
guard.force_flush();
}
#[test]
fn attr_value_conversions() {
assert!(matches!(AttrValue::from("s"), AttrValue::StaticStr("s")));
assert!(matches!(AttrValue::from(1i64), AttrValue::Int(1)));
assert!(matches!(AttrValue::from(2u64), AttrValue::Int(2)));
assert!(matches!(AttrValue::from(true), AttrValue::Bool(true)));
}
}