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
+29
-65
@@ -8,6 +8,7 @@ use std::io::ErrorKind;
|
||||
use std::io::prelude::*;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::Mutex;
|
||||
use tracing_opentelemetry::OpenTelemetrySpanExt;
|
||||
|
||||
use actix_files::NamedFile;
|
||||
use actix_multipart as mp;
|
||||
@@ -19,7 +20,7 @@ use chrono::Utc;
|
||||
use futures::stream::StreamExt;
|
||||
use log::{debug, error, info, trace, warn};
|
||||
use opentelemetry::KeyValue;
|
||||
use opentelemetry::trace::{Span, Status, TraceContextExt, Tracer};
|
||||
use opentelemetry::trace::Status;
|
||||
use urlencoding::decode;
|
||||
|
||||
use crate::content_hash;
|
||||
@@ -34,11 +35,11 @@ use crate::file_types;
|
||||
use crate::files::{RefreshThumbnailsMessage, is_image_or_video, is_valid_full_path};
|
||||
use crate::libraries;
|
||||
use crate::memories;
|
||||
use crate::otel::{extract_context_from_request, global_tracer};
|
||||
use crate::perceptual_hash;
|
||||
use crate::state::AppState;
|
||||
|
||||
#[get("/image")]
|
||||
#[tracing::instrument(name = "get_image", skip_all)]
|
||||
pub async fn get_image(
|
||||
_claims: Claims,
|
||||
request: HttpRequest,
|
||||
@@ -46,10 +47,7 @@ pub async fn get_image(
|
||||
app_state: Data<AppState>,
|
||||
exif_dao: Data<Mutex<Box<dyn ExifDao>>>,
|
||||
) -> impl Responder {
|
||||
let tracer = global_tracer();
|
||||
let context = extract_context_from_request(&request);
|
||||
|
||||
let mut span = tracer.start_with_context("get_image", &context);
|
||||
let span = tracing::Span::current();
|
||||
|
||||
// Resolve library from query param; default to primary so clients that
|
||||
// don't yet send `library=` continue to work.
|
||||
@@ -110,7 +108,7 @@ pub async fn get_image(
|
||||
// rows that aren't hashed yet.
|
||||
let hash_large_path: Option<PathBuf> = {
|
||||
let mut dao = exif_dao.lock().expect("Unable to lock ExifDao");
|
||||
match dao.get_exif(&context, &relative_path_str) {
|
||||
match dao.get_exif(&relative_path_str) {
|
||||
Ok(Some(row)) => row
|
||||
.content_hash
|
||||
.as_deref()
|
||||
@@ -208,7 +206,7 @@ pub async fn get_image(
|
||||
|
||||
let hash_xlarge_path: Option<PathBuf> = {
|
||||
let mut dao = exif_dao.lock().expect("Unable to lock ExifDao");
|
||||
match dao.get_exif(&context, &relative_path_str) {
|
||||
match dao.get_exif(&relative_path_str) {
|
||||
Ok(Some(row)) => row
|
||||
.content_hash
|
||||
.as_deref()
|
||||
@@ -327,7 +325,7 @@ pub async fn get_image(
|
||||
// prevents needless 404s for already-warmed deployments.
|
||||
let hash_thumb_path: Option<PathBuf> = {
|
||||
let mut dao = exif_dao.lock().expect("Unable to lock ExifDao");
|
||||
match dao.get_exif(&context, &relative_path_str) {
|
||||
match dao.get_exif(&relative_path_str) {
|
||||
Ok(Some(row)) => row
|
||||
.content_hash
|
||||
.as_deref()
|
||||
@@ -479,18 +477,14 @@ async fn create_circular_thumbnail(
|
||||
}
|
||||
|
||||
#[get("/image/metadata")]
|
||||
#[tracing::instrument(name = "get_file_metadata", skip_all)]
|
||||
pub async fn get_file_metadata(
|
||||
_: Claims,
|
||||
request: HttpRequest,
|
||||
path: web::Query<ThumbnailRequest>,
|
||||
app_state: Data<AppState>,
|
||||
exif_dao: Data<Mutex<Box<dyn ExifDao>>>,
|
||||
) -> impl Responder {
|
||||
let tracer = global_tracer();
|
||||
let context = extract_context_from_request(&request);
|
||||
let mut span = tracer.start_with_context("get_file_metadata", &context);
|
||||
let span_context =
|
||||
opentelemetry::Context::new().with_remote_span_context(span.span_context().clone());
|
||||
let span = tracing::Span::current();
|
||||
|
||||
let library = libraries::resolve_library_param_state(&app_state, path.library.as_deref())
|
||||
.ok()
|
||||
@@ -531,7 +525,7 @@ pub async fn get_file_metadata(
|
||||
|
||||
// Query EXIF data if available
|
||||
if let Ok(mut dao) = exif_dao.lock()
|
||||
&& let Ok(Some(exif)) = dao.get_exif(&span_context, &path.path)
|
||||
&& let Ok(Some(exif)) = dao.get_exif(&path.path)
|
||||
{
|
||||
response.exif = Some(exif.into());
|
||||
}
|
||||
@@ -567,18 +561,14 @@ struct SetGpsRequest {
|
||||
}
|
||||
|
||||
#[post("/image/exif/gps")]
|
||||
#[tracing::instrument(name = "set_image_gps", skip_all)]
|
||||
pub async fn set_image_gps(
|
||||
_: Claims,
|
||||
request: HttpRequest,
|
||||
body: web::Json<SetGpsRequest>,
|
||||
app_state: Data<AppState>,
|
||||
exif_dao: Data<Mutex<Box<dyn ExifDao>>>,
|
||||
) -> impl Responder {
|
||||
let tracer = global_tracer();
|
||||
let context = extract_context_from_request(&request);
|
||||
let mut span = tracer.start_with_context("set_image_gps", &context);
|
||||
let span_context =
|
||||
opentelemetry::Context::new().with_remote_span_context(span.span_context().clone());
|
||||
let span = tracing::Span::current();
|
||||
|
||||
let library = libraries::resolve_library_param_state(&app_state, body.library.as_deref())
|
||||
.ok()
|
||||
@@ -693,10 +683,10 @@ pub async fn set_image_gps(
|
||||
// If the row doesn't exist yet (file isn't indexed for some reason),
|
||||
// insert instead so the GPS write is at least visible the moment
|
||||
// the watcher catches up.
|
||||
match dao.get_exif(&span_context, &normalized_path) {
|
||||
Ok(Some(_)) => dao.update_exif(&span_context, insert_exif),
|
||||
Ok(None) => dao.store_exif(&span_context, insert_exif),
|
||||
Err(_) => dao.update_exif(&span_context, insert_exif),
|
||||
match dao.get_exif(&normalized_path) {
|
||||
Ok(Some(_)) => dao.update_exif(insert_exif),
|
||||
Ok(None) => dao.store_exif(insert_exif),
|
||||
Err(_) => dao.update_exif(insert_exif),
|
||||
}
|
||||
};
|
||||
|
||||
@@ -744,15 +734,13 @@ pub async fn set_image_gps(
|
||||
/// to 503 when exiftool isn't installed (deployer guidance is the same
|
||||
/// as for the RAW preview pipeline: install exiftool for full coverage).
|
||||
#[get("/image/exif/full")]
|
||||
#[tracing::instrument(name = "get_full_exif", skip_all)]
|
||||
pub async fn get_full_exif(
|
||||
_: Claims,
|
||||
request: HttpRequest,
|
||||
path: web::Query<ThumbnailRequest>,
|
||||
app_state: Data<AppState>,
|
||||
) -> impl Responder {
|
||||
let tracer = global_tracer();
|
||||
let context = extract_context_from_request(&request);
|
||||
let mut span = tracer.start_with_context("get_full_exif", &context);
|
||||
let span = tracing::Span::current();
|
||||
|
||||
let library = libraries::resolve_library_param_state(&app_state, path.library.as_deref())
|
||||
.ok()
|
||||
@@ -887,18 +875,14 @@ fn build_metadata_response_for_date_mutation(
|
||||
}
|
||||
|
||||
#[post("/image/exif/date")]
|
||||
#[tracing::instrument(name = "set_image_date", skip_all)]
|
||||
pub async fn set_image_date(
|
||||
_: Claims,
|
||||
request: HttpRequest,
|
||||
body: web::Json<SetDateRequest>,
|
||||
app_state: Data<AppState>,
|
||||
exif_dao: Data<Mutex<Box<dyn ExifDao>>>,
|
||||
) -> impl Responder {
|
||||
let tracer = global_tracer();
|
||||
let context = extract_context_from_request(&request);
|
||||
let mut span = tracer.start_with_context("set_image_date", &context);
|
||||
let span_context =
|
||||
opentelemetry::Context::new().with_remote_span_context(span.span_context().clone());
|
||||
let span = tracing::Span::current();
|
||||
|
||||
let library = match libraries::resolve_library_param_state(&app_state, body.library.as_deref())
|
||||
{
|
||||
@@ -922,7 +906,7 @@ pub async fn set_image_date(
|
||||
.user_ai_prefs_dao
|
||||
.lock()
|
||||
.expect("Unable to lock UserAiPrefsDao");
|
||||
match prefs_dao.get_prefs(&span_context) {
|
||||
match prefs_dao.get_prefs() {
|
||||
Ok(Some(prefs)) => crate::camera_tz::display_zone_for(prefs.display_tz.as_deref()),
|
||||
_ => crate::camera_tz::display_zone_for(None),
|
||||
}
|
||||
@@ -930,13 +914,7 @@ pub async fn set_image_date(
|
||||
|
||||
let updated = {
|
||||
let mut dao = exif_dao.lock().expect("Unable to lock ExifDao");
|
||||
dao.set_manual_date_taken(
|
||||
&span_context,
|
||||
library.id,
|
||||
&normalized_path,
|
||||
body.date_taken,
|
||||
&display_tz,
|
||||
)
|
||||
dao.set_manual_date_taken(library.id, &normalized_path, body.date_taken, &display_tz)
|
||||
};
|
||||
|
||||
match updated {
|
||||
@@ -961,18 +939,14 @@ pub async fn set_image_date(
|
||||
}
|
||||
|
||||
#[post("/image/exif/date/clear")]
|
||||
#[tracing::instrument(name = "clear_image_date", skip_all)]
|
||||
pub async fn clear_image_date(
|
||||
_: Claims,
|
||||
request: HttpRequest,
|
||||
body: web::Json<ClearDateRequest>,
|
||||
app_state: Data<AppState>,
|
||||
exif_dao: Data<Mutex<Box<dyn ExifDao>>>,
|
||||
) -> impl Responder {
|
||||
let tracer = global_tracer();
|
||||
let context = extract_context_from_request(&request);
|
||||
let mut span = tracer.start_with_context("clear_image_date", &context);
|
||||
let span_context =
|
||||
opentelemetry::Context::new().with_remote_span_context(span.span_context().clone());
|
||||
let span = tracing::Span::current();
|
||||
|
||||
let library = match libraries::resolve_library_param_state(&app_state, body.library.as_deref())
|
||||
{
|
||||
@@ -988,7 +962,7 @@ pub async fn clear_image_date(
|
||||
|
||||
let updated = {
|
||||
let mut dao = exif_dao.lock().expect("Unable to lock ExifDao");
|
||||
dao.clear_manual_date_taken(&span_context, library.id, &normalized_path)
|
||||
dao.clear_manual_date_taken(library.id, &normalized_path)
|
||||
};
|
||||
|
||||
match updated {
|
||||
@@ -1018,19 +992,15 @@ struct UploadQuery {
|
||||
}
|
||||
|
||||
#[post("/image")]
|
||||
#[tracing::instrument(name = "upload_image", skip_all)]
|
||||
pub async fn upload_image(
|
||||
_: Claims,
|
||||
request: HttpRequest,
|
||||
query: web::Query<UploadQuery>,
|
||||
mut payload: mp::Multipart,
|
||||
app_state: Data<AppState>,
|
||||
exif_dao: Data<Mutex<Box<dyn ExifDao>>>,
|
||||
) -> impl Responder {
|
||||
let tracer = global_tracer();
|
||||
let context = extract_context_from_request(&request);
|
||||
let mut span = tracer.start_with_context("upload_image", &context);
|
||||
let span_context =
|
||||
opentelemetry::Context::new().with_remote_span_context(span.span_context().clone());
|
||||
let span = tracing::Span::current();
|
||||
|
||||
// Resolve the optional library selector. Absent → primary library
|
||||
// (backwards-compatible with clients that don't yet send `library=`).
|
||||
@@ -1097,7 +1067,7 @@ pub async fn upload_image(
|
||||
.to_string();
|
||||
{
|
||||
let mut dao = exif_dao.lock().expect("Unable to lock ExifDao");
|
||||
if let Ok(Some(existing)) = dao.find_by_content_hash(&span_context, &upload_hash)
|
||||
if let Ok(Some(existing)) = dao.find_by_content_hash(&upload_hash)
|
||||
&& existing.duplicate_of_hash.is_none()
|
||||
{
|
||||
let library_name = libraries::load_all(&mut crate::database::connect())
|
||||
@@ -1116,12 +1086,6 @@ pub async fn upload_image(
|
||||
}
|
||||
}
|
||||
|
||||
let context =
|
||||
opentelemetry::Context::new().with_remote_span_context(span.span_context().clone());
|
||||
tracer
|
||||
.span_builder("file write")
|
||||
.start_with_context(&tracer, &context);
|
||||
|
||||
let uploaded_path = if !full_path.is_file() && is_image_or_video(&full_path) {
|
||||
let mut file = File::create(&full_path).unwrap();
|
||||
file.write_all(&file_content).unwrap();
|
||||
@@ -1212,7 +1176,7 @@ pub async fn upload_image(
|
||||
};
|
||||
|
||||
if let Ok(mut dao) = exif_dao.lock() {
|
||||
if let Err(e) = dao.store_exif(&span_context, insert_exif) {
|
||||
if let Err(e) = dao.store_exif(insert_exif) {
|
||||
error!("Failed to store EXIF data for {}: {:?}", relative_path, e);
|
||||
} else {
|
||||
debug!("EXIF data stored for {}", relative_path);
|
||||
|
||||
Reference in new issue
Block a user