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
+52
-144
@@ -9,17 +9,9 @@ use crate::database::{DbError, DbErrorKind, connect};
|
||||
use crate::otel::trace_db_call;
|
||||
|
||||
pub trait InsightDao: Sync + Send {
|
||||
fn store_insight(
|
||||
&mut self,
|
||||
context: &opentelemetry::Context,
|
||||
insight: InsertPhotoInsight,
|
||||
) -> Result<PhotoInsight, DbError>;
|
||||
fn store_insight(&mut self, insight: InsertPhotoInsight) -> Result<PhotoInsight, DbError>;
|
||||
|
||||
fn get_insight(
|
||||
&mut self,
|
||||
context: &opentelemetry::Context,
|
||||
file_path: &str,
|
||||
) -> Result<Option<PhotoInsight>, DbError>;
|
||||
fn get_insight(&mut self, file_path: &str) -> Result<Option<PhotoInsight>, DbError>;
|
||||
|
||||
/// Library-scoped variant of `get_insight`. The default `get_insight`
|
||||
/// finds any `is_current=true` row matching `file_path` across
|
||||
@@ -32,7 +24,6 @@ pub trait InsightDao: Sync + Send {
|
||||
/// one (chat / insight write paths always do).
|
||||
fn get_current_insight_for_library(
|
||||
&mut self,
|
||||
context: &opentelemetry::Context,
|
||||
library_id: i32,
|
||||
file_path: &str,
|
||||
) -> Result<Option<PhotoInsight>, DbError>;
|
||||
@@ -41,61 +32,29 @@ pub trait InsightDao: Sync + Send {
|
||||
/// `paths`. Used for content-hash sharing: the caller expands a
|
||||
/// single file into all rel_paths with the same content_hash, then
|
||||
/// asks here for any existing insight attached to any of them.
|
||||
fn get_insight_for_paths(
|
||||
&mut self,
|
||||
context: &opentelemetry::Context,
|
||||
paths: &[String],
|
||||
) -> Result<Option<PhotoInsight>, DbError>;
|
||||
fn get_insight_for_paths(&mut self, paths: &[String]) -> Result<Option<PhotoInsight>, DbError>;
|
||||
|
||||
fn get_insight_history(
|
||||
&mut self,
|
||||
context: &opentelemetry::Context,
|
||||
file_path: &str,
|
||||
) -> Result<Vec<PhotoInsight>, DbError>;
|
||||
fn get_insight_history(&mut self, file_path: &str) -> Result<Vec<PhotoInsight>, DbError>;
|
||||
|
||||
/// Fetch a single insight by primary key, regardless of `is_current`.
|
||||
/// Used by the few-shot injection flow where the caller picks specific
|
||||
/// historical insights (which may have been superseded) as training
|
||||
/// exemplars for a fresh generation.
|
||||
fn get_insight_by_id(
|
||||
&mut self,
|
||||
context: &opentelemetry::Context,
|
||||
insight_id: i32,
|
||||
) -> Result<Option<PhotoInsight>, DbError>;
|
||||
fn get_insight_by_id(&mut self, insight_id: i32) -> Result<Option<PhotoInsight>, DbError>;
|
||||
|
||||
fn delete_insight(
|
||||
&mut self,
|
||||
context: &opentelemetry::Context,
|
||||
file_path: &str,
|
||||
) -> Result<(), DbError>;
|
||||
fn delete_insight(&mut self, file_path: &str) -> Result<(), DbError>;
|
||||
|
||||
fn get_all_insights(
|
||||
&mut self,
|
||||
context: &opentelemetry::Context,
|
||||
) -> Result<Vec<PhotoInsight>, DbError>;
|
||||
fn get_all_insights(&mut self) -> Result<Vec<PhotoInsight>, DbError>;
|
||||
|
||||
fn rate_insight(
|
||||
&mut self,
|
||||
context: &opentelemetry::Context,
|
||||
file_path: &str,
|
||||
approved: bool,
|
||||
) -> Result<(), DbError>;
|
||||
fn rate_insight(&mut self, file_path: &str, approved: bool) -> Result<(), DbError>;
|
||||
|
||||
/// Rate a specific insight version by primary key, regardless of
|
||||
/// `is_current`. Used by the per-file history view to approve/reject
|
||||
/// previously generated (superseded) versions, which the path-based
|
||||
/// `rate_insight` (current row only) cannot reach.
|
||||
fn rate_insight_by_id(
|
||||
&mut self,
|
||||
context: &opentelemetry::Context,
|
||||
insight_id: i32,
|
||||
approved: bool,
|
||||
) -> Result<(), DbError>;
|
||||
fn rate_insight_by_id(&mut self, insight_id: i32, approved: bool) -> Result<(), DbError>;
|
||||
|
||||
fn get_approved_insights(
|
||||
&mut self,
|
||||
context: &opentelemetry::Context,
|
||||
) -> Result<Vec<PhotoInsight>, DbError>;
|
||||
fn get_approved_insights(&mut self) -> Result<Vec<PhotoInsight>, DbError>;
|
||||
|
||||
/// Replace the `training_messages` JSON blob on the current row for
|
||||
/// `(library_id, rel_path)`. Used by chat-turn append mode to persist
|
||||
@@ -104,7 +63,6 @@ pub trait InsightDao: Sync + Send {
|
||||
/// indicating a concurrent regenerate/reconcile flipped `is_current`).
|
||||
fn update_training_messages(
|
||||
&mut self,
|
||||
context: &opentelemetry::Context,
|
||||
library_id: i32,
|
||||
file_path: &str,
|
||||
training_messages_json: &str,
|
||||
@@ -136,12 +94,8 @@ impl SqliteInsightDao {
|
||||
}
|
||||
|
||||
impl InsightDao for SqliteInsightDao {
|
||||
fn store_insight(
|
||||
&mut self,
|
||||
context: &opentelemetry::Context,
|
||||
mut insight: InsertPhotoInsight,
|
||||
) -> Result<PhotoInsight, DbError> {
|
||||
trace_db_call(context, "insert", "store_insight", |_span| {
|
||||
fn store_insight(&mut self, mut insight: InsertPhotoInsight) -> Result<PhotoInsight, DbError> {
|
||||
trace_db_call("insert", "store_insight", |_span| {
|
||||
use schema::photo_insights::dsl::*;
|
||||
|
||||
let mut connection = self.connection.lock().expect("Unable to get InsightDao");
|
||||
@@ -193,12 +147,8 @@ impl InsightDao for SqliteInsightDao {
|
||||
})
|
||||
}
|
||||
|
||||
fn get_insight(
|
||||
&mut self,
|
||||
context: &opentelemetry::Context,
|
||||
path: &str,
|
||||
) -> Result<Option<PhotoInsight>, DbError> {
|
||||
trace_db_call(context, "query", "get_insight", |_span| {
|
||||
fn get_insight(&mut self, path: &str) -> Result<Option<PhotoInsight>, DbError> {
|
||||
trace_db_call("query", "get_insight", |_span| {
|
||||
use schema::photo_insights::dsl::*;
|
||||
|
||||
let mut connection = self.connection.lock().expect("Unable to get InsightDao");
|
||||
@@ -215,40 +165,30 @@ impl InsightDao for SqliteInsightDao {
|
||||
|
||||
fn get_current_insight_for_library(
|
||||
&mut self,
|
||||
context: &opentelemetry::Context,
|
||||
lib_id: i32,
|
||||
path: &str,
|
||||
) -> Result<Option<PhotoInsight>, DbError> {
|
||||
trace_db_call(
|
||||
context,
|
||||
"query",
|
||||
"get_current_insight_for_library",
|
||||
|_span| {
|
||||
use schema::photo_insights::dsl::*;
|
||||
trace_db_call("query", "get_current_insight_for_library", |_span| {
|
||||
use schema::photo_insights::dsl::*;
|
||||
|
||||
let mut connection = self.connection.lock().expect("Unable to get InsightDao");
|
||||
let mut connection = self.connection.lock().expect("Unable to get InsightDao");
|
||||
|
||||
photo_insights
|
||||
.filter(library_id.eq(lib_id))
|
||||
.filter(rel_path.eq(path))
|
||||
.filter(is_current.eq(true))
|
||||
.first::<PhotoInsight>(connection.deref_mut())
|
||||
.optional()
|
||||
.map_err(|e| anyhow::anyhow!("Query error: {}", e))
|
||||
},
|
||||
)
|
||||
photo_insights
|
||||
.filter(library_id.eq(lib_id))
|
||||
.filter(rel_path.eq(path))
|
||||
.filter(is_current.eq(true))
|
||||
.first::<PhotoInsight>(connection.deref_mut())
|
||||
.optional()
|
||||
.map_err(|e| anyhow::anyhow!("Query error: {}", e))
|
||||
})
|
||||
.map_err(|e| DbError::log(DbErrorKind::QueryError, e))
|
||||
}
|
||||
|
||||
fn get_insight_for_paths(
|
||||
&mut self,
|
||||
context: &opentelemetry::Context,
|
||||
paths: &[String],
|
||||
) -> Result<Option<PhotoInsight>, DbError> {
|
||||
fn get_insight_for_paths(&mut self, paths: &[String]) -> Result<Option<PhotoInsight>, DbError> {
|
||||
if paths.is_empty() {
|
||||
return Ok(None);
|
||||
}
|
||||
trace_db_call(context, "query", "get_insight_for_paths", |_span| {
|
||||
trace_db_call("query", "get_insight_for_paths", |_span| {
|
||||
use schema::photo_insights::dsl::*;
|
||||
|
||||
let mut connection = self.connection.lock().expect("Unable to get InsightDao");
|
||||
@@ -264,12 +204,8 @@ impl InsightDao for SqliteInsightDao {
|
||||
.map_err(|e| DbError::log(DbErrorKind::QueryError, e))
|
||||
}
|
||||
|
||||
fn get_insight_history(
|
||||
&mut self,
|
||||
context: &opentelemetry::Context,
|
||||
path: &str,
|
||||
) -> Result<Vec<PhotoInsight>, DbError> {
|
||||
trace_db_call(context, "query", "get_insight_history", |_span| {
|
||||
fn get_insight_history(&mut self, path: &str) -> Result<Vec<PhotoInsight>, DbError> {
|
||||
trace_db_call("query", "get_insight_history", |_span| {
|
||||
use schema::photo_insights::dsl::*;
|
||||
|
||||
let mut connection = self.connection.lock().expect("Unable to get InsightDao");
|
||||
@@ -283,12 +219,8 @@ impl InsightDao for SqliteInsightDao {
|
||||
.map_err(|e| DbError::log(DbErrorKind::QueryError, e))
|
||||
}
|
||||
|
||||
fn get_insight_by_id(
|
||||
&mut self,
|
||||
context: &opentelemetry::Context,
|
||||
insight_id: i32,
|
||||
) -> Result<Option<PhotoInsight>, DbError> {
|
||||
trace_db_call(context, "query", "get_insight_by_id", |_span| {
|
||||
fn get_insight_by_id(&mut self, insight_id: i32) -> Result<Option<PhotoInsight>, DbError> {
|
||||
trace_db_call("query", "get_insight_by_id", |_span| {
|
||||
use schema::photo_insights::dsl::*;
|
||||
|
||||
let mut connection = self.connection.lock().expect("Unable to get InsightDao");
|
||||
@@ -302,12 +234,8 @@ impl InsightDao for SqliteInsightDao {
|
||||
.map_err(|e| DbError::log(DbErrorKind::QueryError, e))
|
||||
}
|
||||
|
||||
fn delete_insight(
|
||||
&mut self,
|
||||
context: &opentelemetry::Context,
|
||||
path: &str,
|
||||
) -> Result<(), DbError> {
|
||||
trace_db_call(context, "delete", "delete_insight", |_span| {
|
||||
fn delete_insight(&mut self, path: &str) -> Result<(), DbError> {
|
||||
trace_db_call("delete", "delete_insight", |_span| {
|
||||
use schema::photo_insights::dsl::*;
|
||||
|
||||
let mut connection = self.connection.lock().expect("Unable to get InsightDao");
|
||||
@@ -320,11 +248,8 @@ impl InsightDao for SqliteInsightDao {
|
||||
.map_err(|e| DbError::log(DbErrorKind::QueryError, e))
|
||||
}
|
||||
|
||||
fn get_all_insights(
|
||||
&mut self,
|
||||
context: &opentelemetry::Context,
|
||||
) -> Result<Vec<PhotoInsight>, DbError> {
|
||||
trace_db_call(context, "query", "get_all_insights", |_span| {
|
||||
fn get_all_insights(&mut self) -> Result<Vec<PhotoInsight>, DbError> {
|
||||
trace_db_call("query", "get_all_insights", |_span| {
|
||||
use schema::photo_insights::dsl::*;
|
||||
|
||||
let mut connection = self.connection.lock().expect("Unable to get InsightDao");
|
||||
@@ -338,13 +263,8 @@ impl InsightDao for SqliteInsightDao {
|
||||
.map_err(|e| DbError::log(DbErrorKind::QueryError, e))
|
||||
}
|
||||
|
||||
fn rate_insight(
|
||||
&mut self,
|
||||
context: &opentelemetry::Context,
|
||||
path: &str,
|
||||
is_approved: bool,
|
||||
) -> Result<(), DbError> {
|
||||
trace_db_call(context, "update", "rate_insight", |_span| {
|
||||
fn rate_insight(&mut self, path: &str, is_approved: bool) -> Result<(), DbError> {
|
||||
trace_db_call("update", "rate_insight", |_span| {
|
||||
use schema::photo_insights::dsl::*;
|
||||
|
||||
let mut connection = self.connection.lock().expect("Unable to get InsightDao");
|
||||
@@ -362,13 +282,8 @@ impl InsightDao for SqliteInsightDao {
|
||||
.map_err(|e| DbError::log(DbErrorKind::UpdateError, e))
|
||||
}
|
||||
|
||||
fn rate_insight_by_id(
|
||||
&mut self,
|
||||
context: &opentelemetry::Context,
|
||||
target_id: i32,
|
||||
is_approved: bool,
|
||||
) -> Result<(), DbError> {
|
||||
trace_db_call(context, "update", "rate_insight_by_id", |_span| {
|
||||
fn rate_insight_by_id(&mut self, target_id: i32, is_approved: bool) -> Result<(), DbError> {
|
||||
trace_db_call("update", "rate_insight_by_id", |_span| {
|
||||
use schema::photo_insights::dsl::*;
|
||||
|
||||
let mut connection = self.connection.lock().expect("Unable to get InsightDao");
|
||||
@@ -382,11 +297,8 @@ impl InsightDao for SqliteInsightDao {
|
||||
.map_err(|e| DbError::log(DbErrorKind::UpdateError, e))
|
||||
}
|
||||
|
||||
fn get_approved_insights(
|
||||
&mut self,
|
||||
context: &opentelemetry::Context,
|
||||
) -> Result<Vec<PhotoInsight>, DbError> {
|
||||
trace_db_call(context, "query", "get_approved_insights", |_span| {
|
||||
fn get_approved_insights(&mut self) -> Result<Vec<PhotoInsight>, DbError> {
|
||||
trace_db_call("query", "get_approved_insights", |_span| {
|
||||
use schema::photo_insights::dsl::*;
|
||||
|
||||
let mut connection = self.connection.lock().expect("Unable to get InsightDao");
|
||||
@@ -403,12 +315,11 @@ impl InsightDao for SqliteInsightDao {
|
||||
|
||||
fn update_training_messages(
|
||||
&mut self,
|
||||
context: &opentelemetry::Context,
|
||||
lib_id: i32,
|
||||
path: &str,
|
||||
training_messages_json: &str,
|
||||
) -> Result<usize, DbError> {
|
||||
trace_db_call(context, "update", "update_training_messages", |_span| {
|
||||
trace_db_call("update", "update_training_messages", |_span| {
|
||||
use schema::photo_insights::dsl::*;
|
||||
|
||||
let mut connection = self.connection.lock().expect("Unable to get InsightDao");
|
||||
@@ -466,19 +377,17 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn get_insight_history_returns_all_versions_newest_first() {
|
||||
let cx = opentelemetry::Context::new();
|
||||
let mut dao = dao();
|
||||
|
||||
// store_insight flips prior rows to is_current=false, so three
|
||||
// generations for the same path leave a 3-row history.
|
||||
dao.store_insight(&cx, insert("a.jpg", 100, "m1")).unwrap();
|
||||
dao.store_insight(&cx, insert("a.jpg", 200, "m2")).unwrap();
|
||||
dao.store_insight(&cx, insert("a.jpg", 300, "m3")).unwrap();
|
||||
dao.store_insight(insert("a.jpg", 100, "m1")).unwrap();
|
||||
dao.store_insight(insert("a.jpg", 200, "m2")).unwrap();
|
||||
dao.store_insight(insert("a.jpg", 300, "m3")).unwrap();
|
||||
// A different path must not leak into the history.
|
||||
dao.store_insight(&cx, insert("b.jpg", 250, "other"))
|
||||
.unwrap();
|
||||
dao.store_insight(insert("b.jpg", 250, "other")).unwrap();
|
||||
|
||||
let history = dao.get_insight_history(&cx, "a.jpg").unwrap();
|
||||
let history = dao.get_insight_history("a.jpg").unwrap();
|
||||
assert_eq!(history.len(), 3);
|
||||
assert_eq!(
|
||||
history.iter().map(|i| i.generated_at).collect::<Vec<_>>(),
|
||||
@@ -493,20 +402,19 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn rate_insight_by_id_rates_only_the_targeted_version() {
|
||||
let cx = opentelemetry::Context::new();
|
||||
let mut dao = dao();
|
||||
|
||||
dao.store_insight(&cx, insert("a.jpg", 100, "m1")).unwrap();
|
||||
dao.store_insight(&cx, insert("a.jpg", 200, "m2")).unwrap();
|
||||
dao.store_insight(insert("a.jpg", 100, "m1")).unwrap();
|
||||
dao.store_insight(insert("a.jpg", 200, "m2")).unwrap();
|
||||
|
||||
// History is newest-first: [200 (current), 100 (superseded)].
|
||||
let history = dao.get_insight_history(&cx, "a.jpg").unwrap();
|
||||
let history = dao.get_insight_history("a.jpg").unwrap();
|
||||
let old_version = history.iter().find(|i| i.generated_at == 100).unwrap();
|
||||
assert!(!old_version.is_current);
|
||||
|
||||
dao.rate_insight_by_id(&cx, old_version.id, true).unwrap();
|
||||
dao.rate_insight_by_id(old_version.id, true).unwrap();
|
||||
|
||||
let history = dao.get_insight_history(&cx, "a.jpg").unwrap();
|
||||
let history = dao.get_insight_history("a.jpg").unwrap();
|
||||
let old = history.iter().find(|i| i.generated_at == 100).unwrap();
|
||||
let current = history.iter().find(|i| i.generated_at == 200).unwrap();
|
||||
assert_eq!(old.approved, Some(true), "targeted version is rated");
|
||||
|
||||
Reference in new issue
Block a user