#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum OtelValue {
String {
string_value: String,
},
Int {
int_value: i64,
},
Float {
double_value: f64,
},
Bool {
bool_value: bool,
},
}
impl From<&str> for OtelValue {
fn from(s: &str) -> Self {
OtelValue::String {
string_value: s.to_string(),
}
}
}
impl From<String> for OtelValue {
fn from(s: String) -> Self {
OtelValue::String { string_value: s }
}
}
impl From<i64> for OtelValue {
fn from(v: i64) -> Self {
OtelValue::Int { int_value: v }
}
}
impl From<f64> for OtelValue {
fn from(v: f64) -> Self {
OtelValue::Float { double_value: v }
}
}
impl From<bool> for OtelValue {
fn from(v: bool) -> Self {
OtelValue::Bool { bool_value: v }
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct ObservabilityConfig {
pub trueno_db_uri: Option<String>,
pub tracing_enabled: bool,
pub trace_sample_rate: f64,
pub flush_interval_secs: u64,
pub ab_testing_enabled: bool,
}
impl ObservabilityConfig {
#[must_use]
pub fn new() -> Self {
Self {
trueno_db_uri: None,
tracing_enabled: true,
trace_sample_rate: 1.0,
flush_interval_secs: 60,
ab_testing_enabled: true,
}
}
#[must_use]
pub fn with_trueno_db(mut self, uri: impl Into<String>) -> Self {
self.trueno_db_uri = Some(uri.into());
self
}
#[must_use]
pub fn with_tracing(mut self, enabled: bool) -> Self {
self.tracing_enabled = enabled;
self
}
#[must_use]
pub fn with_sample_rate(mut self, rate: f64) -> Self {
self.trace_sample_rate = rate.clamp(0.0, 1.0);
self
}
#[must_use]
pub fn with_flush_interval(mut self, secs: u64) -> Self {
self.flush_interval_secs = secs;
self
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MetricPoint {
pub name: String,
pub value: f64,
pub timestamp: u64,
pub labels: HashMap<String, String>,
}
impl MetricPoint {
#[must_use]
pub fn new(name: impl Into<String>, value: f64) -> Self {
Self {
name: name.into(),
value,
timestamp: SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|d| d.as_millis() as u64)
.unwrap_or(0),
labels: HashMap::new(),
}
}
#[must_use]
pub fn with_label(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
self.labels.insert(key.into(), value.into());
self
}
#[must_use]
pub fn to_line_protocol(&self) -> String {
let labels_str = if self.labels.is_empty() {
String::new()
} else {
let pairs: Vec<String> = self
.labels
.iter()
.map(|(k, v)| format!("{k}={v}"))
.collect();
format!(",{}", pairs.join(","))
};
format!(
"{}{} value={} {}",
self.name, labels_str, self.value, self.timestamp
)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Span {
pub span_id: String,
pub trace_id: String,
pub parent_id: Option<String>,
pub operation: String,
pub service: String,
pub start_time: u64,
pub duration_us: Option<u64>,
pub status: SpanStatus,
pub attributes: HashMap<String, String>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
pub enum SpanStatus {
#[default]
InProgress,
Ok,
Error,
}
impl Span {
#[must_use]
pub fn new(operation: impl Into<String>, trace_id: impl Into<String>) -> Self {
Self {
span_id: generate_id(),
trace_id: trace_id.into(),
parent_id: None,
operation: operation.into(),
service: "realizar".to_string(),
start_time: SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|d| d.as_micros() as u64)
.unwrap_or(0),
duration_us: None,
status: SpanStatus::InProgress,
attributes: HashMap::new(),
}
}
#[must_use]
pub fn child(&self, operation: impl Into<String>) -> Self {
let mut span = Self::new(operation, self.trace_id.clone());
span.parent_id = Some(self.span_id.clone());
span
}
#[must_use]
pub fn with_parent(mut self, parent_id: impl Into<String>) -> Self {
self.parent_id = Some(parent_id.into());
self
}
#[must_use]
pub fn with_attribute(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
self.attributes.insert(key.into(), value.into());
self
}
pub fn end_ok(&mut self) {
self.status = SpanStatus::Ok;
self.duration_us = Some(
SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|d| d.as_micros() as u64)
.unwrap_or(0)
.saturating_sub(self.start_time),
);
}
pub fn end_error(&mut self, error: impl Into<String>) {
self.status = SpanStatus::Error;
self.attributes.insert("error".to_string(), error.into());
self.duration_us = Some(
SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|d| d.as_micros() as u64)
.unwrap_or(0)
.saturating_sub(self.start_time),
);
}
#[must_use]
pub fn duration(&self) -> Option<Duration> {
self.duration_us.map(Duration::from_micros)
}
#[must_use]
pub fn with_kind(mut self, kind: SpanKind) -> Self {
self.attributes
.insert("span.kind".to_string(), format!("{kind:?}"));
self
}
#[must_use]
pub fn to_otel(&self) -> OtelSpan {
let end_time = self.start_time + self.duration_us.unwrap_or(0);
let status = match self.status {
SpanStatus::Ok => OtelStatus {
code: OtelStatusCode::Ok,
message: None,
},
SpanStatus::Error => OtelStatus {
code: OtelStatusCode::Error,
message: self.attributes.get("error").cloned(),
},
SpanStatus::InProgress => OtelStatus {
code: OtelStatusCode::Unset,
message: None,
},
};
let attributes: Vec<OtelAttribute> = self
.attributes
.iter()
.map(|(k, v)| OtelAttribute {
key: k.clone(),
value: OtelValue::from(v.as_str()),
})
.collect();
let kind = self
.attributes
.get("span.kind")
.map_or(SpanKind::Internal, |k| match k.as_str() {
"Server" => SpanKind::Server,
"Client" => SpanKind::Client,
"Producer" => SpanKind::Producer,
"Consumer" => SpanKind::Consumer,
_ => SpanKind::Internal,
});
OtelSpan {
trace_id: self.trace_id.clone(),
span_id: self.span_id.clone(),
parent_span_id: self.parent_id.clone(),
operation_name: self.operation.clone(),
service_name: self.service.clone(),
start_time: self.start_time * 1000, end_time: end_time * 1000,
kind,
status,
attributes,
}
}
#[must_use]
pub fn trace_context(&self) -> TraceContext {
TraceContext {
trace_id: self.trace_id.clone(),
parent_span_id: Some(self.span_id.clone()),
trace_flags: 0x01, trace_state: None,
}
}
#[must_use]
pub fn traceparent(&self) -> String {
format!("00-{}-{}-01", self.trace_id, self.span_id)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ABTest {
pub name: String,
pub variants: Vec<ABVariant>,
pub active: bool,
pub start_time: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ABVariant {
pub name: String,
pub model: String,
pub weight: f64,
}