1#[cfg(feature = "otel")]
30use opentelemetry::{
31 global,
32 trace::{Span, Status, Tracer},
33 KeyValue,
34};
35
36#[cfg(feature = "otel")]
37use opentelemetry_sdk::trace::{RandomIdGenerator, Sampler, SdkTracerProvider};
38
39use anyhow::Result;
40
41#[derive(Debug, Clone)]
43pub struct TracingConfig {
44 pub service_name: String,
46 pub service_version: String,
48 pub sampling_ratio: f64,
50}
51
52impl Default for TracingConfig {
53 fn default() -> Self {
54 Self {
55 service_name: "oxify-vector".to_string(),
56 service_version: env!("CARGO_PKG_VERSION").to_string(),
57 sampling_ratio: 1.0,
58 }
59 }
60}
61
62#[cfg(feature = "otel")]
67pub fn init_tracing(_config: TracingConfig) -> Result<()> {
68 let provider = SdkTracerProvider::builder()
72 .with_id_generator(RandomIdGenerator::default())
73 .with_sampler(Sampler::AlwaysOn)
74 .build();
75
76 global::set_tracer_provider(provider);
77
78 Ok(())
79}
80
81#[cfg(feature = "otel")]
83pub fn shutdown_tracing() {
84 }
87
88#[cfg(feature = "otel")]
99pub fn trace_search<F, T>(index_name: &str, query: &[f32], k: usize, f: F) -> Result<T>
100where
101 F: FnOnce() -> T,
102{
103 let tracer = global::tracer("oxify-vector");
104 let mut span = tracer
105 .span_builder(format!("search.{}", index_name))
106 .start(&tracer);
107
108 span.set_attribute(KeyValue::new("vector.dimensions", query.len() as i64));
110 span.set_attribute(KeyValue::new("vector.k", k as i64));
111 span.set_attribute(KeyValue::new("index.name", index_name.to_string()));
112
113 let result = f();
115
116 span.set_status(Status::Ok);
118 span.end();
119
120 Ok(result)
121}
122
123#[cfg(feature = "otel")]
127#[allow(clippy::too_many_arguments)]
128pub fn trace_search_detailed<F, T>(
129 index_name: &str,
130 query: &[f32],
131 k: usize,
132 metric: &str,
133 filter_applied: bool,
134 result_count: usize,
135 f: F,
136) -> Result<T>
137where
138 F: FnOnce() -> T,
139{
140 let tracer = global::tracer("oxify-vector");
141 let mut span = tracer
142 .span_builder(format!("search.{}", index_name))
143 .start(&tracer);
144
145 span.set_attribute(KeyValue::new("vector.dimensions", query.len() as i64));
147 span.set_attribute(KeyValue::new("vector.k", k as i64));
148 span.set_attribute(KeyValue::new("index.name", index_name.to_string()));
149 span.set_attribute(KeyValue::new("search.metric", metric.to_string()));
150 span.set_attribute(KeyValue::new("search.filtered", filter_applied));
151 span.set_attribute(KeyValue::new("search.result_count", result_count as i64));
152
153 let result = f();
155
156 span.set_status(Status::Ok);
158 span.end();
159
160 Ok(result)
161}
162
163#[cfg(feature = "otel")]
165pub fn record_error_message(error_msg: &str) {
166 let tracer = global::tracer("oxify-vector");
167 let mut span = tracer.span_builder("error").start(&tracer);
168
169 span.set_status(Status::error(error_msg.to_string()));
170 span.set_attribute(KeyValue::new("error.message", error_msg.to_string()));
171 span.end();
172}
173
174#[cfg(not(feature = "otel"))]
176pub fn init_tracing(_config: TracingConfig) -> Result<()> {
177 Ok(())
178}
179
180#[cfg(not(feature = "otel"))]
181pub fn shutdown_tracing() {}
182
183#[cfg(test)]
184mod tests {
185 use super::*;
186
187 #[test]
188 fn test_tracing_config_default() {
189 let config = TracingConfig::default();
190 assert_eq!(config.service_name, "oxify-vector");
191 assert_eq!(config.sampling_ratio, 1.0);
192 }
193
194 #[test]
195 fn test_tracing_config_custom() {
196 let config = TracingConfig {
197 service_name: "my-service".to_string(),
198 service_version: "1.0.0".to_string(),
199 sampling_ratio: 0.5,
200 };
201 assert_eq!(config.service_name, "my-service");
202 assert_eq!(config.service_version, "1.0.0");
203 assert_eq!(config.sampling_ratio, 0.5);
204 }
205
206 #[test]
207 #[cfg(feature = "otel")]
208 fn test_init_and_shutdown_tracing() {
209 let config = TracingConfig::default();
210 let result = init_tracing(config);
211 assert!(result.is_ok());
212 shutdown_tracing();
213 }
214
215 #[test]
216 #[cfg(feature = "otel")]
217 fn test_trace_search() {
218 let config = TracingConfig::default();
219 init_tracing(config).unwrap();
220
221 let query = vec![0.1, 0.2, 0.3];
222 let result = trace_search("test_index", &query, 5, || vec!["doc1", "doc2"]);
223
224 assert!(result.is_ok());
225 let docs = result.unwrap();
226 assert_eq!(docs.len(), 2);
227
228 shutdown_tracing();
229 }
230
231 #[test]
232 #[cfg(feature = "otel")]
233 fn test_trace_search_detailed() {
234 let config = TracingConfig::default();
235 init_tracing(config).unwrap();
236
237 let query = vec![0.1, 0.2, 0.3];
238 let result = trace_search_detailed("test_index", &query, 5, "cosine", true, 2, || {
239 vec!["doc1", "doc2"]
240 });
241
242 assert!(result.is_ok());
243 let docs = result.unwrap();
244 assert_eq!(docs.len(), 2);
245
246 shutdown_tracing();
247 }
248
249 #[test]
250 #[cfg(not(feature = "otel"))]
251 fn test_stub_functions() {
252 let config = TracingConfig::default();
254 let result = init_tracing(config);
255 assert!(result.is_ok());
256 shutdown_tracing();
257 }
258}