Skip to main content

max / makenotwork

audit Run 16 Phase 6: Observability Add #[tracing::instrument(skip_all, name = "...")] to the periodic scheduler job entry points (announcements, cleanup, integrity, mt_threads, synckit_warnings, webhooks) and to the eight custom-page dashboard handlers. These were the only significant functions without spans, so a slow or failing scheduled job / custom-page write could not be trace-correlated. Matches the per-handler tracing convention used everywhere else. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Author: Max Johnson <me@maxj.phd> · 2026-07-02 13:51 UTC
Commit: dfdf4b9631ba21a086d9cdbf85711c10f519dec8
Parent: 44c2178
5 files changed, +32 insertions, -0 deletions
@@ -91,6 +91,7 @@ struct Target {
91 91
92 92 // ── Route handlers ───────────────────────────────────────────────────────────
93 93
94 + #[tracing::instrument(skip_all, name = "custom_page::user_editor")]
94 95 pub async fn user_editor(
95 96 State(state): State<AppState>,
96 97 AuthUser(user): AuthUser,
@@ -100,6 +101,7 @@ pub async fn user_editor(
100 101 render_editor(&state, &session, target).await
101 102 }
102 103
104 + #[tracing::instrument(skip_all, name = "custom_page::user_save")]
103 105 pub async fn user_save(
104 106 State(state): State<AppState>,
105 107 AuthUser(user): AuthUser,
@@ -109,6 +111,7 @@ pub async fn user_save(
109 111 save(&state, target, form).await
110 112 }
111 113
114 + #[tracing::instrument(skip_all, name = "custom_page::user_autosave")]
112 115 pub async fn user_autosave(
113 116 State(state): State<AppState>,
114 117 AuthUser(user): AuthUser,
@@ -118,11 +121,13 @@ pub async fn user_autosave(
118 121 autosave(&state, target, form).await
119 122 }
120 123
124 + #[tracing::instrument(skip_all, name = "custom_page::user_reset")]
121 125 pub async fn user_reset(State(state): State<AppState>, AuthUser(user): AuthUser) -> Result<Response> {
122 126 let target = user_target(&state, user.id, user.username.as_ref()).await?;
123 127 reset(&state, target).await
124 128 }
125 129
130 + #[tracing::instrument(skip_all, name = "custom_page::project_editor")]
126 131 pub async fn project_editor(
127 132 State(state): State<AppState>,
128 133 AuthUser(user): AuthUser,
@@ -133,6 +138,7 @@ pub async fn project_editor(
133 138 render_editor(&state, &session, target).await
134 139 }
135 140
141 + #[tracing::instrument(skip_all, name = "custom_page::project_save")]
136 142 pub async fn project_save(
137 143 State(state): State<AppState>,
138 144 AuthUser(user): AuthUser,
@@ -143,6 +149,7 @@ pub async fn project_save(
143 149 save(&state, target, form).await
144 150 }
145 151
152 + #[tracing::instrument(skip_all, name = "custom_page::project_autosave")]
146 153 pub async fn project_autosave(
147 154 State(state): State<AppState>,
148 155 AuthUser(user): AuthUser,
@@ -153,6 +160,7 @@ pub async fn project_autosave(
153 160 autosave(&state, target, form).await
154 161 }
155 162
163 + #[tracing::instrument(skip_all, name = "custom_page::project_reset")]
156 164 pub async fn project_reset(
157 165 State(state): State<AppState>,
158 166 AuthUser(user): AuthUser,
@@ -40,6 +40,7 @@ where
40 40 /// Shared between the manual publish handler (`routes/api/items.rs`) and the
41 41 /// scheduler. Safe to call multiple times — `mark_release_announced`
42 42 /// is a no-op if the item was already announced.
43 + #[tracing::instrument(skip_all, name = "scheduler::send_release_announcements")]
43 44 pub async fn send_release_announcements(state: &AppState, item: &DbItem) {
44 45 if !db::items::mark_release_announced(&state.db, item.id)
45 46 .await
@@ -125,6 +126,7 @@ pub async fn send_release_announcements(state: &AppState, item: &DbItem) {
125 126 /// Shared between the blog post publish handlers and the scheduler.
126 127 /// Safe to call multiple times — `mark_blog_post_announced` is a no-op
127 128 /// if the post was already announced.
129 + #[tracing::instrument(skip_all, name = "scheduler::send_blog_post_announcements")]
128 130 pub async fn send_blog_post_announcements(state: &AppState, post: &DbBlogPost) {
129 131 if !db::blog_posts::mark_blog_post_announced(&state.db, post.id)
130 132 .await
@@ -234,6 +236,7 @@ impl OnboardingStep {
234 236 /// actual Postmark sends are spawned off the scheduler's lock-held connection
235 237 /// so a backlog of serial email I/O can't extend the advisory-lock hold time
236 238 /// (Run #14 MEDIUM, mirrors the release/blog announcement fan-out).
239 + #[tracing::instrument(skip_all, name = "scheduler::send_onboarding_emails")]
237 240 pub(super) async fn send_onboarding_emails(state: &AppState) {
238 241 // Step 1→2: profile tips (24h after welcome)
239 242 let next = OnboardingStep::ProfileTipsSent;
@@ -9,6 +9,7 @@ use crate::AppState;
9 9
10 10 /// Hourly: purge `scan_jobs` rows in a terminal state older than the
11 11 /// retention window. Queued/running rows are not touched.
12 + #[tracing::instrument(skip_all, name = "scheduler::purge_old_scan_jobs")]
12 13 pub(super) async fn purge_old_scan_jobs(state: &AppState) {
13 14 let window = chrono::Duration::days(constants::SCAN_JOB_RETENTION_DAYS as i64);
14 15 match db::scan_jobs::purge_old_terminal(&state.db, window).await {
@@ -66,6 +67,7 @@ async fn drain_cleanup_bounded(
66 67 }
67 68
68 69 /// Delete expired sandbox accounts and their S3 objects.
70 + #[tracing::instrument(skip_all, name = "scheduler::cleanup_sandbox_accounts")]
69 71 pub(super) async fn cleanup_sandbox_accounts(state: &AppState) {
70 72 let expired_ids = match db::users::get_expired_sandbox_ids(&state.db).await {
71 73 Ok(ids) => ids,
@@ -176,6 +178,7 @@ async fn cleanup_user_s3_and_delete(state: &AppState, user_id: db::UserId, event
176 178 /// Must be called before `delete_user` (which CASCADE-deletes the git_repos rows).
177 179 /// Best-effort: logs warnings on failure but does not block account deletion.
178 180 /// Runs blocking I/O on a dedicated thread to avoid stalling the Tokio runtime.
181 + #[tracing::instrument(skip_all, name = "scheduler::cleanup_git_repos_on_disk")]
179 182 pub(super) async fn cleanup_git_repos_on_disk(git_repos_path: &str, username: &str, user_id: db::UserId) {
180 183 let user_git_dir = std::path::Path::new(git_repos_path).join(username);
181 184 if user_git_dir.exists() {
@@ -200,6 +203,7 @@ pub(super) async fn cleanup_git_repos_on_disk(git_repos_path: &str, username: &s
200 203 /// (extremely large accounts), the next `get_expired_terminated_ids` query
201 204 /// excludes already-deleted rows; the worst-case race is one extra harmless
202 205 /// no-op call against an in-flight target.
206 + #[tracing::instrument(skip_all, name = "scheduler::delete_expired_terminated_accounts")]
203 207 pub(super) async fn delete_expired_terminated_accounts(state: &AppState) {
204 208 spawn_expired_account_cleanups(
205 209 state,
@@ -213,6 +217,7 @@ pub(super) async fn delete_expired_terminated_accounts(state: &AppState) {
213 217
214 218 /// Delete creator accounts whose 90-day content removal grace period has expired.
215 219 /// Off-lock spawn pattern — see `delete_expired_terminated_accounts`.
220 + #[tracing::instrument(skip_all, name = "scheduler::delete_expired_content_removal_accounts")]
216 221 pub(super) async fn delete_expired_content_removal_accounts(state: &AppState) {
217 222 spawn_expired_account_cleanups(
218 223 state,
@@ -267,6 +272,7 @@ async fn spawn_expired_account_cleanups(
267 272 }
268 273
269 274 /// Delete pending transactions older than 25 hours and release promo code reservations.
275 + #[tracing::instrument(skip_all, name = "scheduler::cleanup_stale_pending_transactions")]
270 276 pub(super) async fn cleanup_stale_pending_transactions(state: &AppState) {
271 277 let promo_ids = match db::transactions::cleanup_stale_pending(
272 278 &state.db,
@@ -301,6 +307,7 @@ pub(super) async fn cleanup_stale_pending_transactions(state: &AppState) {
301 307 }
302 308
303 309 /// NULL out IP addresses older than 30 days in user_sessions.
310 + #[tracing::instrument(skip_all, name = "scheduler::scrub_stale_ip_addresses")]
304 311 pub(super) async fn scrub_stale_ip_addresses(state: &AppState) {
305 312 let cutoff = chrono::Utc::now() - chrono::Duration::days(30);
306 313
@@ -331,6 +338,7 @@ pub(super) async fn scrub_stale_ip_addresses(state: &AppState) {
331 338 /// large allocation is transient, after a rare mass-delete event. Bounding it
332 339 /// per-tick means coupling a LIMIT on the key-gather to the same slice as the
333 340 /// CASCADE delete; deferred as low-value for a cron path. Revisit if it matters.
341 + #[tracing::instrument(skip_all, name = "scheduler::purge_expired_deleted_items")]
334 342 pub(super) async fn purge_expired_deleted_items(state: &AppState) {
335 343 // Collect S3 keys from items AND their versions before CASCADE delete destroys the data
336 344 let mut all_s3_keys: Vec<(String, String)> = Vec::new();
@@ -469,6 +477,7 @@ async fn delete_orphan_key_guarded(
469 477 }
470 478
471 479 /// Delete S3 objects from presigned uploads that were never confirmed (>24h old).
480 + #[tracing::instrument(skip_all, name = "scheduler::cleanup_orphaned_uploads")]
472 481 pub(super) async fn cleanup_orphaned_uploads(state: &AppState) {
473 482 let stale = match db::pending_uploads::get_stale_pending_uploads(
474 483 &state.db,
@@ -559,6 +568,7 @@ pub(super) async fn cleanup_orphaned_uploads(state: &AppState) {
559 568 }
560 569
561 570 /// Remove stale cart items (>30 days old) and items that became unavailable.
571 + #[tracing::instrument(skip_all, name = "scheduler::cleanup_cart_items")]
562 572 pub(super) async fn cleanup_cart_items(state: &AppState) {
563 573 match db::cart::cleanup_stale_cart_items(&state.db, chrono::Duration::days(30)).await {
564 574 Ok(n) if n > 0 => tracing::info!(removed = n, "cleaned up stale cart items"),
@@ -573,6 +583,7 @@ pub(super) async fn cleanup_cart_items(state: &AppState) {
573 583 }
574 584
575 585 /// Retry stale pending S3 deletions (older than 10 minutes, batch of 100).
586 + #[tracing::instrument(skip_all, name = "scheduler::retry_pending_s3_deletions")]
576 587 pub(super) async fn retry_pending_s3_deletions(state: &AppState) {
577 588 let stale = match db::pending_s3_deletions::get_stale_pending(
578 589 &state.db,
@@ -721,6 +732,7 @@ pub(super) async fn retry_pending_s3_deletions(state: &AppState) {
721 732 /// mirroring the scheduler's per-row guarded-delete path. Returns the number of
722 733 /// objects actually deleted. Exposed via `TestHarness::drain_s3_deletions`.
723 734 #[doc(hidden)]
735 + #[tracing::instrument(skip_all, name = "scheduler::drain_pending_s3_deletions_for_test")]
724 736 pub async fn drain_pending_s3_deletions_for_test(
725 737 pool: &sqlx::PgPool,
726 738 s3: &dyn crate::storage::StorageBackend,
@@ -5,6 +5,7 @@ use crate::db;
5 5 use crate::AppState;
6 6
7 7 /// Weekly storage drift correction — batch recalculates storage_used_bytes for all creators.
8 + #[tracing::instrument(skip_all, name = "scheduler::recalculate_all_storage_used")]
8 9 pub(super) async fn recalculate_all_storage_used(state: &AppState) {
9 10 match db::creator_tiers::recalculate_all_storage_batch(&state.db).await {
10 11 Ok(corrected) => {
@@ -19,6 +20,7 @@ pub(super) async fn recalculate_all_storage_used(state: &AppState) {
19 20 }
20 21
21 22 /// Enforce post-grace item hiding for creators whose cancellation grace period has expired.
23 + #[tracing::instrument(skip_all, name = "scheduler::enforce_post_grace_hiding")]
22 24 pub(super) async fn enforce_post_grace_hiding(state: &AppState) {
23 25 let user_ids = match db::creator_tiers::get_expired_grace_creators(&state.db).await {
24 26 Ok(ids) => ids,
@@ -55,6 +57,7 @@ pub(super) async fn enforce_post_grace_hiding(state: &AppState) {
55 57 }
56 58
57 59 /// Detect items where denormalized sales_count has drifted from actual transaction count.
60 + #[tracing::instrument(skip_all, name = "scheduler::check_sales_count_drift")]
58 61 pub(super) async fn check_sales_count_drift(state: &AppState) {
59 62 // Pre-filter to items that either have a non-zero recorded count OR have
60 63 // at least one completed transaction. The previous query GROUPed every
@@ -103,6 +106,7 @@ pub(super) async fn check_sales_count_drift(state: &AppState) {
103 106 }
104 107
105 108 /// Find subscriptions stuck in past_due for >7 days (possible missed webhook).
109 + #[tracing::instrument(skip_all, name = "scheduler::check_stale_subscriptions")]
106 110 pub(super) async fn check_stale_subscriptions(state: &AppState) {
107 111 let count: i64 = match sqlx::query_scalar(
108 112 r#"
@@ -138,6 +142,7 @@ pub(super) async fn check_stale_subscriptions(state: &AppState) {
138 142 }
139 143
140 144 /// Detect email bounce/complaint spikes (>10 suppressions in 24h).
145 + #[tracing::instrument(skip_all, name = "scheduler::check_email_bounce_spike")]
141 146 pub(super) async fn check_email_bounce_spike(state: &AppState) {
142 147 let count: i64 = match sqlx::query_scalar(
143 148 "SELECT COUNT(*) FROM email_suppressions WHERE created_at > NOW() - INTERVAL '24 hours'",
@@ -12,6 +12,7 @@ pub(super) fn is_webhook_dead(attempt: i32) -> bool {
12 12 }
13 13
14 14 /// Retry failed webhook events with exponential backoff.
15 + #[tracing::instrument(skip_all, name = "scheduler::retry_failed_webhooks")]
15 16 pub(super) async fn retry_failed_webhooks(state: &AppState) {
16 17 let events = match db::webhook_events::get_retryable_events(&state.db).await {
17 18 Ok(e) if e.is_empty() => return,
@@ -106,6 +107,7 @@ pub(super) async fn retry_failed_webhooks(state: &AppState) {
106 107 }
107 108
108 109 /// Alert the admin about pending refunds that have gone unmatched for >24 hours.
110 + #[tracing::instrument(skip_all, name = "scheduler::escalate_stale_refunds")]
109 111 pub(super) async fn escalate_stale_refunds(state: &AppState) {
110 112 let stale = match db::pending_refunds::get_stale_refunds(
111 113 &state.db,
@@ -198,6 +200,7 @@ const SETTLE_CREDITS_PER_TICK: usize = 50;
198 200 /// releases the claim for a later retry; a process death between claim and settle
199 201 /// leaves the row claimed-but-unsettled and is escalated by
200 202 /// [`escalate_stale_platform_credits`] rather than blindly retried.
203 + #[tracing::instrument(skip_all, name = "scheduler::settle_platform_credits")]
201 204 pub(super) async fn settle_platform_credits(state: &AppState) {
202 205 let Some(stripe) = state.stripe.as_ref() else { return };
203 206
@@ -251,6 +254,7 @@ pub(super) async fn settle_platform_credits(state: &AppState) {
251 254 /// Alert the admin about platform-funded credits claimed for settlement but never
252 255 /// completed (the crash window between claim and transfer). The transfer's
253 256 /// deterministic idempotency key means a human can safely re-trigger it.
257 + #[tracing::instrument(skip_all, name = "scheduler::escalate_stale_platform_credits")]
254 258 pub(super) async fn escalate_stale_platform_credits(state: &AppState) {
255 259 let stale = match db::platform_credits::get_stale_credits(&state.db, chrono::Duration::hours(24)).await {
256 260 Ok(s) if s.is_empty() => return,