From 658aaced2a5888b726558c0149642d5e9ed03915 Mon Sep 17 00:00:00 2001 From: Peter Drier Date: Wed, 15 Apr 2026 04:08:37 +0200 Subject: [PATCH 1/3] Quiet expected error logs in profile/shift controllers - #497: Catch OperationCanceledException in ProfileController.Picture and return 499 without logging when the client aborted. - #499: Drop LogWarning wrapping ValidationException from UserEmailService.AddEmailAsync; user-input validation is expected. - #500: Drop LogWarning wrapping "cannot delete shift with signups" in ShiftAdminController.DeleteShift; this is an expected guardrail. Co-Authored-By: Claude Opus 4.6 (1M context) --- src/Humans.Web/Controllers/ProfileController.cs | 16 +++++++++++----- .../Controllers/ShiftAdminController.cs | 1 - 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/src/Humans.Web/Controllers/ProfileController.cs b/src/Humans.Web/Controllers/ProfileController.cs index 951703ed..1edd26d8 100644 --- a/src/Humans.Web/Controllers/ProfileController.cs +++ b/src/Humans.Web/Controllers/ProfileController.cs @@ -567,7 +567,6 @@ await _emailService.SendEmailVerificationAsync( } catch (ValidationException ex) { - _logger.LogWarning(ex, "Failed to add email address for user {UserId}", user.Id); ModelState.AddModelError(nameof(model.NewEmail), ex.Message); return View(nameof(Emails), await BuildEmailsViewModelAsync(user)); } @@ -971,12 +970,19 @@ public async Task DownloadData() [ResponseCache(Duration = 3600, Location = ResponseCacheLocation.Client)] public async Task Picture(Guid id, CancellationToken ct) { - var (data, contentType) = await _profileService.GetProfilePictureAsync(id, ct); + try + { + var (data, contentType) = await _profileService.GetProfilePictureAsync(id, ct); - if (data is null || string.IsNullOrEmpty(contentType)) - return NotFound(); + if (data is null || string.IsNullOrEmpty(contentType)) + return NotFound(); - return File(data, contentType); + return File(data, contentType); + } + catch (OperationCanceledException) when (ct.IsCancellationRequested) + { + return StatusCode(499); + } } // ─── View Another Profile ──────────────────────────────────────── diff --git a/src/Humans.Web/Controllers/ShiftAdminController.cs b/src/Humans.Web/Controllers/ShiftAdminController.cs index ef4472d2..2c797150 100644 --- a/src/Humans.Web/Controllers/ShiftAdminController.cs +++ b/src/Humans.Web/Controllers/ShiftAdminController.cs @@ -479,7 +479,6 @@ public async Task DeleteShift(string slug, Guid shiftId) } catch (InvalidOperationException ex) { - _logger.LogWarning(ex, "Failed to delete shift {ShiftId} in team {Slug}", shiftId, slug); SetError(ex.Message); } From 161dd82005cde05378e486b73f4f6a0859d280dc Mon Sep 17 00:00:00 2001 From: Peter Drier Date: Wed, 15 Apr 2026 14:32:47 +0200 Subject: [PATCH 2/3] Log expected problems at Information level instead of dropping them MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previous commit deleted the log calls entirely; we still want a record of these problems in the log — just without the exception object/stack trace and at a lower severity than Warning. - ProfileController.Picture: LogInformation on client-cancelled request - ProfileController.AddEmail: LogInformation on rejected email add - ShiftAdminController.DeleteShift: LogInformation on rejected delete Co-Authored-By: Claude Opus 4.6 (1M context) --- src/Humans.Web/Controllers/ProfileController.cs | 2 ++ src/Humans.Web/Controllers/ShiftAdminController.cs | 1 + 2 files changed, 3 insertions(+) diff --git a/src/Humans.Web/Controllers/ProfileController.cs b/src/Humans.Web/Controllers/ProfileController.cs index 1edd26d8..67f4f23b 100644 --- a/src/Humans.Web/Controllers/ProfileController.cs +++ b/src/Humans.Web/Controllers/ProfileController.cs @@ -567,6 +567,7 @@ await _emailService.SendEmailVerificationAsync( } catch (ValidationException ex) { + _logger.LogInformation("Rejected email add for user {UserId}: {Reason}", user.Id, ex.Message); ModelState.AddModelError(nameof(model.NewEmail), ex.Message); return View(nameof(Emails), await BuildEmailsViewModelAsync(user)); } @@ -981,6 +982,7 @@ public async Task Picture(Guid id, CancellationToken ct) } catch (OperationCanceledException) when (ct.IsCancellationRequested) { + _logger.LogInformation("Profile picture request for {ProfileId} was cancelled by the client", id); return StatusCode(499); } } diff --git a/src/Humans.Web/Controllers/ShiftAdminController.cs b/src/Humans.Web/Controllers/ShiftAdminController.cs index 2c797150..0311764d 100644 --- a/src/Humans.Web/Controllers/ShiftAdminController.cs +++ b/src/Humans.Web/Controllers/ShiftAdminController.cs @@ -479,6 +479,7 @@ public async Task DeleteShift(string slug, Guid shiftId) } catch (InvalidOperationException ex) { + _logger.LogInformation("Rejected shift delete for shift {ShiftId} in team {Slug}: {Reason}", shiftId, slug, ex.Message); SetError(ex.Message); } From ad0f93389ef2b4f291a71744d2862a43f95ae4a5 Mon Sep 17 00:00:00 2001 From: Peter Drier Date: Wed, 15 Apr 2026 14:39:13 +0200 Subject: [PATCH 3/3] Keep expected-problem logs at Warning severity Previous commit downgraded these to LogInformation, but the production log viewer only renders Warning and above, so Information-level logs are effectively invisible in prod. Keep LogWarning; the value is that the exception object is no longer attached (no stack trace spam) while the event is still visible to whoever watches the prod log viewer. Co-Authored-By: Claude Opus 4.6 (1M context) --- src/Humans.Web/Controllers/ProfileController.cs | 4 ++-- src/Humans.Web/Controllers/ShiftAdminController.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Humans.Web/Controllers/ProfileController.cs b/src/Humans.Web/Controllers/ProfileController.cs index 67f4f23b..b6434120 100644 --- a/src/Humans.Web/Controllers/ProfileController.cs +++ b/src/Humans.Web/Controllers/ProfileController.cs @@ -567,7 +567,7 @@ await _emailService.SendEmailVerificationAsync( } catch (ValidationException ex) { - _logger.LogInformation("Rejected email add for user {UserId}: {Reason}", user.Id, ex.Message); + _logger.LogWarning("Rejected email add for user {UserId}: {Reason}", user.Id, ex.Message); ModelState.AddModelError(nameof(model.NewEmail), ex.Message); return View(nameof(Emails), await BuildEmailsViewModelAsync(user)); } @@ -982,7 +982,7 @@ public async Task Picture(Guid id, CancellationToken ct) } catch (OperationCanceledException) when (ct.IsCancellationRequested) { - _logger.LogInformation("Profile picture request for {ProfileId} was cancelled by the client", id); + _logger.LogWarning("Profile picture request for {ProfileId} was cancelled by the client", id); return StatusCode(499); } } diff --git a/src/Humans.Web/Controllers/ShiftAdminController.cs b/src/Humans.Web/Controllers/ShiftAdminController.cs index 0311764d..257895d7 100644 --- a/src/Humans.Web/Controllers/ShiftAdminController.cs +++ b/src/Humans.Web/Controllers/ShiftAdminController.cs @@ -479,7 +479,7 @@ public async Task DeleteShift(string slug, Guid shiftId) } catch (InvalidOperationException ex) { - _logger.LogInformation("Rejected shift delete for shift {ShiftId} in team {Slug}: {Reason}", shiftId, slug, ex.Message); + _logger.LogWarning("Rejected shift delete for shift {ShiftId} in team {Slug}: {Reason}", shiftId, slug, ex.Message); SetError(ex.Message); }