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
+14
-46
@@ -20,16 +20,14 @@ use std::collections::HashMap;
|
||||
use std::sync::Mutex;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use actix_web::{App, HttpRequest, HttpResponse, Responder, dev::ServiceFactory, web};
|
||||
use actix_web::{App, HttpResponse, Responder, dev::ServiceFactory, web};
|
||||
use bk_tree::{BKTree, Metric};
|
||||
use lazy_static::lazy_static;
|
||||
use opentelemetry::trace::{TraceContextExt, Tracer};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::data::Claims;
|
||||
use crate::database::{DuplicateRow, ExifDao};
|
||||
use crate::libraries;
|
||||
use crate::otel::{extract_context_from_request, global_tracer};
|
||||
use crate::state::AppState;
|
||||
|
||||
// ── Cache ────────────────────────────────────────────────────────────────
|
||||
@@ -223,17 +221,13 @@ pub struct FolderPair {
|
||||
|
||||
// ── Handlers ─────────────────────────────────────────────────────────────
|
||||
|
||||
#[tracing::instrument(name = "duplicates.list_exact", skip_all)]
|
||||
async fn list_exact_handler(
|
||||
_: Claims,
|
||||
request: HttpRequest,
|
||||
app_state: web::Data<AppState>,
|
||||
query: web::Query<ListDuplicatesQuery>,
|
||||
exif_dao: web::Data<Mutex<Box<dyn ExifDao>>>,
|
||||
) -> impl Responder {
|
||||
let context = extract_context_from_request(&request);
|
||||
let span = global_tracer().start_with_context("duplicates.list_exact", &context);
|
||||
let span_context = opentelemetry::Context::current_with_span(span);
|
||||
|
||||
let library_id = libraries::resolve_library_param_state(&app_state, query.library.as_deref())
|
||||
.ok()
|
||||
.flatten()
|
||||
@@ -242,7 +236,7 @@ async fn list_exact_handler(
|
||||
|
||||
let rows = {
|
||||
let mut dao = exif_dao.lock().expect("exif dao lock");
|
||||
match dao.list_duplicates_exact(&span_context, library_id, include_resolved) {
|
||||
match dao.list_duplicates_exact(library_id, include_resolved) {
|
||||
Ok(rows) => rows,
|
||||
Err(e) => {
|
||||
return HttpResponse::InternalServerError().body(format!("{:?}", e));
|
||||
@@ -254,17 +248,13 @@ async fn list_exact_handler(
|
||||
HttpResponse::Ok().json(GroupsResponse { groups })
|
||||
}
|
||||
|
||||
#[tracing::instrument(name = "duplicates.list_perceptual", skip_all)]
|
||||
async fn list_perceptual_handler(
|
||||
_: Claims,
|
||||
request: HttpRequest,
|
||||
app_state: web::Data<AppState>,
|
||||
query: web::Query<ListDuplicatesQuery>,
|
||||
exif_dao: web::Data<Mutex<Box<dyn ExifDao>>>,
|
||||
) -> impl Responder {
|
||||
let context = extract_context_from_request(&request);
|
||||
let span = global_tracer().start_with_context("duplicates.list_perceptual", &context);
|
||||
let span_context = opentelemetry::Context::current_with_span(span);
|
||||
|
||||
let library_id = libraries::resolve_library_param_state(&app_state, query.library.as_deref())
|
||||
.ok()
|
||||
.flatten()
|
||||
@@ -287,7 +277,7 @@ async fn list_perceptual_handler(
|
||||
|
||||
let rows = {
|
||||
let mut dao = exif_dao.lock().expect("exif dao lock");
|
||||
match dao.list_perceptual_candidates(&span_context, library_id, include_resolved) {
|
||||
match dao.list_perceptual_candidates(library_id, include_resolved) {
|
||||
Ok(rows) => rows,
|
||||
Err(e) => {
|
||||
return HttpResponse::InternalServerError().body(format!("{:?}", e));
|
||||
@@ -310,16 +300,12 @@ async fn list_perceptual_handler(
|
||||
HttpResponse::Ok().json(GroupsResponse { groups })
|
||||
}
|
||||
|
||||
#[tracing::instrument(name = "duplicates.resolve", skip_all)]
|
||||
async fn resolve_handler(
|
||||
_: Claims,
|
||||
request: HttpRequest,
|
||||
body: web::Json<ResolveDuplicatesReq>,
|
||||
exif_dao: web::Data<Mutex<Box<dyn ExifDao>>>,
|
||||
) -> impl Responder {
|
||||
let context = extract_context_from_request(&request);
|
||||
let span = global_tracer().start_with_context("duplicates.resolve", &context);
|
||||
let span_context = opentelemetry::Context::current_with_span(span);
|
||||
|
||||
if body.demoted.is_empty() {
|
||||
return HttpResponse::BadRequest().body("demoted list is empty");
|
||||
}
|
||||
@@ -328,11 +314,8 @@ async fn resolve_handler(
|
||||
|
||||
// Resolve survivor → its content_hash, plus the canonical rel_path
|
||||
// we'll use as the destination for any tag-union INSERTs.
|
||||
let survivor = match dao.lookup_duplicate_row(
|
||||
&span_context,
|
||||
body.survivor.library_id,
|
||||
&body.survivor.rel_path,
|
||||
) {
|
||||
let survivor = match dao.lookup_duplicate_row(body.survivor.library_id, &body.survivor.rel_path)
|
||||
{
|
||||
Ok(Some(row)) => row,
|
||||
Ok(None) => return HttpResponse::NotFound().body("survivor not found"),
|
||||
Err(e) => return HttpResponse::InternalServerError().body(format!("{:?}", e)),
|
||||
@@ -348,11 +331,7 @@ async fn resolve_handler(
|
||||
let mut resolved_count = 0usize;
|
||||
|
||||
for member_ref in &body.demoted {
|
||||
let demoted = match dao.lookup_duplicate_row(
|
||||
&span_context,
|
||||
member_ref.library_id,
|
||||
&member_ref.rel_path,
|
||||
) {
|
||||
let demoted = match dao.lookup_duplicate_row(member_ref.library_id, &member_ref.rel_path) {
|
||||
Ok(Some(row)) => row,
|
||||
Ok(None) => {
|
||||
log::warn!(
|
||||
@@ -379,7 +358,6 @@ async fn resolve_handler(
|
||||
// already shared at the bytes layer — the union is a no-op.
|
||||
if demoted.content_hash != survivor.content_hash
|
||||
&& let Err(e) = dao.union_perceptual_tags(
|
||||
&span_context,
|
||||
&survivor.content_hash,
|
||||
&demoted.content_hash,
|
||||
&survivor.rel_path,
|
||||
@@ -397,7 +375,6 @@ async fn resolve_handler(
|
||||
}
|
||||
|
||||
if let Err(e) = dao.set_duplicate_of(
|
||||
&span_context,
|
||||
demoted.library_id,
|
||||
&demoted.rel_path,
|
||||
&survivor.content_hash,
|
||||
@@ -416,18 +393,14 @@ async fn resolve_handler(
|
||||
HttpResponse::Ok().json(ResolveResponse { resolved_count })
|
||||
}
|
||||
|
||||
#[tracing::instrument(name = "duplicates.unresolve", skip_all)]
|
||||
async fn unresolve_handler(
|
||||
_: Claims,
|
||||
request: HttpRequest,
|
||||
body: web::Json<UnresolveDuplicateReq>,
|
||||
exif_dao: web::Data<Mutex<Box<dyn ExifDao>>>,
|
||||
) -> impl Responder {
|
||||
let context = extract_context_from_request(&request);
|
||||
let span = global_tracer().start_with_context("duplicates.unresolve", &context);
|
||||
let span_context = opentelemetry::Context::current_with_span(span);
|
||||
|
||||
let mut dao = exif_dao.lock().expect("exif dao lock");
|
||||
if let Err(e) = dao.clear_duplicate_of(&span_context, body.library_id, &body.rel_path) {
|
||||
if let Err(e) = dao.clear_duplicate_of(body.library_id, &body.rel_path) {
|
||||
return HttpResponse::InternalServerError().body(format!("{:?}", e));
|
||||
}
|
||||
|
||||
@@ -438,17 +411,13 @@ async fn unresolve_handler(
|
||||
HttpResponse::Ok().finish()
|
||||
}
|
||||
|
||||
#[tracing::instrument(name = "duplicates.list_folder_pairs", skip_all)]
|
||||
async fn list_folder_pairs_handler(
|
||||
_: Claims,
|
||||
request: HttpRequest,
|
||||
app_state: web::Data<AppState>,
|
||||
query: web::Query<ListFolderPairsQuery>,
|
||||
exif_dao: web::Data<Mutex<Box<dyn ExifDao>>>,
|
||||
) -> impl Responder {
|
||||
let context = extract_context_from_request(&request);
|
||||
let span = global_tracer().start_with_context("duplicates.list_folder_pairs", &context);
|
||||
let span_context = opentelemetry::Context::current_with_span(span);
|
||||
|
||||
let library_id = libraries::resolve_library_param_state(&app_state, query.library.as_deref())
|
||||
.ok()
|
||||
.flatten()
|
||||
@@ -472,12 +441,11 @@ async fn list_folder_pairs_handler(
|
||||
|
||||
let (dup_rows, all_paths) = {
|
||||
let mut dao = exif_dao.lock().expect("exif dao lock");
|
||||
let dup_rows = match dao.list_duplicates_exact(&span_context, library_id, include_resolved)
|
||||
{
|
||||
let dup_rows = match dao.list_duplicates_exact(library_id, include_resolved) {
|
||||
Ok(rows) => rows,
|
||||
Err(e) => return HttpResponse::InternalServerError().body(format!("{:?}", e)),
|
||||
};
|
||||
let all_paths = match dao.list_image_paths(&span_context, library_id, include_resolved) {
|
||||
let all_paths = match dao.list_image_paths(library_id, include_resolved) {
|
||||
Ok(rows) => rows,
|
||||
Err(e) => return HttpResponse::InternalServerError().body(format!("{:?}", e)),
|
||||
};
|
||||
|
||||
Reference in new issue
Block a user