diff --git a/src/otel.rs b/src/otel.rs index 595c834..e3e70e9 100644 --- a/src/otel.rs +++ b/src/otel.rs @@ -3,6 +3,7 @@ use actix_web::HttpRequest; use opentelemetry::global::BoxedTracer; use opentelemetry::{global, Context, KeyValue}; use opentelemetry::propagation::TextMapPropagator; +use opentelemetry::trace::Tracer; use opentelemetry_appender_log::OpenTelemetryLogBridge; use opentelemetry_otlp::WithExportConfig; use opentelemetry_sdk::logs::{BatchLogProcessor, SdkLoggerProvider}; @@ -13,6 +14,7 @@ pub fn global_tracer() -> BoxedTracer { global::tracer("image-server") } +#[allow(dead_code)] pub fn init_tracing() { let resources = Resource::builder() .with_attributes([ @@ -35,6 +37,7 @@ pub fn init_tracing() { global::set_tracer_provider(tracer_provider); } +#[allow(dead_code)] pub fn init_logs() { let otlp_exporter = opentelemetry_otlp::LogExporter::builder() .with_tonic() @@ -47,7 +50,7 @@ pub fn init_logs() { let resources = Resource::builder() .with_attributes([ KeyValue::new("service.name", "image-server"), - KeyValue::new("service.version", "1.0"), + KeyValue::new("service.version", env!("CARGO_PKG_VERSION")), ]) .build(); @@ -78,3 +81,16 @@ pub fn extract_context_from_request(req: &HttpRequest) -> Context { let propagator = TraceContextPropagator::new(); propagator.extract(&HeaderExtractor(req.headers())) } + +pub fn trace_db_call(operation: &str, query_type: &str, func: F) -> anyhow::Result +where F: FnOnce() -> anyhow::Result { + let tracer = global::tracer("db"); + let _span = tracer + .span_builder(format!("db.{}.{}", operation, query_type)) + .with_attributes(vec![ + KeyValue::new("db.operation", operation.to_string().clone()), + KeyValue::new("db.query_type", query_type.to_string().clone()), + ]).start(&tracer); + + func() +} \ No newline at end of file diff --git a/src/tags.rs b/src/tags.rs index 79632f4..a2cdeae 100644 --- a/src/tags.rs +++ b/src/tags.rs @@ -1,4 +1,5 @@ use crate::data::GetTagsRequest; +use crate::otel::trace_db_call; use crate::{connect, data::AddTagRequest, error::IntoHttpError, schema, Claims, ThumbnailRequest}; use actix_web::dev::{ServiceFactory, ServiceRequest}; use actix_web::{web, App, HttpResponse, Responder}; @@ -231,123 +232,133 @@ impl TagDao for SqliteTagDao { fn get_all_tags(&mut self, path: Option) -> anyhow::Result> { // select name, count(*) from tags join tagged_photo ON tags.id = tagged_photo.tag_id GROUP BY tags.name ORDER BY COUNT(*); - let path = path.map(|p| p + "%").unwrap_or("%".to_string()); - let (id, name, created_time) = tags::all_columns; - tags::table - .inner_join(tagged_photo::table) - .group_by(tags::id) - .select((count_star(), id, name, created_time)) - .filter(tagged_photo::photo_name.like(path)) - .get_results(&mut self.connection) - .map::, _>(|tags_with_count: Vec<(i64, i32, String, i64)>| { - tags_with_count - .iter() - .map(|tup| { - ( - tup.0, - Tag { - id: tup.1, - name: tup.2.clone(), - created_time: tup.3, - }, - ) - }) - .collect() - }) - .with_context(|| "Unable to get all tags") + trace_db_call("query", "get_all_tags", || { + let path = path.map(|p| p + "%").unwrap_or("%".to_string()); + let (id, name, created_time) = tags::all_columns; + tags::table + .inner_join(tagged_photo::table) + .group_by(tags::id) + .select((count_star(), id, name, created_time)) + .filter(tagged_photo::photo_name.like(path)) + .get_results(&mut self.connection) + .map::, _>(|tags_with_count: Vec<(i64, i32, String, i64)>| { + tags_with_count + .iter() + .map(|tup| { + ( + tup.0, + Tag { + id: tup.1, + name: tup.2.clone(), + created_time: tup.3, + }, + ) + }) + .collect() + }) + .with_context(|| "Unable to get all tags") + }) } fn get_tags_for_path(&mut self, path: &str) -> anyhow::Result> { - trace!("Getting Tags for path: {:?}", path); - tags::table - .left_join(tagged_photo::table) - .filter(tagged_photo::photo_name.eq(&path)) - .select((tags::id, tags::name, tags::created_time)) - .get_results::(self.connection.borrow_mut()) - .with_context(|| "Unable to get tags from Sqlite") + trace_db_call("query", "get_tags_for_path", || { + trace!("Getting Tags for path: {:?}", path); + tags::table + .left_join(tagged_photo::table) + .filter(tagged_photo::photo_name.eq(&path)) + .select((tags::id, tags::name, tags::created_time)) + .get_results::(self.connection.borrow_mut()) + .with_context(|| "Unable to get tags from Sqlite") + }) } fn create_tag(&mut self, name: &str) -> anyhow::Result { - diesel::insert_into(tags::table) - .values(InsertTag { - name: name.to_string(), - created_time: Utc::now().timestamp(), - }) - .execute(&mut self.connection) - .with_context(|| format!("Unable to insert tag {:?} in Sqlite", name)) - .and_then(|_| { - info!("Inserted tag: {:?}", name); - define_sql_function! { - fn last_insert_rowid() -> Integer; - } - diesel::select(last_insert_rowid()) - .get_result::(&mut self.connection) - .with_context(|| "Unable to get last inserted tag from Sqlite") - }) - .and_then(|id| { - debug!("Got id: {:?} for inserted tag: {:?}", id, name); - tags::table - .filter(tags::id.eq(id)) - .select((tags::id, tags::name, tags::created_time)) - .get_result::(self.connection.borrow_mut()) - .with_context(|| { - format!("Unable to get tagged photo with id: {:?} from Sqlite", id) - }) - }) + trace_db_call("insert", "create_tag", || { + diesel::insert_into(tags::table) + .values(InsertTag { + name: name.to_string(), + created_time: Utc::now().timestamp(), + }) + .execute(&mut self.connection) + .with_context(|| format!("Unable to insert tag {:?} in Sqlite", name)) + .and_then(|_| { + info!("Inserted tag: {:?}", name); + define_sql_function! { + fn last_insert_rowid() -> Integer; + } + diesel::select(last_insert_rowid()) + .get_result::(&mut self.connection) + .with_context(|| "Unable to get last inserted tag from Sqlite") + }) + .and_then(|id| { + debug!("Got id: {:?} for inserted tag: {:?}", id, name); + tags::table + .filter(tags::id.eq(id)) + .select((tags::id, tags::name, tags::created_time)) + .get_result::(self.connection.borrow_mut()) + .with_context(|| { + format!("Unable to get tagged photo with id: {:?} from Sqlite", id) + }) + }) + }) } fn remove_tag(&mut self, tag_name: &str, path: &str) -> anyhow::Result> { - tags::table - .filter(tags::name.eq(tag_name)) - .get_result::(self.connection.borrow_mut()) - .optional() - .with_context(|| format!("Unable to get tag '{}'", tag_name)) - .and_then(|tag| { - if let Some(tag) = tag { - diesel::delete( - tagged_photo::table - .filter(tagged_photo::tag_id.eq(tag.id)) - .filter(tagged_photo::photo_name.eq(path)), - ) - .execute(&mut self.connection) - .with_context(|| format!("Unable to delete tag: '{}'", &tag.name)) - .map(|_| Some(())) - } else { - info!("No tag found with name '{}'", tag_name); - Ok(None) - } - }) + trace_db_call("delete", "remove_tag", || { + tags::table + .filter(tags::name.eq(tag_name)) + .get_result::(self.connection.borrow_mut()) + .optional() + .with_context(|| format!("Unable to get tag '{}'", tag_name)) + .and_then(|tag| { + if let Some(tag) = tag { + diesel::delete( + tagged_photo::table + .filter(tagged_photo::tag_id.eq(tag.id)) + .filter(tagged_photo::photo_name.eq(path)), + ) + .execute(&mut self.connection) + .with_context(|| format!("Unable to delete tag: '{}'", &tag.name)) + .map(|_| Some(())) + } else { + info!("No tag found with name '{}'", tag_name); + Ok(None) + } + }) + }) } fn tag_file(&mut self, path: &str, tag_id: i32) -> anyhow::Result { - diesel::insert_into(tagged_photo::table) - .values(InsertTaggedPhoto { - tag_id, - photo_name: path.to_string(), - created_time: Utc::now().timestamp(), - }) - .execute(self.connection.borrow_mut()) - .with_context(|| format!("Unable to tag file {:?} in sqlite", path)) - .and_then(|_| { - info!("Inserted tagged photo: {:#} -> {:?}", tag_id, path); - define_sql_function! { - fn last_insert_rowid() -> diesel::sql_types::Integer; - } - diesel::select(last_insert_rowid()) - .get_result::(&mut self.connection) - .with_context(|| "Unable to get last inserted tag from Sqlite") - }) - .and_then(|tagged_id| { - tagged_photo::table - .find(tagged_id) - .first(self.connection.borrow_mut()) - .with_context(|| { - format!( - "Error getting inserted tagged photo with id: {:?}", - tagged_id - ) - }) - }) + trace_db_call("insert", "tag_file", || { + diesel::insert_into(tagged_photo::table) + .values(InsertTaggedPhoto { + tag_id, + photo_name: path.to_string(), + created_time: Utc::now().timestamp(), + }) + .execute(self.connection.borrow_mut()) + .with_context(|| format!("Unable to tag file {:?} in sqlite", path)) + .and_then(|_| { + info!("Inserted tagged photo: {:#} -> {:?}", tag_id, path); + define_sql_function! { + fn last_insert_rowid() -> diesel::sql_types::Integer; + } + diesel::select(last_insert_rowid()) + .get_result::(&mut self.connection) + .with_context(|| "Unable to get last inserted tag from Sqlite") + }) + .and_then(|tagged_id| { + tagged_photo::table + .find(tagged_id) + .first(self.connection.borrow_mut()) + .with_context(|| { + format!( + "Error getting inserted tagged photo with id: {:?}", + tagged_id + ) + }) + }) + }) } fn get_files_with_all_tag_ids( @@ -355,33 +366,35 @@ impl TagDao for SqliteTagDao { tag_ids: Vec, exclude_tag_ids: Vec, ) -> anyhow::Result> { - use diesel::dsl::*; + trace_db_call("query", "get_files_with_all_tags", || { + use diesel::dsl::*; - let exclude_subquery = tagged_photo::table - .filter(tagged_photo::tag_id.eq_any(exclude_tag_ids.clone())) - .select(tagged_photo::photo_name) - .into_boxed(); + let exclude_subquery = tagged_photo::table + .filter(tagged_photo::tag_id.eq_any(exclude_tag_ids.clone())) + .select(tagged_photo::photo_name) + .into_boxed(); - tagged_photo::table - .filter(tagged_photo::tag_id.eq_any(tag_ids.clone())) - .filter(tagged_photo::photo_name.ne_all(exclude_subquery)) - .group_by(tagged_photo::photo_name) - .select(( - tagged_photo::photo_name, - count_distinct(tagged_photo::tag_id), - )) - .having(count_distinct(tagged_photo::tag_id).ge(tag_ids.len() as i64)) - .get_results::<(String, i64)>(&mut self.connection) - .map(|results| { - results - .into_iter() - .map(|(file_name, tag_count)| FileWithTagCount { - file_name, - tag_count, - }) - .collect() - }) - .with_context(|| format!("Unable to get Tagged photos with ids: {:?}", tag_ids)) + tagged_photo::table + .filter(tagged_photo::tag_id.eq_any(tag_ids.clone())) + .filter(tagged_photo::photo_name.ne_all(exclude_subquery)) + .group_by(tagged_photo::photo_name) + .select(( + tagged_photo::photo_name, + count_distinct(tagged_photo::tag_id), + )) + .having(count_distinct(tagged_photo::tag_id).ge(tag_ids.len() as i64)) + .get_results::<(String, i64)>(&mut self.connection) + .map(|results| { + results + .into_iter() + .map(|(file_name, tag_count)| FileWithTagCount { + file_name, + tag_count, + }) + .collect() + }) + .with_context(|| format!("Unable to get Tagged photos with ids: {:?}", tag_ids)) + }) } fn get_files_with_any_tag_ids( @@ -389,22 +402,23 @@ impl TagDao for SqliteTagDao { tag_ids: Vec, exclude_tag_ids: Vec, ) -> anyhow::Result> { - use diesel::dsl::*; + trace_db_call("query", "get_files_with_any_tags", || { + use diesel::dsl::*; - let tag_ids_str = tag_ids - .iter() - .map(|id| id.to_string()) - .collect::>() - .join(","); + let tag_ids_str = tag_ids + .iter() + .map(|id| id.to_string()) + .collect::>() + .join(","); - let exclude_tag_ids_str = exclude_tag_ids - .iter() - .map(|id| id.to_string()) - .collect::>() - .join(","); + let exclude_tag_ids_str = exclude_tag_ids + .iter() + .map(|id| id.to_string()) + .collect::>() + .join(","); - let query = sql_query(format!( - r#" + let query = sql_query(format!( + r#" WITH filtered_photos AS ( SELECT DISTINCT photo_name FROM tagged_photo tp @@ -421,12 +435,13 @@ WITH filtered_photos AS ( FROM filtered_photos fp JOIN tagged_photo tp2 ON fp.photo_name = tp2.photo_name GROUP BY fp.photo_name"#, - tag_ids_str, exclude_tag_ids_str - )); + tag_ids_str, exclude_tag_ids_str + )); - // Execute the query: - let results = query.load::(&mut self.connection)?; - Ok(results) + // Execute the query: + let results = query.load::(&mut self.connection)?; + Ok(results) + }) } }