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
+34
-69
@@ -93,14 +93,12 @@ pub async fn generation_status_handler(
|
||||
query: web::Query<GenerationStatusQuery>,
|
||||
app_state: web::Data<AppState>,
|
||||
) -> impl Responder {
|
||||
let ctx = opentelemetry::Context::new();
|
||||
|
||||
if let Some(jid) = query.job_id {
|
||||
let mut dao = app_state
|
||||
.insight_job_dao
|
||||
.lock()
|
||||
.expect("Unable to lock InsightJobDao");
|
||||
match dao.get_job_by_id(&ctx, jid) {
|
||||
match dao.get_job_by_id(jid) {
|
||||
Ok(Some(job)) => {
|
||||
return HttpResponse::Ok().json(GenerationStatusResponse {
|
||||
job_id: job.id,
|
||||
@@ -136,7 +134,7 @@ pub async fn generation_status_handler(
|
||||
.insight_job_dao
|
||||
.lock()
|
||||
.expect("Unable to lock InsightJobDao");
|
||||
match dao.get_active_job(&ctx, library.id, &normalized) {
|
||||
match dao.get_active_job(library.id, &normalized) {
|
||||
Ok(Some(job)) => {
|
||||
return HttpResponse::Ok().json(GenerationStatusResponse {
|
||||
job_id: job.id,
|
||||
@@ -187,14 +185,12 @@ pub async fn cancel_generation_handler(
|
||||
request: web::Json<CancelGenerationRequest>,
|
||||
app_state: web::Data<AppState>,
|
||||
) -> impl Responder {
|
||||
let ctx = opentelemetry::Context::new();
|
||||
|
||||
if let Some(jid) = request.job_id {
|
||||
let mut dao = app_state
|
||||
.insight_job_dao
|
||||
.lock()
|
||||
.expect("Unable to lock InsightJobDao");
|
||||
match dao.cancel_job(&ctx, jid) {
|
||||
match dao.cancel_job(jid) {
|
||||
Ok(true) => {
|
||||
let mut handles = app_state
|
||||
.insight_job_handles
|
||||
@@ -238,12 +234,12 @@ pub async fn cancel_generation_handler(
|
||||
.lock()
|
||||
.expect("Unable to lock InsightJobDao");
|
||||
let ids = dao
|
||||
.get_active_job(&ctx, library.id, &normalized)
|
||||
.get_active_job(library.id, &normalized)
|
||||
.ok()
|
||||
.flatten()
|
||||
.map(|j| vec![j.id])
|
||||
.unwrap_or_default();
|
||||
let _ = dao.cancel_active_jobs(&ctx, library.id, &normalized);
|
||||
let _ = dao.cancel_active_jobs(library.id, &normalized);
|
||||
ids
|
||||
};
|
||||
|
||||
@@ -400,7 +396,7 @@ fn apply_owner_defaults(
|
||||
let Some(mut dao) = app_state.user_ai_prefs_dao.lock().ok() else {
|
||||
return;
|
||||
};
|
||||
let Ok(Some(prefs)) = dao.get_prefs(&opentelemetry::Context::new()) else {
|
||||
let Ok(Some(prefs)) = dao.get_prefs() else {
|
||||
return;
|
||||
};
|
||||
if model.is_none() {
|
||||
@@ -455,14 +451,13 @@ pub async fn generate_insight_handler(
|
||||
.insight_job_dao
|
||||
.lock()
|
||||
.expect("Unable to lock InsightJobDao");
|
||||
let ctx = opentelemetry::Context::new();
|
||||
let ids = dao
|
||||
.get_active_job(&ctx, library.id, &normalized_path)
|
||||
.get_active_job(library.id, &normalized_path)
|
||||
.ok()
|
||||
.flatten()
|
||||
.map(|j| vec![j.id])
|
||||
.unwrap_or_default();
|
||||
let _ = dao.cancel_active_jobs(&ctx, library.id, &normalized_path);
|
||||
let _ = dao.cancel_active_jobs(library.id, &normalized_path);
|
||||
ids
|
||||
};
|
||||
for jid in &old_job_ids {
|
||||
@@ -481,12 +476,7 @@ pub async fn generate_insight_handler(
|
||||
.insight_job_dao
|
||||
.lock()
|
||||
.expect("Unable to lock InsightJobDao");
|
||||
match dao.create_job(
|
||||
&opentelemetry::Context::new(),
|
||||
library.id,
|
||||
&normalized_path,
|
||||
gen_type,
|
||||
) {
|
||||
match dao.create_job(library.id, &normalized_path, gen_type) {
|
||||
Ok(id) => id,
|
||||
Err(e) => {
|
||||
log::error!("Failed to create generation job: {:?}", e);
|
||||
@@ -538,7 +528,6 @@ pub async fn generate_insight_handler(
|
||||
})
|
||||
.await;
|
||||
|
||||
let ctx = opentelemetry::Context::new();
|
||||
let mut dao = job_dao.lock().expect("Unable to lock InsightJobDao");
|
||||
|
||||
match result {
|
||||
@@ -547,18 +536,12 @@ pub async fn generate_insight_handler(
|
||||
.insight_dao()
|
||||
.lock()
|
||||
.expect("Unable to lock InsightDao");
|
||||
let insight_id = insight_dao
|
||||
.get_insight(&ctx, &path)
|
||||
.ok()
|
||||
.flatten()
|
||||
.map(|i| i.id);
|
||||
let insight_id = insight_dao.get_insight(&path).ok().flatten().map(|i| i.id);
|
||||
if let Some(id) = insight_id {
|
||||
if let Err(e) = dao.complete_job(&ctx, job_id, id) {
|
||||
if let Err(e) = dao.complete_job(job_id, id) {
|
||||
log::error!("Failed to mark job {} as completed: {:?}", job_id, e);
|
||||
}
|
||||
} else if let Err(e) =
|
||||
dao.fail_job(&ctx, job_id, "generation returned no insight")
|
||||
{
|
||||
} else if let Err(e) = dao.fail_job(job_id, "generation returned no insight") {
|
||||
log::error!("Failed to mark job {} as failed: {:?}", job_id, e);
|
||||
}
|
||||
}
|
||||
@@ -566,7 +549,7 @@ pub async fn generate_insight_handler(
|
||||
log::error!("Insight generation failed for {}: {:?}", path, e);
|
||||
// `{:#}` = one-line context chain; the job's error_message is
|
||||
// returned to the client verbatim, so no Debug/backtrace here.
|
||||
if let Err(err) = dao.fail_job(&ctx, job_id, &format!("{:#}", e)) {
|
||||
if let Err(err) = dao.fail_job(job_id, &format!("{:#}", e)) {
|
||||
log::error!("Failed to mark job {} as failed: {:?}", job_id, err);
|
||||
}
|
||||
}
|
||||
@@ -577,14 +560,14 @@ pub async fn generate_insight_handler(
|
||||
timeout_secs
|
||||
);
|
||||
if let Err(err) =
|
||||
dao.fail_job(&ctx, job_id, &format!("timeout after {}s", timeout_secs))
|
||||
dao.fail_job(job_id, &format!("timeout after {}s", timeout_secs))
|
||||
{
|
||||
log::error!("Failed to mark job {} as failed: {:?}", job_id, err);
|
||||
}
|
||||
}
|
||||
Err(_) => {
|
||||
log::error!("Insight generation task panicked for {}", path);
|
||||
if let Err(err) = dao.fail_job(&ctx, job_id, "generation task panicked") {
|
||||
if let Err(err) = dao.fail_job(job_id, "generation task panicked") {
|
||||
log::error!("Failed to mark job {} as failed: {:?}", job_id, err);
|
||||
}
|
||||
}
|
||||
@@ -627,8 +610,6 @@ pub async fn get_insight_handler(
|
||||
let normalized_path = normalize_path(&query.path);
|
||||
log::debug!("Fetching insight for {}", normalized_path);
|
||||
|
||||
let otel_context = opentelemetry::Context::new();
|
||||
|
||||
// Expand to rel_paths sharing content so an insight generated under
|
||||
// library 1 still shows when the same photo is viewed from library 2.
|
||||
let library = libraries::resolve_library_param_state(&app_state, query.library.as_deref())
|
||||
@@ -637,13 +618,13 @@ pub async fn get_insight_handler(
|
||||
.unwrap_or_else(|| app_state.primary_library());
|
||||
let sibling_paths = {
|
||||
let mut exif = exif_dao.lock().expect("Unable to lock ExifDao");
|
||||
exif.get_rel_paths_sharing_content(&otel_context, library.id, &normalized_path)
|
||||
exif.get_rel_paths_sharing_content(library.id, &normalized_path)
|
||||
.unwrap_or_else(|_| vec![normalized_path.clone()])
|
||||
};
|
||||
|
||||
let mut dao = insight_dao.lock().expect("Unable to lock InsightDao");
|
||||
|
||||
match dao.get_insight_for_paths(&otel_context, &sibling_paths) {
|
||||
match dao.get_insight_for_paths(&sibling_paths) {
|
||||
Ok(Some(insight)) => HttpResponse::Ok().json(PhotoInsightResponse::from(insight)),
|
||||
Ok(None) => HttpResponse::NotFound().json(serde_json::json!({
|
||||
"error": "Insight not found"
|
||||
@@ -667,10 +648,9 @@ pub async fn delete_insight_handler(
|
||||
let normalized_path = normalize_path(&query.path);
|
||||
log::info!("Deleting insight for {}", normalized_path);
|
||||
|
||||
let otel_context = opentelemetry::Context::new();
|
||||
let mut dao = insight_dao.lock().expect("Unable to lock InsightDao");
|
||||
|
||||
match dao.delete_insight(&otel_context, &normalized_path) {
|
||||
match dao.delete_insight(&normalized_path) {
|
||||
Ok(()) => HttpResponse::Ok().json(serde_json::json!({
|
||||
"success": true,
|
||||
"message": "Insight deleted successfully"
|
||||
@@ -692,10 +672,9 @@ pub async fn get_all_insights_handler(
|
||||
) -> impl Responder {
|
||||
log::debug!("Fetching all insights");
|
||||
|
||||
let otel_context = opentelemetry::Context::new();
|
||||
let mut dao = insight_dao.lock().expect("Unable to lock InsightDao");
|
||||
|
||||
match dao.get_all_insights(&otel_context) {
|
||||
match dao.get_all_insights() {
|
||||
Ok(insights) => {
|
||||
let responses: Vec<PhotoInsightResponse> = insights
|
||||
.into_iter()
|
||||
@@ -725,10 +704,9 @@ pub async fn get_insight_history_handler(
|
||||
let normalized_path = normalize_path(&query.path);
|
||||
log::debug!("Fetching insight history for {}", normalized_path);
|
||||
|
||||
let otel_context = opentelemetry::Context::new();
|
||||
let mut dao = insight_dao.lock().expect("Unable to lock InsightDao");
|
||||
|
||||
match dao.get_insight_history(&otel_context, &normalized_path) {
|
||||
match dao.get_insight_history(&normalized_path) {
|
||||
Ok(insights) => {
|
||||
let responses: Vec<PhotoInsightResponse> = insights
|
||||
.into_iter()
|
||||
@@ -790,14 +768,13 @@ pub async fn generate_agentic_insight_handler(
|
||||
.insight_job_dao
|
||||
.lock()
|
||||
.expect("Unable to lock InsightJobDao");
|
||||
let ctx = opentelemetry::Context::new();
|
||||
let ids = dao
|
||||
.get_active_job(&ctx, library.id, &normalized_path)
|
||||
.get_active_job(library.id, &normalized_path)
|
||||
.ok()
|
||||
.flatten()
|
||||
.map(|j| vec![j.id])
|
||||
.unwrap_or_default();
|
||||
let _ = dao.cancel_active_jobs(&ctx, library.id, &normalized_path);
|
||||
let _ = dao.cancel_active_jobs(library.id, &normalized_path);
|
||||
ids
|
||||
};
|
||||
for jid in &old_job_ids {
|
||||
@@ -816,12 +793,7 @@ pub async fn generate_agentic_insight_handler(
|
||||
.insight_job_dao
|
||||
.lock()
|
||||
.expect("Unable to lock InsightJobDao");
|
||||
match dao.create_job(
|
||||
&opentelemetry::Context::new(),
|
||||
library.id,
|
||||
&normalized_path,
|
||||
gen_type,
|
||||
) {
|
||||
match dao.create_job(library.id, &normalized_path, gen_type) {
|
||||
Ok(id) => id,
|
||||
Err(e) => {
|
||||
log::error!("Failed to create agentic generation job: {:?}", e);
|
||||
@@ -844,7 +816,6 @@ pub async fn generate_agentic_insight_handler(
|
||||
};
|
||||
|
||||
let fewshot_examples: Vec<Vec<ChatMessage>> = {
|
||||
let otel_context = opentelemetry::Context::new();
|
||||
let mut dao = app_state
|
||||
.insight_chat
|
||||
.insight_dao()
|
||||
@@ -853,7 +824,7 @@ pub async fn generate_agentic_insight_handler(
|
||||
fewshot_ids
|
||||
.iter()
|
||||
.filter_map(|id| {
|
||||
let insight = dao.get_insight_by_id(&otel_context, *id).ok().flatten()?;
|
||||
let insight = dao.get_insight_by_id(*id).ok().flatten()?;
|
||||
let json = insight.training_messages?;
|
||||
match serde_json::from_str::<Vec<ChatMessage>>(&json) {
|
||||
Ok(msgs) => Some(msgs),
|
||||
@@ -940,19 +911,16 @@ pub async fn generate_agentic_insight_handler(
|
||||
})
|
||||
.await;
|
||||
|
||||
let ctx = opentelemetry::Context::new();
|
||||
let mut dao = job_dao.lock().expect("Unable to lock InsightJobDao");
|
||||
|
||||
match result {
|
||||
Ok(Ok(Ok((Some(insight_id), _, _)))) => {
|
||||
if let Err(e) = dao.complete_job(&ctx, job_id, insight_id) {
|
||||
if let Err(e) = dao.complete_job(job_id, insight_id) {
|
||||
log::error!("Failed to mark job {} as completed: {:?}", job_id, e);
|
||||
}
|
||||
}
|
||||
Ok(Ok(Ok((None, _, _)))) => {
|
||||
if let Err(e) =
|
||||
dao.fail_job(&ctx, job_id, "agentic generation returned no insight")
|
||||
{
|
||||
if let Err(e) = dao.fail_job(job_id, "agentic generation returned no insight") {
|
||||
log::error!("Failed to mark job {} as failed: {:?}", job_id, e);
|
||||
}
|
||||
}
|
||||
@@ -960,7 +928,7 @@ pub async fn generate_agentic_insight_handler(
|
||||
log::error!("Agentic insight generation failed for {}: {:?}", path, e);
|
||||
// `{:#}` = one-line context chain; the job's error_message is
|
||||
// returned to the client verbatim, so no Debug/backtrace here.
|
||||
if let Err(err) = dao.fail_job(&ctx, job_id, &format!("{:#}", e)) {
|
||||
if let Err(err) = dao.fail_job(job_id, &format!("{:#}", e)) {
|
||||
log::error!("Failed to mark job {} as failed: {:?}", job_id, err);
|
||||
}
|
||||
}
|
||||
@@ -971,14 +939,14 @@ pub async fn generate_agentic_insight_handler(
|
||||
timeout_secs
|
||||
);
|
||||
if let Err(err) =
|
||||
dao.fail_job(&ctx, job_id, &format!("timeout after {}s", timeout_secs))
|
||||
dao.fail_job(job_id, &format!("timeout after {}s", timeout_secs))
|
||||
{
|
||||
log::error!("Failed to mark job {} as failed: {:?}", job_id, err);
|
||||
}
|
||||
}
|
||||
Err(_) => {
|
||||
log::error!("Agentic insight generation task panicked for {}", path);
|
||||
if let Err(err) = dao.fail_job(&ctx, job_id, "generation task panicked") {
|
||||
if let Err(err) = dao.fail_job(job_id, "generation task panicked") {
|
||||
log::error!("Failed to mark job {} as failed: {:?}", job_id, err);
|
||||
}
|
||||
}
|
||||
@@ -1135,14 +1103,13 @@ pub async fn rate_insight_handler(
|
||||
request.approved
|
||||
);
|
||||
|
||||
let otel_context = opentelemetry::Context::new();
|
||||
let mut dao = insight_dao.lock().expect("Unable to lock InsightDao");
|
||||
|
||||
// Rate a specific version by id when provided (history view), otherwise
|
||||
// rate the current insight for the path.
|
||||
let result = match request.insight_id {
|
||||
Some(id) => dao.rate_insight_by_id(&otel_context, id, request.approved),
|
||||
None => dao.rate_insight(&otel_context, &normalized_path, request.approved),
|
||||
Some(id) => dao.rate_insight_by_id(id, request.approved),
|
||||
None => dao.rate_insight(&normalized_path, request.approved),
|
||||
};
|
||||
|
||||
match result {
|
||||
@@ -1169,13 +1136,12 @@ pub async fn export_training_data_handler(
|
||||
let approved_only = query.approved_only.unwrap_or(true);
|
||||
log::info!("Exporting training data (approved_only={})", approved_only);
|
||||
|
||||
let otel_context = opentelemetry::Context::new();
|
||||
let mut dao = insight_dao.lock().expect("Unable to lock InsightDao");
|
||||
|
||||
let insights = if approved_only {
|
||||
dao.get_approved_insights(&otel_context)
|
||||
dao.get_approved_insights()
|
||||
} else {
|
||||
dao.get_all_insights(&otel_context)
|
||||
dao.get_all_insights()
|
||||
};
|
||||
|
||||
match insights {
|
||||
@@ -2369,8 +2335,7 @@ mod owner_defaults_tests {
|
||||
)));
|
||||
if let Some(p) = prefs {
|
||||
let mut dao = SqliteUserAiPrefsDao::from_connection(conn);
|
||||
dao.upsert_prefs(&opentelemetry::Context::new(), &p)
|
||||
.expect("seed prefs");
|
||||
dao.upsert_prefs(&p).expect("seed prefs");
|
||||
}
|
||||
state
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user