feat: complete observability stack (O1-O13) #52

Merged
rock merged 18 commits from feat/observability-o1-o13 into main 2026-09-13 13:53:54 +00:00
Showing only changes of commit 0d0fe55519 - Show all commits
+30 -1
View File
@@ -118,17 +118,26 @@ pub async fn unified_query_handler(
body: web::Json<UnifiedQueryRequest>, body: web::Json<UnifiedQueryRequest>,
state: web::Data<AppState>, state: web::Data<AppState>,
) -> HttpResponse { ) -> HttpResponse {
use crate::metrics::*;
QUERY_REQUESTS_TOTAL.inc();
QUERY_IN_FLIGHT.inc();
let _timer = Timer::new(&QUERY_DURATION);
let start_time = std::time::Instant::now(); let start_time = std::time::Instant::now();
// 1. Validate JWT + rate limit // 1. Validate JWT + rate limit
if let Err(response) = crate::handlers::middleware::validate_and_rate_limit( if let Err(response) = crate::handlers::middleware::validate_and_rate_limit(
&req, &state, "query", 500 &req, &state, "query", 500
) { ) {
QUERY_AUTH_FAILURES.inc();
QUERY_ERRORS_TOTAL.inc();
QUERY_IN_FLIGHT.dec();
return response; return response;
} }
// 2. Validate input // 2. Validate input
if let Err(response) = validate_unified_request(&body) { if let Err(response) = validate_unified_request(&body) {
QUERY_ERRORS_TOTAL.inc();
QUERY_IN_FLIGHT.dec();
return response; return response;
} }
@@ -136,9 +145,16 @@ pub async fn unified_query_handler(
body.search_type, body.query, body.entity_type, body.relation_type); body.search_type, body.query, body.entity_type, body.relation_type);
// 3. Embed query once (reused for all search types) // 3. Embed query once (reused for all search types)
let embed_start = std::time::Instant::now();
let query_embedding = match state.embeddings.embed_one(&body.query).await { let query_embedding = match state.embeddings.embed_one(&body.query).await {
Ok(emb) => emb.to_vec(), Ok(emb) => {
QUERY_EMBEDDING_DURATION.observe(embed_start.elapsed().as_secs_f64());
emb.to_vec()
}
Err(e) => { Err(e) => {
QUERY_EMBEDDING_FAILURES.inc();
QUERY_ERRORS_TOTAL.inc();
QUERY_IN_FLIGHT.dec();
error!("Embedding failed: {}", e); error!("Embedding failed: {}", e);
return crate::handlers::response_builder::internal_error( return crate::handlers::response_builder::internal_error(
"Failed to embed query" "Failed to embed query"
@@ -152,12 +168,15 @@ pub async fn unified_query_handler(
"edges" => search_edges(&body, &state, &query_embedding, start_time).await, "edges" => search_edges(&body, &state, &query_embedding, start_time).await,
"hybrid" => search_hybrid(&body, &state, &query_embedding, start_time).await, "hybrid" => search_hybrid(&body, &state, &query_embedding, start_time).await,
_ => { _ => {
QUERY_ERRORS_TOTAL.inc();
QUERY_IN_FLIGHT.dec();
return crate::handlers::response_builder::bad_request( return crate::handlers::response_builder::bad_request(
"search_type must be 'entities', 'edges', or 'hybrid'" "search_type must be 'entities', 'edges', or 'hybrid'"
); );
} }
}; };
QUERY_IN_FLIGHT.dec();
response response
} }
@@ -247,6 +266,10 @@ async fn search_entities(
info!("Unified query (entities): {} results in {}ms", count, elapsed); info!("Unified query (entities): {} results in {}ms", count, elapsed);
// O2: Track result counts
crate::metrics::QUERY_RESULTS_TOTAL.inc_by(count as u64);
if count == 0 { crate::metrics::QUERY_EMPTY_RESULTS.inc(); }
let response = UnifiedQueryResponse { let response = UnifiedQueryResponse {
query: req.query.clone(), query: req.query.clone(),
search_type: "entities".to_string(), search_type: "entities".to_string(),
@@ -305,6 +328,9 @@ async fn search_edges(
info!("Unified query (edges): {} results in {}ms", count, elapsed); info!("Unified query (edges): {} results in {}ms", count, elapsed);
crate::metrics::QUERY_RESULTS_TOTAL.inc_by(count as u64);
if count == 0 { crate::metrics::QUERY_EMPTY_RESULTS.inc(); }
let response = UnifiedQueryResponse { let response = UnifiedQueryResponse {
query: req.query.clone(), query: req.query.clone(),
search_type: "edges".to_string(), search_type: "edges".to_string(),
@@ -350,6 +376,9 @@ async fn search_hybrid(
info!("Unified query (hybrid): {} results in {}ms", count, elapsed); info!("Unified query (hybrid): {} results in {}ms", count, elapsed);
crate::metrics::QUERY_RESULTS_TOTAL.inc_by(count as u64);
if count == 0 { crate::metrics::QUERY_EMPTY_RESULTS.inc(); }
let response = UnifiedQueryResponse { let response = UnifiedQueryResponse {
query: req.query.clone(), query: req.query.clone(),
search_type: "hybrid".to_string(), search_type: "hybrid".to_string(),