feat: complete observability stack (O1-O13) #52
@@ -118,17 +118,26 @@ pub async fn unified_query_handler(
|
||||
body: web::Json<UnifiedQueryRequest>,
|
||||
state: web::Data<AppState>,
|
||||
) -> 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();
|
||||
|
||||
// 1. Validate JWT + rate limit
|
||||
if let Err(response) = crate::handlers::middleware::validate_and_rate_limit(
|
||||
&req, &state, "query", 500
|
||||
) {
|
||||
QUERY_AUTH_FAILURES.inc();
|
||||
QUERY_ERRORS_TOTAL.inc();
|
||||
QUERY_IN_FLIGHT.dec();
|
||||
return response;
|
||||
}
|
||||
|
||||
// 2. Validate input
|
||||
if let Err(response) = validate_unified_request(&body) {
|
||||
QUERY_ERRORS_TOTAL.inc();
|
||||
QUERY_IN_FLIGHT.dec();
|
||||
return response;
|
||||
}
|
||||
|
||||
@@ -136,9 +145,16 @@ pub async fn unified_query_handler(
|
||||
body.search_type, body.query, body.entity_type, body.relation_type);
|
||||
|
||||
// 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 {
|
||||
Ok(emb) => emb.to_vec(),
|
||||
Ok(emb) => {
|
||||
QUERY_EMBEDDING_DURATION.observe(embed_start.elapsed().as_secs_f64());
|
||||
emb.to_vec()
|
||||
}
|
||||
Err(e) => {
|
||||
QUERY_EMBEDDING_FAILURES.inc();
|
||||
QUERY_ERRORS_TOTAL.inc();
|
||||
QUERY_IN_FLIGHT.dec();
|
||||
error!("Embedding failed: {}", e);
|
||||
return crate::handlers::response_builder::internal_error(
|
||||
"Failed to embed query"
|
||||
@@ -152,12 +168,15 @@ pub async fn unified_query_handler(
|
||||
"edges" => search_edges(&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(
|
||||
"search_type must be 'entities', 'edges', or 'hybrid'"
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
QUERY_IN_FLIGHT.dec();
|
||||
response
|
||||
}
|
||||
|
||||
@@ -247,6 +266,10 @@ async fn search_entities(
|
||||
|
||||
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 {
|
||||
query: req.query.clone(),
|
||||
search_type: "entities".to_string(),
|
||||
@@ -305,6 +328,9 @@ async fn search_edges(
|
||||
|
||||
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 {
|
||||
query: req.query.clone(),
|
||||
search_type: "edges".to_string(),
|
||||
@@ -350,6 +376,9 @@ async fn search_hybrid(
|
||||
|
||||
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 {
|
||||
query: req.query.clone(),
|
||||
search_type: "hybrid".to_string(),
|
||||
|
||||
Reference in New Issue
Block a user