feat(logging): add CRUD operation and timeline error logging
- Add info! logging for entity create/update/delete across all route handlers - Add entity deleted logging in define_delete_handler! macro - Log timeline insert errors instead of silently discarding them
This commit is contained in:
parent
4ab00dd630
commit
fe4cb4ffbb
10 changed files with 54 additions and 6 deletions
|
|
@ -3,6 +3,7 @@ use axum::extract::{Path, Query, State};
|
|||
use axum::http::{HeaderMap, StatusCode};
|
||||
use axum::response::{IntoResponse, Redirect, Response};
|
||||
use serde::Deserialize;
|
||||
use tracing::{info, warn};
|
||||
|
||||
use super::macros::{define_delete_handler, define_enriched_get_handler};
|
||||
use crate::application::auth::AuthenticatedUser;
|
||||
|
|
@ -80,6 +81,8 @@ pub(crate) async fn create_bag(
|
|||
.await
|
||||
.map_err(AppError::from)?;
|
||||
|
||||
info!(bag_id = %bag.id, roast = %roast.name, "bag created");
|
||||
|
||||
// Add timeline event
|
||||
let event = NewTimelineEvent {
|
||||
entity_type: "bag".to_string(),
|
||||
|
|
@ -102,7 +105,9 @@ pub(crate) async fn create_bag(
|
|||
roaster_slug: Some(roaster.slug.clone()),
|
||||
brew_data: None,
|
||||
};
|
||||
let _ = state.timeline_repo.insert(event).await;
|
||||
if let Err(err) = state.timeline_repo.insert(event).await {
|
||||
warn!(error = %err, entity_type = "bag", "failed to record timeline event");
|
||||
}
|
||||
|
||||
if is_datastar_request(&headers) {
|
||||
render_bag_list_fragment(state, request, search, true)
|
||||
|
|
@ -181,6 +186,8 @@ pub(crate) async fn update_bag(
|
|||
.await
|
||||
.map_err(AppError::from)?;
|
||||
|
||||
info!(%id, closed = ?update.closed, "bag updated");
|
||||
|
||||
if let Some(true) = update.closed {
|
||||
// Fetch roast and roaster for timeline event
|
||||
if let Ok(roast) = state.roast_repo.get(bag.roast_id).await
|
||||
|
|
@ -207,7 +214,9 @@ pub(crate) async fn update_bag(
|
|||
roaster_slug: Some(roaster.slug.clone()),
|
||||
brew_data: None,
|
||||
};
|
||||
let _ = state.timeline_repo.insert(event).await;
|
||||
if let Err(err) = state.timeline_repo.insert(event).await {
|
||||
warn!(error = %err, entity_type = "bag", "failed to record timeline event");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ use axum::extract::{Query, State};
|
|||
use axum::http::{HeaderMap, StatusCode};
|
||||
use axum::response::{IntoResponse, Redirect, Response};
|
||||
use serde::{Deserialize, Deserializer};
|
||||
use tracing::{info, warn};
|
||||
|
||||
use super::macros::{define_delete_handler, define_enriched_get_handler};
|
||||
use crate::application::auth::AuthenticatedUser;
|
||||
|
|
@ -207,6 +208,8 @@ pub(crate) async fn create_brew(
|
|||
.await
|
||||
.map_err(AppError::from)?;
|
||||
|
||||
info!(brew_id = %brew.id, "brew created");
|
||||
|
||||
// Fetch enriched brew details for timeline event
|
||||
let enriched = state
|
||||
.brew_repo
|
||||
|
|
@ -215,10 +218,13 @@ pub(crate) async fn create_brew(
|
|||
.map_err(AppError::from)?;
|
||||
|
||||
// Add timeline event
|
||||
let _ = state
|
||||
if let Err(err) = state
|
||||
.timeline_repo
|
||||
.insert(brew_timeline_event(&enriched))
|
||||
.await;
|
||||
.await
|
||||
{
|
||||
warn!(error = %err, entity_type = "brew", "failed to record timeline event");
|
||||
}
|
||||
|
||||
if is_datastar_request(&headers) {
|
||||
// Check if request came from timeline - return a script that redirects
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ use crate::domain::listing::{ListRequest, SortDirection};
|
|||
use crate::infrastructure::foursquare;
|
||||
use crate::presentation::web::templates::{CafeListTemplate, NearbyCafesFragment};
|
||||
use crate::presentation::web::views::{CafeView, ListNavigator, NearbyCafeView, Paginated};
|
||||
use tracing::info;
|
||||
|
||||
const CAFE_PAGE_PATH: &str = "/data?type=cafes";
|
||||
const CAFE_FRAGMENT_PATH: &str = "/data?type=cafes#cafe-list";
|
||||
|
|
@ -70,6 +71,8 @@ pub(crate) async fn create_cafe(
|
|||
.await
|
||||
.map_err(AppError::from)?;
|
||||
|
||||
info!(cafe_id = %cafe.id, name = %cafe.name, "cafe created");
|
||||
|
||||
if is_datastar_request(&headers) {
|
||||
render_cafe_list_fragment(state, request, search, true)
|
||||
.await
|
||||
|
|
@ -108,6 +111,7 @@ pub(crate) async fn update_cafe(
|
|||
.update(id, payload)
|
||||
.await
|
||||
.map_err(AppError::from)?;
|
||||
info!(%id, "cafe updated");
|
||||
Ok(Json(cafe))
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ use crate::domain::cafes::NewCafe;
|
|||
use crate::domain::cups::NewCup;
|
||||
use crate::domain::ids::{CafeId, RoastId};
|
||||
use crate::presentation::web::templates::CheckInTemplate;
|
||||
use tracing::info;
|
||||
|
||||
#[tracing::instrument(skip(state, cookies))]
|
||||
pub(crate) async fn checkin_page(
|
||||
|
|
@ -115,6 +116,8 @@ pub(crate) async fn submit_checkin(
|
|||
.await
|
||||
.map_err(AppError::from)?;
|
||||
|
||||
info!(cup_id = %cup.id, %cafe_id, "check-in recorded");
|
||||
|
||||
if is_datastar_request(&headers) {
|
||||
crate::application::routes::support::render_signals_json(&[]).map_err(ApiError::from)
|
||||
} else if matches!(source, PayloadSource::Form) {
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ use crate::domain::ids::CupId;
|
|||
use crate::domain::listing::{ListRequest, SortDirection};
|
||||
use crate::presentation::web::templates::CupListTemplate;
|
||||
use crate::presentation::web::views::{CupView, ListNavigator, Paginated};
|
||||
use tracing::info;
|
||||
|
||||
const CUP_PAGE_PATH: &str = "/data?type=cups";
|
||||
const CUP_FRAGMENT_PATH: &str = "/data?type=cups#cup-list";
|
||||
|
|
@ -60,6 +61,8 @@ pub(crate) async fn create_cup(
|
|||
.await
|
||||
.map_err(AppError::from)?;
|
||||
|
||||
info!(cup_id = %cup.id, "cup created");
|
||||
|
||||
if is_datastar_request(&headers) {
|
||||
render_cup_list_fragment(state, request, search, true)
|
||||
.await
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ use axum::extract::{Path, Query, State};
|
|||
use axum::http::{HeaderMap, StatusCode};
|
||||
use axum::response::{IntoResponse, Redirect, Response};
|
||||
use serde::Deserialize;
|
||||
use tracing::{info, warn};
|
||||
|
||||
use super::macros::{define_delete_handler, define_get_handler, define_list_fragment_renderer};
|
||||
use crate::application::auth::AuthenticatedUser;
|
||||
|
|
@ -63,6 +64,8 @@ pub(crate) async fn create_gear(
|
|||
.await
|
||||
.map_err(AppError::from)?;
|
||||
|
||||
info!(gear_id = %gear.id, make = %gear.make, model = %gear.model, "gear created");
|
||||
|
||||
// Add timeline event
|
||||
let event = NewTimelineEvent {
|
||||
entity_type: "gear".to_string(),
|
||||
|
|
@ -89,7 +92,9 @@ pub(crate) async fn create_gear(
|
|||
roaster_slug: None, // Gear is not related to roasters
|
||||
brew_data: None,
|
||||
};
|
||||
let _ = state.timeline_repo.insert(event).await;
|
||||
if let Err(err) = state.timeline_repo.insert(event).await {
|
||||
warn!(error = %err, entity_type = "gear", "failed to record timeline event");
|
||||
}
|
||||
|
||||
if is_datastar_request(&headers) {
|
||||
render_gear_list_fragment(state, request, search, true)
|
||||
|
|
@ -145,6 +150,8 @@ pub(crate) async fn update_gear(
|
|||
.await
|
||||
.map_err(AppError::from)?;
|
||||
|
||||
info!(%id, "gear updated");
|
||||
|
||||
if is_datastar_request(&headers) {
|
||||
render_gear_list_fragment(state, request, search, true)
|
||||
.await
|
||||
|
|
|
|||
|
|
@ -95,6 +95,8 @@ macro_rules! define_delete_handler {
|
|||
.await
|
||||
.map_err(crate::application::errors::AppError::from)?;
|
||||
|
||||
tracing::info!(%id, "entity deleted");
|
||||
|
||||
if crate::application::routes::support::is_datastar_request(&headers) {
|
||||
$render_fragment(state, request, search, true)
|
||||
.await
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ use crate::domain::roasters::{NewRoaster, Roaster, RoasterSortKey, UpdateRoaster
|
|||
use crate::infrastructure::ai::{self, ExtractionInput};
|
||||
use crate::presentation::web::templates::RoasterListTemplate;
|
||||
use crate::presentation::web::views::{ListNavigator, Paginated, RoasterView};
|
||||
use tracing::info;
|
||||
|
||||
const ROASTER_PAGE_PATH: &str = "/data?type=roasters";
|
||||
const ROASTER_FRAGMENT_PATH: &str = "/data?type=roasters#roaster-list";
|
||||
|
|
@ -71,6 +72,8 @@ pub(crate) async fn create_roaster(
|
|||
.await
|
||||
.map_err(AppError::from)?;
|
||||
|
||||
info!(roaster_id = %roaster.id, name = %roaster.name, "roaster created");
|
||||
|
||||
if is_datastar_request(&headers) {
|
||||
render_roaster_list_fragment(state, request, search, true)
|
||||
.await
|
||||
|
|
@ -107,6 +110,7 @@ pub(crate) async fn update_roaster(
|
|||
.update(id, payload)
|
||||
.await
|
||||
.map_err(AppError::from)?;
|
||||
info!(%id, "roaster updated");
|
||||
Ok(Json(roaster))
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ use crate::domain::roasts::{NewRoast, RoastSortKey, RoastWithRoaster, UpdateRoas
|
|||
use crate::infrastructure::ai::{self, ExtractionInput};
|
||||
use crate::presentation::web::templates::{RoastListTemplate, RoastOptionsTemplate};
|
||||
use crate::presentation::web::views::{ListNavigator, Paginated, RoastView};
|
||||
use tracing::info;
|
||||
|
||||
const ROAST_PAGE_PATH: &str = "/data?type=roasts";
|
||||
const ROAST_FRAGMENT_PATH: &str = "/data?type=roasts#roast-list";
|
||||
|
|
@ -69,6 +70,8 @@ pub(crate) async fn create_roast(
|
|||
.await
|
||||
.map_err(AppError::from)?;
|
||||
|
||||
info!(roast_id = %roast.id, name = %roast.name, "roast created");
|
||||
|
||||
if is_datastar_request(&headers) {
|
||||
render_roast_list_fragment(state, request, search, true)
|
||||
.await
|
||||
|
|
@ -169,6 +172,8 @@ pub(crate) async fn update_roast(
|
|||
.await
|
||||
.map_err(AppError::from)?;
|
||||
|
||||
info!(%id, "roast updated");
|
||||
|
||||
let enriched = state
|
||||
.roast_repo
|
||||
.get_with_roaster(id)
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ use axum::extract::State;
|
|||
use axum::http::{HeaderMap, StatusCode};
|
||||
use axum::response::{IntoResponse, Response};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tracing::{info, warn};
|
||||
|
||||
use crate::application::auth::AuthenticatedUser;
|
||||
use crate::application::errors::{ApiError, AppError};
|
||||
|
|
@ -285,6 +286,8 @@ pub(crate) async fn submit_scan(
|
|||
.await
|
||||
.map_err(AppError::from)?;
|
||||
|
||||
info!(roaster_id = %roaster.id, roast_id = %roast.id, roast_name = %roast.name, "scan created roast");
|
||||
|
||||
// Optionally create a bag
|
||||
let wants_bag = submission
|
||||
.open_bag
|
||||
|
|
@ -324,7 +327,9 @@ pub(crate) async fn submit_scan(
|
|||
roaster_slug: Some(roaster.slug.clone()),
|
||||
brew_data: None,
|
||||
};
|
||||
let _ = state.timeline_repo.insert(event).await;
|
||||
if let Err(err) = state.timeline_repo.insert(event).await {
|
||||
warn!(error = %err, entity_type = "bag", "failed to record timeline event");
|
||||
}
|
||||
}
|
||||
|
||||
let redirect = format!("/roasters/{}/roasts/{}", roaster.slug, roast.slug);
|
||||
|
|
|
|||
Loading…
Reference in a new issue