Tracing: DAO layer + handler spans on tracing, drop all Context params (#141)
trace_db_call no longer takes an OpenTelemetry Context: it opens a db.<query_type>.<operation> span (same name via otel.name, same db.query_type / db.operation attrs) on the caller's ambient tracing span and runs the DAO body inside it. The cx / ctx / context parameter is gone from every DAO trait method, impl and call site, and from the helpers and background drains that only threaded it. The remaining request-handler spans (files, tags, faces, image, video, favorites, duplicates, memories) move to #[instrument]; the Context::current_with_span / with_remote_span_context plumbing is gone, as is extract_context_from_request (the tracing-actix-web root span covers it). otel::set_span_attributes replaces BoxedSpan::set_attributes. Background-job raw spans (thumbnails, video actors) are left for #142. tests/otel_db_spans.rs pins name, attributes, status and nesting (across an instrumented spawn); otel_traceparent now uses an #[instrument] handler. CLAUDE.md documents the tracing conventions. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
1c508cdabd
commit
b6deca2a52
55 files changed
+1942
-3852
No files matched your search
+31
-56
@@ -34,9 +34,8 @@ fn resolve_persona_filter(
|
||||
|
||||
let uid = claims.sub.parse::<i32>().unwrap_or(1);
|
||||
|
||||
let cx = opentelemetry::Context::current();
|
||||
let mut dao = persona_dao.lock().expect("Unable to lock PersonaDao");
|
||||
match dao.get_persona(&cx, uid, &pid) {
|
||||
match dao.get_persona(uid, &pid) {
|
||||
Ok(Some(p)) if p.include_all_memories => PersonaFilter::All { user_id: uid },
|
||||
_ => PersonaFilter::Single {
|
||||
user_id: uid,
|
||||
@@ -490,16 +489,15 @@ async fn list_entities<D: KnowledgeDao + 'static>(
|
||||
offset,
|
||||
};
|
||||
|
||||
let cx = opentelemetry::Context::current();
|
||||
let mut dao = dao.lock().expect("Unable to lock KnowledgeDao");
|
||||
match dao.list_entities_with_fact_counts(&cx, filter, sort, &persona) {
|
||||
match dao.list_entities_with_fact_counts(filter, sort, &persona) {
|
||||
Ok((pairs, total)) => {
|
||||
// Batch fetch persona breakdowns so the list-row tooltip
|
||||
// and detail panel can show "0 here · 12 in journal".
|
||||
// One extra query for the visible page.
|
||||
let entity_ids: Vec<i32> = pairs.iter().map(|(e, _)| e.id).collect();
|
||||
let breakdowns = dao
|
||||
.get_persona_breakdowns_for_entities(&cx, &entity_ids, persona.user_id())
|
||||
.get_persona_breakdowns_for_entities(&entity_ids, persona.user_id())
|
||||
.unwrap_or_default();
|
||||
let summaries: Vec<EntitySummary> = pairs
|
||||
.into_iter()
|
||||
@@ -534,11 +532,10 @@ async fn get_entity<D: KnowledgeDao + 'static>(
|
||||
persona_dao: PersonaDaoData,
|
||||
) -> impl Responder {
|
||||
let persona = resolve_persona_filter(&req, &claims, &persona_dao);
|
||||
let cx = opentelemetry::Context::current();
|
||||
let entity_id = id.into_inner();
|
||||
let mut dao = dao.lock().expect("Unable to lock KnowledgeDao");
|
||||
|
||||
let entity = match dao.get_entity_by_id(&cx, entity_id) {
|
||||
let entity = match dao.get_entity_by_id(entity_id) {
|
||||
Ok(Some(e)) => e,
|
||||
Ok(None) => {
|
||||
return HttpResponse::NotFound().json(serde_json::json!({"error": "Entity not found"}));
|
||||
@@ -551,7 +548,7 @@ async fn get_entity<D: KnowledgeDao + 'static>(
|
||||
};
|
||||
|
||||
// Fetch all facts (all statuses for audit), scoped to the active persona.
|
||||
let raw_facts: Vec<EntityFact> = match dao.get_facts_for_entity(&cx, entity_id, &persona) {
|
||||
let raw_facts: Vec<EntityFact> = match dao.get_facts_for_entity(entity_id, &persona) {
|
||||
Ok(f) => f,
|
||||
Err(e) => {
|
||||
log::error!("get_facts_for_entity error: {:?}", e);
|
||||
@@ -564,10 +561,7 @@ async fn get_entity<D: KnowledgeDao + 'static>(
|
||||
let mut facts = Vec::with_capacity(raw_facts.len());
|
||||
for f in raw_facts {
|
||||
let object_entity_name = if let Some(oid) = f.object_entity_id {
|
||||
dao.get_entity_by_id(&cx, oid)
|
||||
.ok()
|
||||
.flatten()
|
||||
.map(|e| e.name)
|
||||
dao.get_entity_by_id(oid).ok().flatten().map(|e| e.name)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
@@ -646,7 +640,7 @@ async fn get_entity<D: KnowledgeDao + 'static>(
|
||||
}
|
||||
|
||||
// Fetch photo links
|
||||
let photo_links: Vec<PhotoLinkDetail> = match dao.get_links_for_entity(&cx, entity_id) {
|
||||
let photo_links: Vec<PhotoLinkDetail> = match dao.get_links_for_entity(entity_id) {
|
||||
Ok(links) => links.into_iter().map(PhotoLinkDetail::from).collect(),
|
||||
Err(e) => {
|
||||
log::error!("get_links_for_entity error: {:?}", e);
|
||||
@@ -659,7 +653,7 @@ async fn get_entity<D: KnowledgeDao + 'static>(
|
||||
// {persona}" block — same data the list-row tooltip reads. One
|
||||
// query, single entity in scope.
|
||||
let persona_breakdown: Vec<PersonaCount> = dao
|
||||
.get_persona_breakdowns_for_entities(&cx, &[entity_id], persona.user_id())
|
||||
.get_persona_breakdowns_for_entities(&[entity_id], persona.user_id())
|
||||
.ok()
|
||||
.and_then(|mut map| map.remove(&entity_id))
|
||||
.unwrap_or_default()
|
||||
@@ -688,7 +682,6 @@ async fn patch_entity<D: KnowledgeDao + 'static>(
|
||||
body: web::Json<EntityPatchRequest>,
|
||||
dao: web::Data<Mutex<D>>,
|
||||
) -> impl Responder {
|
||||
let cx = opentelemetry::Context::current();
|
||||
let entity_id = id.into_inner();
|
||||
let patch = EntityPatch {
|
||||
name: body.name.clone(),
|
||||
@@ -698,7 +691,7 @@ async fn patch_entity<D: KnowledgeDao + 'static>(
|
||||
};
|
||||
|
||||
let mut dao = dao.lock().expect("Unable to lock KnowledgeDao");
|
||||
match dao.update_entity(&cx, entity_id, patch) {
|
||||
match dao.update_entity(entity_id, patch) {
|
||||
Ok(Some(entity)) => HttpResponse::Ok().json(EntitySummary::from(entity)),
|
||||
Ok(None) => HttpResponse::NotFound().json(serde_json::json!({"error": "Entity not found"})),
|
||||
Err(e) => {
|
||||
@@ -713,12 +706,11 @@ async fn delete_entity<D: KnowledgeDao + 'static>(
|
||||
id: web::Path<i32>,
|
||||
dao: web::Data<Mutex<D>>,
|
||||
) -> impl Responder {
|
||||
let cx = opentelemetry::Context::current();
|
||||
let entity_id = id.into_inner();
|
||||
let mut dao = dao.lock().expect("Unable to lock KnowledgeDao");
|
||||
|
||||
// Verify entity exists before deleting
|
||||
match dao.get_entity_by_id(&cx, entity_id) {
|
||||
match dao.get_entity_by_id(entity_id) {
|
||||
Ok(None) => {
|
||||
return HttpResponse::NotFound().json(serde_json::json!({"error": "Entity not found"}));
|
||||
}
|
||||
@@ -730,7 +722,7 @@ async fn delete_entity<D: KnowledgeDao + 'static>(
|
||||
Ok(Some(_)) => {}
|
||||
}
|
||||
|
||||
match dao.delete_entity(&cx, entity_id) {
|
||||
match dao.delete_entity(entity_id) {
|
||||
Ok(()) => HttpResponse::NoContent().finish(),
|
||||
Err(e) => {
|
||||
log::error!("delete_entity error: {:?}", e);
|
||||
@@ -749,12 +741,11 @@ async fn merge_entities<D: KnowledgeDao + 'static>(
|
||||
.json(serde_json::json!({"error": "source_id and target_id must be different"}));
|
||||
}
|
||||
|
||||
let cx = opentelemetry::Context::current();
|
||||
let mut dao = dao.lock().expect("Unable to lock KnowledgeDao");
|
||||
|
||||
// Verify both entities exist
|
||||
for id in [body.source_id, body.target_id] {
|
||||
match dao.get_entity_by_id(&cx, id) {
|
||||
match dao.get_entity_by_id(id) {
|
||||
Ok(None) => {
|
||||
return HttpResponse::BadRequest()
|
||||
.json(serde_json::json!({"error": format!("Entity {} not found", id)}));
|
||||
@@ -768,7 +759,7 @@ async fn merge_entities<D: KnowledgeDao + 'static>(
|
||||
}
|
||||
}
|
||||
|
||||
match dao.merge_entities(&cx, body.source_id, body.target_id) {
|
||||
match dao.merge_entities(body.source_id, body.target_id) {
|
||||
Ok((facts_transferred, links_transferred)) => HttpResponse::Ok().json(MergeResponse {
|
||||
merged_entity_id: body.target_id,
|
||||
deleted_entity_id: body.source_id,
|
||||
@@ -804,10 +795,9 @@ async fn synthesize_merge<D: KnowledgeDao + 'static>(
|
||||
}
|
||||
|
||||
let (source, target) = {
|
||||
let cx = opentelemetry::Context::current();
|
||||
let mut dao = dao.lock().expect("Unable to lock KnowledgeDao");
|
||||
|
||||
let source = match dao.get_entity_by_id(&cx, body.source_id) {
|
||||
let source = match dao.get_entity_by_id(body.source_id) {
|
||||
Ok(Some(e)) => e,
|
||||
Ok(None) => {
|
||||
return HttpResponse::BadRequest()
|
||||
@@ -819,7 +809,7 @@ async fn synthesize_merge<D: KnowledgeDao + 'static>(
|
||||
.json(serde_json::json!({"error": "Database error"}));
|
||||
}
|
||||
};
|
||||
let target = match dao.get_entity_by_id(&cx, body.target_id) {
|
||||
let target = match dao.get_entity_by_id(body.target_id) {
|
||||
Ok(Some(e)) => e,
|
||||
Ok(None) => {
|
||||
return HttpResponse::BadRequest()
|
||||
@@ -938,22 +928,18 @@ async fn list_facts<D: KnowledgeDao + 'static>(
|
||||
offset,
|
||||
};
|
||||
|
||||
let cx = opentelemetry::Context::current();
|
||||
let mut dao = dao.lock().expect("Unable to lock KnowledgeDao");
|
||||
match dao.list_facts(&cx, filter) {
|
||||
match dao.list_facts(filter) {
|
||||
Ok((facts, total)) => {
|
||||
let mut summaries = Vec::with_capacity(facts.len());
|
||||
for f in facts {
|
||||
let subject_entity_name = dao
|
||||
.get_entity_by_id(&cx, f.subject_entity_id)
|
||||
.get_entity_by_id(f.subject_entity_id)
|
||||
.ok()
|
||||
.flatten()
|
||||
.map(|e| e.name);
|
||||
let object_entity_name = if let Some(oid) = f.object_entity_id {
|
||||
dao.get_entity_by_id(&cx, oid)
|
||||
.ok()
|
||||
.flatten()
|
||||
.map(|e| e.name)
|
||||
dao.get_entity_by_id(oid).ok().flatten().map(|e| e.name)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
@@ -1015,11 +1001,10 @@ async fn create_fact<D: KnowledgeDao + 'static>(
|
||||
PersonaFilter::All { user_id } => (*user_id, "default".to_string()),
|
||||
};
|
||||
|
||||
let cx = opentelemetry::Context::current();
|
||||
let mut dao = dao.lock().expect("Unable to lock KnowledgeDao");
|
||||
|
||||
// Verify subject entity exists.
|
||||
match dao.get_entity_by_id(&cx, body.subject_entity_id) {
|
||||
match dao.get_entity_by_id(body.subject_entity_id) {
|
||||
Ok(None) => {
|
||||
return HttpResponse::BadRequest().json(serde_json::json!({
|
||||
"error": format!("Subject entity {} not found", body.subject_entity_id)
|
||||
@@ -1035,7 +1020,7 @@ async fn create_fact<D: KnowledgeDao + 'static>(
|
||||
|
||||
// Optional object entity validation when supplied.
|
||||
if let Some(oid) = body.object_entity_id {
|
||||
match dao.get_entity_by_id(&cx, oid) {
|
||||
match dao.get_entity_by_id(oid) {
|
||||
Ok(None) => {
|
||||
return HttpResponse::BadRequest().json(serde_json::json!({
|
||||
"error": format!("Object entity {} not found", oid)
|
||||
@@ -1078,7 +1063,7 @@ async fn create_fact<D: KnowledgeDao + 'static>(
|
||||
last_modified_at: None,
|
||||
};
|
||||
|
||||
match dao.upsert_fact(&cx, insert) {
|
||||
match dao.upsert_fact(insert) {
|
||||
Ok((fact, is_new)) => {
|
||||
let status = if is_new {
|
||||
actix_web::http::StatusCode::CREATED
|
||||
@@ -1100,7 +1085,6 @@ async fn patch_fact<D: KnowledgeDao + 'static>(
|
||||
body: web::Json<FactPatchRequest>,
|
||||
dao: web::Data<Mutex<D>>,
|
||||
) -> impl Responder {
|
||||
let cx = opentelemetry::Context::current();
|
||||
let fact_id = id.into_inner();
|
||||
let patch = FactPatch {
|
||||
predicate: body.predicate.clone(),
|
||||
@@ -1115,7 +1099,7 @@ async fn patch_fact<D: KnowledgeDao + 'static>(
|
||||
// Manual PATCH from the curation UI — provenance stamped as
|
||||
// "manual" so the audit feed can distinguish human edits from
|
||||
// agent corrections.
|
||||
match dao.update_fact(&cx, fact_id, patch, Some(("manual", "manual"))) {
|
||||
match dao.update_fact(fact_id, patch, Some(("manual", "manual"))) {
|
||||
Ok(Some(fact)) => HttpResponse::Ok().json(fact),
|
||||
Ok(None) => HttpResponse::NotFound().json(serde_json::json!({"error": "Fact not found"})),
|
||||
Err(e) => {
|
||||
@@ -1130,10 +1114,9 @@ async fn delete_fact<D: KnowledgeDao + 'static>(
|
||||
id: web::Path<i32>,
|
||||
dao: web::Data<Mutex<D>>,
|
||||
) -> impl Responder {
|
||||
let cx = opentelemetry::Context::current();
|
||||
let fact_id = id.into_inner();
|
||||
let mut dao = dao.lock().expect("Unable to lock KnowledgeDao");
|
||||
match dao.delete_fact(&cx, fact_id) {
|
||||
match dao.delete_fact(fact_id) {
|
||||
Ok(()) => HttpResponse::NoContent().finish(),
|
||||
Err(e) => {
|
||||
log::warn!("delete_fact({}) error: {:?}", fact_id, e);
|
||||
@@ -1148,7 +1131,6 @@ async fn supersede_fact<D: KnowledgeDao + 'static>(
|
||||
body: web::Json<SupersedeRequest>,
|
||||
dao: web::Data<Mutex<D>>,
|
||||
) -> impl Responder {
|
||||
let cx = opentelemetry::Context::current();
|
||||
let old_id = id.into_inner();
|
||||
if old_id == body.by_fact_id {
|
||||
return HttpResponse::BadRequest()
|
||||
@@ -1157,7 +1139,7 @@ async fn supersede_fact<D: KnowledgeDao + 'static>(
|
||||
let mut dao = dao.lock().expect("Unable to lock KnowledgeDao");
|
||||
// Manual supersede from the curation UI — same stamping rule as
|
||||
// the PATCH path.
|
||||
match dao.supersede_fact(&cx, old_id, body.by_fact_id, Some(("manual", "manual"))) {
|
||||
match dao.supersede_fact(old_id, body.by_fact_id, Some(("manual", "manual"))) {
|
||||
Ok(Some(fact)) => HttpResponse::Ok().json(fact),
|
||||
Ok(None) => {
|
||||
HttpResponse::NotFound().json(serde_json::json!({"error": "Old or new fact not found"}))
|
||||
@@ -1174,10 +1156,9 @@ async fn restore_fact<D: KnowledgeDao + 'static>(
|
||||
id: web::Path<i32>,
|
||||
dao: web::Data<Mutex<D>>,
|
||||
) -> impl Responder {
|
||||
let cx = opentelemetry::Context::current();
|
||||
let fact_id = id.into_inner();
|
||||
let mut dao = dao.lock().expect("Unable to lock KnowledgeDao");
|
||||
match dao.revert_supersession(&cx, fact_id, Some(("manual", "manual"))) {
|
||||
match dao.revert_supersession(fact_id, Some(("manual", "manual"))) {
|
||||
Ok(Some(fact)) => HttpResponse::Ok().json(fact),
|
||||
Ok(None) => HttpResponse::NotFound().json(serde_json::json!({"error": "Fact not found"})),
|
||||
Err(e) => {
|
||||
@@ -1200,9 +1181,8 @@ async fn get_recent<D: KnowledgeDao + 'static>(
|
||||
let limit = query.limit.unwrap_or(20).min(100);
|
||||
|
||||
let persona = resolve_persona_filter(&req, &claims, &persona_dao);
|
||||
let cx = opentelemetry::Context::current();
|
||||
let mut dao = dao.lock().expect("Unable to lock KnowledgeDao");
|
||||
match dao.get_recent_activity(&cx, since, limit, &persona) {
|
||||
match dao.get_recent_activity(since, limit, &persona) {
|
||||
Ok(RecentActivity { entities, facts }) => {
|
||||
let entity_summaries: Vec<EntitySummary> =
|
||||
entities.into_iter().map(EntitySummary::from).collect();
|
||||
@@ -1227,9 +1207,8 @@ async fn get_predicate_stats<D: KnowledgeDao + 'static>(
|
||||
) -> impl Responder {
|
||||
let limit = query.limit.unwrap_or(100).clamp(1, 500) as usize;
|
||||
let persona = resolve_persona_filter(&req, &claims, &persona_dao);
|
||||
let cx = opentelemetry::Context::current();
|
||||
let mut dao = dao.lock().expect("Unable to lock KnowledgeDao");
|
||||
match dao.get_predicate_stats(&cx, &persona, limit) {
|
||||
match dao.get_predicate_stats(&persona, limit) {
|
||||
Ok(rows) => HttpResponse::Ok().json(PredicateStatsResponse {
|
||||
predicates: rows
|
||||
.into_iter()
|
||||
@@ -1256,10 +1235,8 @@ async fn bulk_reject_predicate<D: KnowledgeDao + 'static>(
|
||||
.json(serde_json::json!({"error": "predicate must not be empty"}));
|
||||
}
|
||||
let persona = resolve_persona_filter(&req, &claims, &persona_dao);
|
||||
let cx = opentelemetry::Context::current();
|
||||
let mut dao = dao.lock().expect("Unable to lock KnowledgeDao");
|
||||
match dao.bulk_reject_facts_by_predicate(&cx, &persona, &predicate, Some(("manual", "manual")))
|
||||
{
|
||||
match dao.bulk_reject_facts_by_predicate(&persona, &predicate, Some(("manual", "manual"))) {
|
||||
Ok(rejected) => HttpResponse::Ok().json(BulkRejectResponse { rejected }),
|
||||
Err(e) => {
|
||||
log::error!("bulk_reject_predicate error: {:?}", e);
|
||||
@@ -1277,9 +1254,8 @@ async fn get_graph<D: KnowledgeDao + 'static>(
|
||||
) -> impl Responder {
|
||||
let limit = query.limit.unwrap_or(200).clamp(1, 1000) as usize;
|
||||
let persona = resolve_persona_filter(&req, &claims, &persona_dao);
|
||||
let cx = opentelemetry::Context::current();
|
||||
let mut dao = dao.lock().expect("Unable to lock KnowledgeDao");
|
||||
match dao.build_entity_graph(&cx, query.entity_type.as_deref(), limit, &persona) {
|
||||
match dao.build_entity_graph(query.entity_type.as_deref(), limit, &persona) {
|
||||
Ok(EntityGraph { nodes, edges }) => HttpResponse::Ok().json(GraphResponse {
|
||||
nodes: nodes
|
||||
.into_iter()
|
||||
@@ -1320,10 +1296,9 @@ async fn get_consolidation_proposals<D: KnowledgeDao + 'static>(
|
||||
let max_groups = query.limit.unwrap_or(50).clamp(1, 200) as usize;
|
||||
|
||||
let persona = resolve_persona_filter(&req, &claims, &persona_dao);
|
||||
let cx = opentelemetry::Context::current();
|
||||
let mut dao = dao.lock().expect("Unable to lock KnowledgeDao");
|
||||
let groups: Vec<ConsolidationGroup> =
|
||||
match dao.find_consolidation_proposals(&cx, threshold, max_groups) {
|
||||
match dao.find_consolidation_proposals(threshold, max_groups) {
|
||||
Ok(g) => g,
|
||||
Err(e) => {
|
||||
log::error!("find_consolidation_proposals: {:?}", e);
|
||||
@@ -1340,7 +1315,7 @@ async fn get_consolidation_proposals<D: KnowledgeDao + 'static>(
|
||||
.flat_map(|g| g.entities.iter().map(|e| e.id))
|
||||
.collect();
|
||||
let breakdowns = dao
|
||||
.get_persona_breakdowns_for_entities(&cx, &entity_ids, persona.user_id())
|
||||
.get_persona_breakdowns_for_entities(&entity_ids, persona.user_id())
|
||||
.unwrap_or_default();
|
||||
|
||||
let groups_view: Vec<ConsolidationGroupView> = groups
|
||||
|
||||
Reference in new issue
Block a user