feat: structured logging infrastructure with Serilog-style timestamps #15

Merged
bjoern merged 1 commit from feat/structured-logging into main 2026-06-29 13:00:48 +02:00
Member

Summary

Adds a structured ILogger<T> logging infrastructure with a Serilog-style console timestamp, plus request logging middleware and domain operation logging across Infrastructure and RestAdapter.

Console output format

Every log line now starts with a full timestamp including timezone offset:

2026-06-29 17:32:14.123+02:00 - [INF] DoujinManager.RestAdapter.Middleware.RequestLoggingMiddleware - GET /api/doujins → 200 (12 ms)
2026-06-29 17:32:14.456+02:00 - [INF] DoujinManager.Infrastructure.Services.ImageService - Creating image a3f1... (image/jpeg, 1920x1080, 485230 bytes)

What changed

1. Console formatter (Program.cs + appsettings.json)

  • SimpleConsoleFormatter with TimestampFormat = "yyyy-MM-dd HH:mm:ss.fffzzz "
  • Single-line output, UTC disabled (local timezone shown in offset)
  • appsettings.json: added Microsoft.EntityFrameworkCore: Warning and DoujinManager: Information to keep the signal-to-noise ratio high

2. Request logging middleware (new)

RequestLoggingMiddleware — logs every HTTP request with method, path, status code, and elapsed milliseconds:

Status code range Log level
/health, /scalar, /openapi Debug (keeps stdout clean in production)
5xx Error
4xx Warning
2xx, 3xx Information

Registered as the outermost middleware (before GlobalExceptionMiddleware) so it always captures the final status code.

3. Infrastructure domain logging

Added ILogger<T> to the classes with meaningful state transitions:

Class What gets logged
ImageService Image creation (id, format, dimensions, size), serve (Debug), delete
DoujinService Create, update, delete with doujin ID
BackupService Backup created (path), backup deleted (filename)
UploadPagesUseCase Upload start (file count), success, failure + rollback + cleanup count
UploadZipPagesUseCase Extract start, success, failure + rollback + cleanup count
DeletePageUseCase Page deletion + image cleanup, warning on best-effort cleanup failure

All messages use structured logging placeholders ({DoujinId}, {VariantId}, etc.) so they're queryable in log aggregators.

4. Package reference

Added Microsoft.Extensions.Logging.Abstractions to ApplicationCore (via CPM) so ILogger<T> is available across all layers without pulling in the full logging implementation.

Tests

4 new RequestLoggingMiddlewareTests:

  • ✅ SuccessfulRequest_LogsAtInformation_WithStructuredFields — verifies GET /api/ok → 200 at Information with method/path/status/ms
  • ✅ NotFoundRequest_LogsAtWarning — verifies 404 → Warning
  • ✅ ServerError_LogsAtError — verifies 500 → Error
  • ✅ HealthEndpoint_LogsAtDebugLevel — verifies /health routes to Debug (direct middleware unit test)

Updated ImageUseCaseTests to pass NullLogger<T>.Instance to the new constructor params.

All 238 tests pass (13 ApplicationCore + 43 Infrastructure + 1 Integration + 181 RestAdapter).

Files changed

 12 files changed, 302 insertions(+), 18 deletions(-)

2 new files:

  • RestAdapter/Middleware/RequestLoggingMiddleware.cs
  • RestAdapter.Tests/Middleware/RequestLoggingMiddlewareTests.cs

10 modified:

  • Program.cs, appsettings.json, Directory.Packages.props
  • ApplicationCore.csproj (added logging abstractions package ref)
  • RestAdapterExtensions.cs (register new middleware)
  • ImageService.cs, DoujinService.cs, BackupService.cs
  • ImageUseCases.cs (Upload/Zip/DeletePage use cases)
  • ImageUseCaseTests.cs (constructor param updates)
## Summary Adds a structured `ILogger<T>` logging infrastructure with a Serilog-style console timestamp, plus request logging middleware and domain operation logging across Infrastructure and RestAdapter. ## Console output format Every log line now starts with a full timestamp including timezone offset: ``` 2026-06-29 17:32:14.123+02:00 - [INF] DoujinManager.RestAdapter.Middleware.RequestLoggingMiddleware - GET /api/doujins → 200 (12 ms) 2026-06-29 17:32:14.456+02:00 - [INF] DoujinManager.Infrastructure.Services.ImageService - Creating image a3f1... (image/jpeg, 1920x1080, 485230 bytes) ``` ## What changed ### 1. Console formatter (`Program.cs` + `appsettings.json`) - `SimpleConsoleFormatter` with `TimestampFormat = "yyyy-MM-dd HH:mm:ss.fffzzz "` - Single-line output, UTC disabled (local timezone shown in offset) - `appsettings.json`: added `Microsoft.EntityFrameworkCore: Warning` and `DoujinManager: Information` to keep the signal-to-noise ratio high ### 2. Request logging middleware (new) `RequestLoggingMiddleware` — logs every HTTP request with method, path, status code, and elapsed milliseconds: | Status code range | Log level | |---|---| | `/health`, `/scalar`, `/openapi` | Debug (keeps stdout clean in production) | | 5xx | Error | | 4xx | Warning | | 2xx, 3xx | Information | Registered as the outermost middleware (before `GlobalExceptionMiddleware`) so it always captures the final status code. ### 3. Infrastructure domain logging Added `ILogger<T>` to the classes with meaningful state transitions: | Class | What gets logged | |---|---| | `ImageService` | Image creation (id, format, dimensions, size), serve (Debug), delete | | `DoujinService` | Create, update, delete with doujin ID | | `BackupService` | Backup created (path), backup deleted (filename) | | `UploadPagesUseCase` | Upload start (file count), success, failure + rollback + cleanup count | | `UploadZipPagesUseCase` | Extract start, success, failure + rollback + cleanup count | | `DeletePageUseCase` | Page deletion + image cleanup, warning on best-effort cleanup failure | All messages use structured logging placeholders (`{DoujinId}`, `{VariantId}`, etc.) so they're queryable in log aggregators. ### 4. Package reference Added `Microsoft.Extensions.Logging.Abstractions` to `ApplicationCore` (via CPM) so `ILogger<T>` is available across all layers without pulling in the full logging implementation. ## Tests 4 new `RequestLoggingMiddlewareTests`: - ✅ `SuccessfulRequest_LogsAtInformation_WithStructuredFields` — verifies GET /api/ok → 200 at Information with method/path/status/ms - ✅ `NotFoundRequest_LogsAtWarning` — verifies 404 → Warning - ✅ `ServerError_LogsAtError` — verifies 500 → Error - ✅ `HealthEndpoint_LogsAtDebugLevel` — verifies /health routes to Debug (direct middleware unit test) Updated `ImageUseCaseTests` to pass `NullLogger<T>.Instance` to the new constructor params. **All 238 tests pass** (13 ApplicationCore + 43 Infrastructure + 1 Integration + 181 RestAdapter). ## Files changed ``` 12 files changed, 302 insertions(+), 18 deletions(-) ``` 2 new files: - `RestAdapter/Middleware/RequestLoggingMiddleware.cs` - `RestAdapter.Tests/Middleware/RequestLoggingMiddlewareTests.cs` 10 modified: - `Program.cs`, `appsettings.json`, `Directory.Packages.props` - `ApplicationCore.csproj` (added logging abstractions package ref) - `RestAdapterExtensions.cs` (register new middleware) - `ImageService.cs`, `DoujinService.cs`, `BackupService.cs` - `ImageUseCases.cs` (Upload/Zip/DeletePage use cases) - `ImageUseCaseTests.cs` (constructor param updates)
feat: add structured logging with Serilog-style timestamp format
All checks were successful
CI / build (pull_request) Successful in 16s
CI / test (pull_request) Successful in 34s
a0fca66524
Replaces the default ASP.NET Core console logger with SimpleConsoleFormatter
configured for a Serilog-style timestamp:
  2026-06-29 17:32:14.123+02:00 - [INF] Category - Message

Changes:
- Program.cs: configure SimpleConsole with TimestampFormat yyyy-MM-dd HH:mm:ss.fffzzz
- appsettings.json: add DoujinManager namespace + EF Core log level filters
- RequestLoggingMiddleware: new middleware that logs every HTTP request with
  method, path, status code, and elapsed ms. Routes health/docs to Debug,
  4xx to Warning, 5xx to Error, 2xx/3xx to Information.
- Infrastructure: add ILogger<T> to ImageService, DoujinService, BackupService,
  UploadPagesUseCase, UploadZipPagesUseCase, DeletePageUseCase. Log at key
  operation points (create, update, delete, transaction commit/rollback,
  compensating cleanup).
- CPM: add Microsoft.Extensions.Logging.Abstractions to ApplicationCore so
  the logging abstraction is available across all layers.
- Tests: 4 new RequestLoggingMiddlewareTests covering Information/Warning/
  Error/Debug log-level routing. Updated existing use case tests to pass
  NullLogger<T>.Instance to new constructor params.

All 238 tests pass.

Summary

Summary
Generated on: 06/29/2026 - 10:46:59
Coverage date: 06/29/2026 - 10:46:45 - 06/29/2026 - 10:46:57
Parser: MultiReport (4x Cobertura)
Assemblies: 4
Classes: 208
Files: 94
Line coverage: 83.5% (4239 of 5076)
Covered lines: 4239
Uncovered lines: 837
Coverable lines: 5076
Total lines: 8776
Branch coverage: 49.4% (368 of 744)
Covered branches: 368
Total branches: 744
Method coverage: Feature is only available for sponsors

Coverage

DoujinManager.ApplicationCore - 84.8%
Name Line Branch
DoujinManager.ApplicationCore 84.8% ****
DoujinManager.ApplicationCore.Entities.Chapter 87.5%
DoujinManager.ApplicationCore.Entities.Circle 100%
DoujinManager.ApplicationCore.Entities.Doujin 100%
DoujinManager.ApplicationCore.Entities.DoujinCircle 75%
DoujinManager.ApplicationCore.Entities.DoujinPerson 80%
DoujinManager.ApplicationCore.Entities.DoujinTag 75%
DoujinManager.ApplicationCore.Entities.ImageFile 100%
DoujinManager.ApplicationCore.Entities.Page 80%
DoujinManager.ApplicationCore.Entities.Person 100%
DoujinManager.ApplicationCore.Entities.Tag 100%
DoujinManager.ApplicationCore.Entities.Title 83.3%
DoujinManager.ApplicationCore.Entities.Variant 91.6%
DoujinManager.ApplicationCore.Ids.ChapterId 66.6%
DoujinManager.ApplicationCore.Ids.CircleId 66.6%
DoujinManager.ApplicationCore.Ids.DoujinId 100%
DoujinManager.ApplicationCore.Ids.ImageFileId 66.6%
DoujinManager.ApplicationCore.Ids.PageId 66.6%
DoujinManager.ApplicationCore.Ids.PersonId 66.6%
DoujinManager.ApplicationCore.Ids.TagId 66.6%
DoujinManager.ApplicationCore.Ids.TitleId 66.6%
DoujinManager.ApplicationCore.Ids.VariantId 66.6%
DoujinManager.ApplicationCore.Ports.ExtractedImage 100%
DoujinManager.ApplicationCore.Ports.ImageInspection 100%
DoujinManager.ApplicationCore.Services.BackupInfo 100%
DoujinManager.ApplicationCore.Services.ITagService 100%
DoujinManager.ApplicationCore.Services.ServiceResult 100%
DoujinManager.ApplicationCore.Services.ServiceResult`1 33.3%
DoujinManager.ApplicationCore.Services.VoidResult 88.8%
DoujinManager.ApplicationCore.UseCases.AddTitleCommand 0%
DoujinManager.ApplicationCore.UseCases.AssignCircleCommand 100%
DoujinManager.ApplicationCore.UseCases.AssignPersonCommand 100%
DoujinManager.ApplicationCore.UseCases.AssignTagCommand 100%
DoujinManager.ApplicationCore.UseCases.CreateChapterCommand 100%
DoujinManager.ApplicationCore.UseCases.CreateCircleCommand 100%
DoujinManager.ApplicationCore.UseCases.CreateDoujinCommand 100%
DoujinManager.ApplicationCore.UseCases.CreatePersonCommand 100%
DoujinManager.ApplicationCore.UseCases.CreateTagCommand 100%
DoujinManager.ApplicationCore.UseCases.CreateTitleCommand 100%
DoujinManager.ApplicationCore.UseCases.CreateVariantCommand 100%
DoujinManager.ApplicationCore.UseCases.DeleteChapterCommand 0%
DoujinManager.ApplicationCore.UseCases.DeleteDoujinCommand 100%
DoujinManager.ApplicationCore.UseCases.DeletePageCommand 100%
DoujinManager.ApplicationCore.UseCases.DeleteVariantCommand 0%
DoujinManager.ApplicationCore.UseCases.GetDoujinQuery 100%
DoujinManager.ApplicationCore.UseCases.GetImageQuery 100%
DoujinManager.ApplicationCore.UseCases.GetImageResult 100%
DoujinManager.ApplicationCore.UseCases.GetThumbnailQuery 100%
DoujinManager.ApplicationCore.UseCases.GetThumbnailResult 100%
DoujinManager.ApplicationCore.UseCases.GetVariantQuery 100%
DoujinManager.ApplicationCore.UseCases.ListChaptersQuery 100%
DoujinManager.ApplicationCore.UseCases.ListCirclesQuery 100%
DoujinManager.ApplicationCore.UseCases.ListDoujinsQuery 100%
DoujinManager.ApplicationCore.UseCases.ListPagesQuery 100%
DoujinManager.ApplicationCore.UseCases.ListPeopleQuery 100%
DoujinManager.ApplicationCore.UseCases.ListTagsQuery 100%
DoujinManager.ApplicationCore.UseCases.ListVariantsQuery 100%
DoujinManager.ApplicationCore.UseCases.RemoveCircleCommand 0%
DoujinManager.ApplicationCore.UseCases.RemovePersonCommand 0%
DoujinManager.ApplicationCore.UseCases.RemoveTagCommand 0%
DoujinManager.ApplicationCore.UseCases.RemoveTitleCommand 0%
DoujinManager.ApplicationCore.UseCases.ReorderPagesCommand 100%
DoujinManager.ApplicationCore.UseCases.SearchDoujinsQuery 100%
DoujinManager.ApplicationCore.UseCases.SearchResult 100%
DoujinManager.ApplicationCore.UseCases.SearchResults 100%
DoujinManager.ApplicationCore.UseCases.UpdateChapterCommand 0%
DoujinManager.ApplicationCore.UseCases.UpdateCircleCommand 0%
DoujinManager.ApplicationCore.UseCases.UpdateDoujinCommand 100%
DoujinManager.ApplicationCore.UseCases.UpdatePersonCommand 0%
DoujinManager.ApplicationCore.UseCases.UpdateVariantCommand 0%
DoujinManager.ApplicationCore.UseCases.UploadImageFile 100%
DoujinManager.ApplicationCore.UseCases.UploadPagesCommand 100%
DoujinManager.ApplicationCore.UseCases.UploadZipPagesCommand 100%
DoujinManager.Infrastructure - 91.6%
Name Line Branch
DoujinManager.Infrastructure 91.6% 70.5%
DoujinManager.Infrastructure.Archives.ZipExtractor 100% 87.5%
DoujinManager.Infrastructure.Data.Configurations.ChapterConfiguration 100%
DoujinManager.Infrastructure.Data.Configurations.CircleConfiguration 100%
DoujinManager.Infrastructure.Data.Configurations.DoujinCircleConfiguration 100%
DoujinManager.Infrastructure.Data.Configurations.DoujinConfiguration 100%
DoujinManager.Infrastructure.Data.Configurations.DoujinPersonConfiguration 100%
DoujinManager.Infrastructure.Data.Configurations.DoujinTagConfiguration 100%
DoujinManager.Infrastructure.Data.Configurations.ImageFileConfiguration 100%
DoujinManager.Infrastructure.Data.Configurations.PageConfiguration 100%
DoujinManager.Infrastructure.Data.Configurations.PersonConfiguration 100%
DoujinManager.Infrastructure.Data.Configurations.TagConfiguration 100%
DoujinManager.Infrastructure.Data.Configurations.TitleConfiguration 100%
DoujinManager.Infrastructure.Data.Configurations.VariantConfiguration 100%
DoujinManager.Infrastructure.Data.DoujinManagerDbContext 100%
DoujinManager.Infrastructure.Data.GuidIdGenerator 11.1%
DoujinManager.Infrastructure.Data.Migrations.DoujinManagerDbContextModelSna
pshot
100%
DoujinManager.Infrastructure.Data.Migrations.InitialCreate 97.1%
DoujinManager.Infrastructure.Data.ModelBuilderExtensions 50%
DoujinManager.Infrastructure.Data.StronglyTypedIdConverterFactory 69.2%
DoujinManager.Infrastructure.Images.SkiaSharpImageInspector 88.2% 70.9%
DoujinManager.Infrastructure.Images.SkiaSharpThumbnailGenerator 94.5% 66.6%
DoujinManager.Infrastructure.Services.BackupService 85.1% 66.6%
DoujinManager.Infrastructure.Services.ChapterService 54.2% 25%
DoujinManager.Infrastructure.Services.CircleService 69.6% 0%
DoujinManager.Infrastructure.Services.DoujinService 70.3% 45.8%
DoujinManager.Infrastructure.Services.ImageService 92% 50%
DoujinManager.Infrastructure.Services.PageService 84.9% 65%
DoujinManager.Infrastructure.Services.PersonService 69.6% 0%
DoujinManager.Infrastructure.Services.SearchService 100% 98.2%
DoujinManager.Infrastructure.Services.TagService 94.7% 100%
DoujinManager.Infrastructure.Services.VariantService 50.7% 16.6%
DoujinManager.Infrastructure.Storage.FilesystemImageStorage 100% 100%
DoujinManager.Infrastructure.Storage.FilesystemThumbnailStorage 95% 50%
DoujinManager.Infrastructure.UseCases.AddTitleUseCase 0% 0%
DoujinManager.Infrastructure.UseCases.AssignCircleUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.AssignPersonUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.AssignTagUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.CreateChapterUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.CreateCircleUseCase 100%
DoujinManager.Infrastructure.UseCases.CreateDoujinUseCase 100% 98%
DoujinManager.Infrastructure.UseCases.CreatePersonUseCase 100%
DoujinManager.Infrastructure.UseCases.CreateTagUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.CreateVariantUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.DeleteChapterUseCase 0%
DoujinManager.Infrastructure.UseCases.DeleteDoujinUseCase 100%
DoujinManager.Infrastructure.UseCases.DeletePageUseCase 92.3% 75%
DoujinManager.Infrastructure.UseCases.DeleteVariantUseCase 0%
DoujinManager.Infrastructure.UseCases.GetDoujinUseCase 100%
DoujinManager.Infrastructure.UseCases.GetImageUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.GetThumbnailUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.GetVariantUseCase 100%
DoujinManager.Infrastructure.UseCases.ListChaptersUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.ListCirclesUseCase 100%
DoujinManager.Infrastructure.UseCases.ListDoujinsUseCase 100%
DoujinManager.Infrastructure.UseCases.ListPagesUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.ListPeopleUseCase 100%
DoujinManager.Infrastructure.UseCases.ListTagsUseCase 100%
DoujinManager.Infrastructure.UseCases.ListVariantsUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.RemoveCircleUseCase 0%
DoujinManager.Infrastructure.UseCases.RemovePersonUseCase 0%
DoujinManager.Infrastructure.UseCases.RemoveTagUseCase 0%
DoujinManager.Infrastructure.UseCases.RemoveTitleUseCase 0%
DoujinManager.Infrastructure.UseCases.ReorderPagesUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.SearchDoujinsUseCase 100%
DoujinManager.Infrastructure.UseCases.UpdateChapterUseCase 0% 0%
DoujinManager.Infrastructure.UseCases.UpdateCircleUseCase 0% 0%
DoujinManager.Infrastructure.UseCases.UpdateDoujinUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.UpdatePersonUseCase 0% 0%
DoujinManager.Infrastructure.UseCases.UpdateVariantUseCase 0%
DoujinManager.Infrastructure.UseCases.UploadPagesUseCase 91.1% 92.8%
DoujinManager.Infrastructure.UseCases.UploadZipPagesUseCase 70.2% 62.5%
DoujinManager.RestAdapter - 83.9%
Name Line Branch
DoujinManager.RestAdapter 83.9% 74.1%
DoujinManager.RestAdapter.Auth.StaticBearerTokenAuthMiddleware 100% 91.6%
DoujinManager.RestAdapter.Dtos.AssignTagDto 0%
DoujinManager.RestAdapter.Dtos.BackupDto 100%
DoujinManager.RestAdapter.Dtos.ChapterDto 100%
DoujinManager.RestAdapter.Dtos.CircleDto 100%
DoujinManager.RestAdapter.Dtos.CreateBackupResponse 100%
DoujinManager.RestAdapter.Dtos.CreateChapterDto 100%
DoujinManager.RestAdapter.Dtos.CreateCircleDto 100%
DoujinManager.RestAdapter.Dtos.CreateDoujinDto 100%
DoujinManager.RestAdapter.Dtos.CreatePersonDto 100%
DoujinManager.RestAdapter.Dtos.CreateTagDto 100%
DoujinManager.RestAdapter.Dtos.CreateTitleDto 100%
DoujinManager.RestAdapter.Dtos.CreateVariantDto 100%
DoujinManager.RestAdapter.Dtos.DoujinDetailDto 100%
DoujinManager.RestAdapter.Dtos.DoujinPersonDto 100%
DoujinManager.RestAdapter.Dtos.DoujinSummaryDto 100%
DoujinManager.RestAdapter.Dtos.ImageFileSummaryDto 0%
DoujinManager.RestAdapter.Dtos.LinkCircleDto 0%
DoujinManager.RestAdapter.Dtos.LinkPersonDto 100%
DoujinManager.RestAdapter.Dtos.PageDetailDto 100%
DoujinManager.RestAdapter.Dtos.PageDto 100%
DoujinManager.RestAdapter.Dtos.PersonDto 100%
DoujinManager.RestAdapter.Dtos.ReorderPagesDto 100%
DoujinManager.RestAdapter.Dtos.SearchDoujinsDto 100%
DoujinManager.RestAdapter.Dtos.SearchResultDto 100%
DoujinManager.RestAdapter.Dtos.TagDto 100%
DoujinManager.RestAdapter.Dtos.TitleDto 100%
DoujinManager.RestAdapter.Dtos.UpdateChapterDto 0%
DoujinManager.RestAdapter.Dtos.UpdateCircleDto 0%
DoujinManager.RestAdapter.Dtos.UpdateDoujinDto 100%
DoujinManager.RestAdapter.Dtos.UpdatePersonDto 0%
DoujinManager.RestAdapter.Dtos.UpdateVariantDto 0%
DoujinManager.RestAdapter.Dtos.UploadPagesResponseDto 100%
DoujinManager.RestAdapter.Dtos.VariantDetailDto 100%
DoujinManager.RestAdapter.Dtos.VariantSummaryDto 100%
DoujinManager.RestAdapter.Endpoints.BackupEndpoints 100%
DoujinManager.RestAdapter.Endpoints.DoujinEndpoints 81.2% 75%
DoujinManager.RestAdapter.Endpoints.ImageEndpoints 96.6% 50%
DoujinManager.RestAdapter.Endpoints.MetadataEndpoints 84.2%
DoujinManager.RestAdapter.Endpoints.PaginationParams 100%
DoujinManager.RestAdapter.Endpoints.SearchEndpoints 100% 50%
DoujinManager.RestAdapter.Endpoints.VariantEndpoints 69.9% 37.5%
DoujinManager.RestAdapter.Envelopes.CollectionResponse`1 83.3%
DoujinManager.RestAdapter.Envelopes.EnvelopeDefaults 0%
DoujinManager.RestAdapter.Envelopes.EnvelopeJsonOptions 100%
DoujinManager.RestAdapter.Envelopes.ErrorResponse 100%
DoujinManager.RestAdapter.Envelopes.HypermediaAction 100%
DoujinManager.RestAdapter.Envelopes.HypermediaHelpers 86.1% 100%
DoujinManager.RestAdapter.Envelopes.Link 100%
DoujinManager.RestAdapter.Envelopes.PageInfo 100%
DoujinManager.RestAdapter.Envelopes.ResourceResponse`1 80%
DoujinManager.RestAdapter.Envelopes.ValidationError 100%
DoujinManager.RestAdapter.Envelopes.ValidationErrorResponse 100%
DoujinManager.RestAdapter.Middleware.GlobalExceptionMiddleware 100% 50%
DoujinManager.RestAdapter.Middleware.RequestLoggingMiddleware 100% 100%
DoujinManager.RestAdapter.RestAdapterExtensions 100% 100%
Microsoft.Extensions.Validation.Generated 78.6% 78.2%
Microsoft.Extensions.Validation.Generated.<ValidatableInfoResolver_g>FB9B0C
E5CB12CEAC5BBBEA65844E1C05F9ADA4DA38B848075C41304FC6A1ABF77__ValidationAttr
ibuteCache
100% 62.5%
System.Runtime.CompilerServices 0%
DoujinManager.Server - 16.6%
Name Line Branch
DoujinManager.Server 16.6% 0%
DoujinManager.Server.ImageInfrastructureRegistration 100%
DoujinManager.Server.ScalarUi 0%
DoujinManager.Server.UseCaseRegistrationHelper 100%
Microsoft.AspNetCore.OpenApi.Generated 0% 0%
Program 0% 0%
System.Runtime.CompilerServices 0%
<!-- coverage-comment --> # Summary <details open><summary>Summary</summary> ||| |:---|:---| | Generated on: | 06/29/2026 - 10:46:59 | | Coverage date: | 06/29/2026 - 10:46:45 - 06/29/2026 - 10:46:57 | | Parser: | MultiReport (4x Cobertura) | | Assemblies: | 4 | | Classes: | 208 | | Files: | 94 | | **Line coverage:** | 83.5% (4239 of 5076) | | Covered lines: | 4239 | | Uncovered lines: | 837 | | Coverable lines: | 5076 | | Total lines: | 8776 | | **Branch coverage:** | 49.4% (368 of 744) | | Covered branches: | 368 | | Total branches: | 744 | | **Method coverage:** | [Feature is only available for sponsors](https://reportgenerator.io/pro) | </details> ## Coverage <details><summary>DoujinManager.ApplicationCore - 84.8%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**DoujinManager.ApplicationCore**|**84.8%**|****| |DoujinManager.ApplicationCore.Entities.Chapter|87.5%|| |DoujinManager.ApplicationCore.Entities.Circle|100%|| |DoujinManager.ApplicationCore.Entities.Doujin|100%|| |DoujinManager.ApplicationCore.Entities.DoujinCircle|75%|| |DoujinManager.ApplicationCore.Entities.DoujinPerson|80%|| |DoujinManager.ApplicationCore.Entities.DoujinTag|75%|| |DoujinManager.ApplicationCore.Entities.ImageFile|100%|| |DoujinManager.ApplicationCore.Entities.Page|80%|| |DoujinManager.ApplicationCore.Entities.Person|100%|| |DoujinManager.ApplicationCore.Entities.Tag|100%|| |DoujinManager.ApplicationCore.Entities.Title|83.3%|| |DoujinManager.ApplicationCore.Entities.Variant|91.6%|| |DoujinManager.ApplicationCore.Ids.ChapterId|66.6%|| |DoujinManager.ApplicationCore.Ids.CircleId|66.6%|| |DoujinManager.ApplicationCore.Ids.DoujinId|100%|| |DoujinManager.ApplicationCore.Ids.ImageFileId|66.6%|| |DoujinManager.ApplicationCore.Ids.PageId|66.6%|| |DoujinManager.ApplicationCore.Ids.PersonId|66.6%|| |DoujinManager.ApplicationCore.Ids.TagId|66.6%|| |DoujinManager.ApplicationCore.Ids.TitleId|66.6%|| |DoujinManager.ApplicationCore.Ids.VariantId|66.6%|| |DoujinManager.ApplicationCore.Ports.ExtractedImage|100%|| |DoujinManager.ApplicationCore.Ports.ImageInspection|100%|| |DoujinManager.ApplicationCore.Services.BackupInfo|100%|| |DoujinManager.ApplicationCore.Services.ITagService|100%|| |DoujinManager.ApplicationCore.Services.ServiceResult|100%|| |DoujinManager.ApplicationCore.Services.ServiceResult`1|33.3%|| |DoujinManager.ApplicationCore.Services.VoidResult|88.8%|| |DoujinManager.ApplicationCore.UseCases.AddTitleCommand|0%|| |DoujinManager.ApplicationCore.UseCases.AssignCircleCommand|100%|| |DoujinManager.ApplicationCore.UseCases.AssignPersonCommand|100%|| |DoujinManager.ApplicationCore.UseCases.AssignTagCommand|100%|| |DoujinManager.ApplicationCore.UseCases.CreateChapterCommand|100%|| |DoujinManager.ApplicationCore.UseCases.CreateCircleCommand|100%|| |DoujinManager.ApplicationCore.UseCases.CreateDoujinCommand|100%|| |DoujinManager.ApplicationCore.UseCases.CreatePersonCommand|100%|| |DoujinManager.ApplicationCore.UseCases.CreateTagCommand|100%|| |DoujinManager.ApplicationCore.UseCases.CreateTitleCommand|100%|| |DoujinManager.ApplicationCore.UseCases.CreateVariantCommand|100%|| |DoujinManager.ApplicationCore.UseCases.DeleteChapterCommand|0%|| |DoujinManager.ApplicationCore.UseCases.DeleteDoujinCommand|100%|| |DoujinManager.ApplicationCore.UseCases.DeletePageCommand|100%|| |DoujinManager.ApplicationCore.UseCases.DeleteVariantCommand|0%|| |DoujinManager.ApplicationCore.UseCases.GetDoujinQuery|100%|| |DoujinManager.ApplicationCore.UseCases.GetImageQuery|100%|| |DoujinManager.ApplicationCore.UseCases.GetImageResult|100%|| |DoujinManager.ApplicationCore.UseCases.GetThumbnailQuery|100%|| |DoujinManager.ApplicationCore.UseCases.GetThumbnailResult|100%|| |DoujinManager.ApplicationCore.UseCases.GetVariantQuery|100%|| |DoujinManager.ApplicationCore.UseCases.ListChaptersQuery|100%|| |DoujinManager.ApplicationCore.UseCases.ListCirclesQuery|100%|| |DoujinManager.ApplicationCore.UseCases.ListDoujinsQuery|100%|| |DoujinManager.ApplicationCore.UseCases.ListPagesQuery|100%|| |DoujinManager.ApplicationCore.UseCases.ListPeopleQuery|100%|| |DoujinManager.ApplicationCore.UseCases.ListTagsQuery|100%|| |DoujinManager.ApplicationCore.UseCases.ListVariantsQuery|100%|| |DoujinManager.ApplicationCore.UseCases.RemoveCircleCommand|0%|| |DoujinManager.ApplicationCore.UseCases.RemovePersonCommand|0%|| |DoujinManager.ApplicationCore.UseCases.RemoveTagCommand|0%|| |DoujinManager.ApplicationCore.UseCases.RemoveTitleCommand|0%|| |DoujinManager.ApplicationCore.UseCases.ReorderPagesCommand|100%|| |DoujinManager.ApplicationCore.UseCases.SearchDoujinsQuery|100%|| |DoujinManager.ApplicationCore.UseCases.SearchResult|100%|| |DoujinManager.ApplicationCore.UseCases.SearchResults|100%|| |DoujinManager.ApplicationCore.UseCases.UpdateChapterCommand|0%|| |DoujinManager.ApplicationCore.UseCases.UpdateCircleCommand|0%|| |DoujinManager.ApplicationCore.UseCases.UpdateDoujinCommand|100%|| |DoujinManager.ApplicationCore.UseCases.UpdatePersonCommand|0%|| |DoujinManager.ApplicationCore.UseCases.UpdateVariantCommand|0%|| |DoujinManager.ApplicationCore.UseCases.UploadImageFile|100%|| |DoujinManager.ApplicationCore.UseCases.UploadPagesCommand|100%|| |DoujinManager.ApplicationCore.UseCases.UploadZipPagesCommand|100%|| </details> <details><summary>DoujinManager.Infrastructure - 91.6%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**DoujinManager.Infrastructure**|**91.6%**|**70.5%**| |DoujinManager.Infrastructure.Archives.ZipExtractor|100%|87.5%| |DoujinManager.Infrastructure.Data.Configurations.ChapterConfiguration|100%|| |DoujinManager.Infrastructure.Data.Configurations.CircleConfiguration|100%|| |DoujinManager.Infrastructure.Data.Configurations.DoujinCircleConfiguration|100%|| |DoujinManager.Infrastructure.Data.Configurations.DoujinConfiguration|100%|| |DoujinManager.Infrastructure.Data.Configurations.DoujinPersonConfiguration|100%|| |DoujinManager.Infrastructure.Data.Configurations.DoujinTagConfiguration|100%|| |DoujinManager.Infrastructure.Data.Configurations.ImageFileConfiguration|100%|| |DoujinManager.Infrastructure.Data.Configurations.PageConfiguration|100%|| |DoujinManager.Infrastructure.Data.Configurations.PersonConfiguration|100%|| |DoujinManager.Infrastructure.Data.Configurations.TagConfiguration|100%|| |DoujinManager.Infrastructure.Data.Configurations.TitleConfiguration|100%|| |DoujinManager.Infrastructure.Data.Configurations.VariantConfiguration|100%|| |DoujinManager.Infrastructure.Data.DoujinManagerDbContext|100%|| |DoujinManager.Infrastructure.Data.GuidIdGenerator|11.1%|| |DoujinManager.Infrastructure.Data.Migrations.DoujinManagerDbContextModelSna<br/>pshot|100%|| |DoujinManager.Infrastructure.Data.Migrations.InitialCreate|97.1%|| |DoujinManager.Infrastructure.Data.ModelBuilderExtensions|50%|| |DoujinManager.Infrastructure.Data.StronglyTypedIdConverterFactory|69.2%|| |DoujinManager.Infrastructure.Images.SkiaSharpImageInspector|88.2%|70.9%| |DoujinManager.Infrastructure.Images.SkiaSharpThumbnailGenerator|94.5%|66.6%| |DoujinManager.Infrastructure.Services.BackupService|85.1%|66.6%| |DoujinManager.Infrastructure.Services.ChapterService|54.2%|25%| |DoujinManager.Infrastructure.Services.CircleService|69.6%|0%| |DoujinManager.Infrastructure.Services.DoujinService|70.3%|45.8%| |DoujinManager.Infrastructure.Services.ImageService|92%|50%| |DoujinManager.Infrastructure.Services.PageService|84.9%|65%| |DoujinManager.Infrastructure.Services.PersonService|69.6%|0%| |DoujinManager.Infrastructure.Services.SearchService|100%|98.2%| |DoujinManager.Infrastructure.Services.TagService|94.7%|100%| |DoujinManager.Infrastructure.Services.VariantService|50.7%|16.6%| |DoujinManager.Infrastructure.Storage.FilesystemImageStorage|100%|100%| |DoujinManager.Infrastructure.Storage.FilesystemThumbnailStorage|95%|50%| |DoujinManager.Infrastructure.UseCases.AddTitleUseCase|0%|0%| |DoujinManager.Infrastructure.UseCases.AssignCircleUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.AssignPersonUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.AssignTagUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.CreateChapterUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.CreateCircleUseCase|100%|| |DoujinManager.Infrastructure.UseCases.CreateDoujinUseCase|100%|98%| |DoujinManager.Infrastructure.UseCases.CreatePersonUseCase|100%|| |DoujinManager.Infrastructure.UseCases.CreateTagUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.CreateVariantUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.DeleteChapterUseCase|0%|| |DoujinManager.Infrastructure.UseCases.DeleteDoujinUseCase|100%|| |DoujinManager.Infrastructure.UseCases.DeletePageUseCase|92.3%|75%| |DoujinManager.Infrastructure.UseCases.DeleteVariantUseCase|0%|| |DoujinManager.Infrastructure.UseCases.GetDoujinUseCase|100%|| |DoujinManager.Infrastructure.UseCases.GetImageUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.GetThumbnailUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.GetVariantUseCase|100%|| |DoujinManager.Infrastructure.UseCases.ListChaptersUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.ListCirclesUseCase|100%|| |DoujinManager.Infrastructure.UseCases.ListDoujinsUseCase|100%|| |DoujinManager.Infrastructure.UseCases.ListPagesUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.ListPeopleUseCase|100%|| |DoujinManager.Infrastructure.UseCases.ListTagsUseCase|100%|| |DoujinManager.Infrastructure.UseCases.ListVariantsUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.RemoveCircleUseCase|0%|| |DoujinManager.Infrastructure.UseCases.RemovePersonUseCase|0%|| |DoujinManager.Infrastructure.UseCases.RemoveTagUseCase|0%|| |DoujinManager.Infrastructure.UseCases.RemoveTitleUseCase|0%|| |DoujinManager.Infrastructure.UseCases.ReorderPagesUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.SearchDoujinsUseCase|100%|| |DoujinManager.Infrastructure.UseCases.UpdateChapterUseCase|0%|0%| |DoujinManager.Infrastructure.UseCases.UpdateCircleUseCase|0%|0%| |DoujinManager.Infrastructure.UseCases.UpdateDoujinUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.UpdatePersonUseCase|0%|0%| |DoujinManager.Infrastructure.UseCases.UpdateVariantUseCase|0%|| |DoujinManager.Infrastructure.UseCases.UploadPagesUseCase|91.1%|92.8%| |DoujinManager.Infrastructure.UseCases.UploadZipPagesUseCase|70.2%|62.5%| </details> <details><summary>DoujinManager.RestAdapter - 83.9%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**DoujinManager.RestAdapter**|**83.9%**|**74.1%**| |DoujinManager.RestAdapter.Auth.StaticBearerTokenAuthMiddleware|100%|91.6%| |DoujinManager.RestAdapter.Dtos.AssignTagDto|0%|| |DoujinManager.RestAdapter.Dtos.BackupDto|100%|| |DoujinManager.RestAdapter.Dtos.ChapterDto|100%|| |DoujinManager.RestAdapter.Dtos.CircleDto|100%|| |DoujinManager.RestAdapter.Dtos.CreateBackupResponse|100%|| |DoujinManager.RestAdapter.Dtos.CreateChapterDto|100%|| |DoujinManager.RestAdapter.Dtos.CreateCircleDto|100%|| |DoujinManager.RestAdapter.Dtos.CreateDoujinDto|100%|| |DoujinManager.RestAdapter.Dtos.CreatePersonDto|100%|| |DoujinManager.RestAdapter.Dtos.CreateTagDto|100%|| |DoujinManager.RestAdapter.Dtos.CreateTitleDto|100%|| |DoujinManager.RestAdapter.Dtos.CreateVariantDto|100%|| |DoujinManager.RestAdapter.Dtos.DoujinDetailDto|100%|| |DoujinManager.RestAdapter.Dtos.DoujinPersonDto|100%|| |DoujinManager.RestAdapter.Dtos.DoujinSummaryDto|100%|| |DoujinManager.RestAdapter.Dtos.ImageFileSummaryDto|0%|| |DoujinManager.RestAdapter.Dtos.LinkCircleDto|0%|| |DoujinManager.RestAdapter.Dtos.LinkPersonDto|100%|| |DoujinManager.RestAdapter.Dtos.PageDetailDto|100%|| |DoujinManager.RestAdapter.Dtos.PageDto|100%|| |DoujinManager.RestAdapter.Dtos.PersonDto|100%|| |DoujinManager.RestAdapter.Dtos.ReorderPagesDto|100%|| |DoujinManager.RestAdapter.Dtos.SearchDoujinsDto|100%|| |DoujinManager.RestAdapter.Dtos.SearchResultDto|100%|| |DoujinManager.RestAdapter.Dtos.TagDto|100%|| |DoujinManager.RestAdapter.Dtos.TitleDto|100%|| |DoujinManager.RestAdapter.Dtos.UpdateChapterDto|0%|| |DoujinManager.RestAdapter.Dtos.UpdateCircleDto|0%|| |DoujinManager.RestAdapter.Dtos.UpdateDoujinDto|100%|| |DoujinManager.RestAdapter.Dtos.UpdatePersonDto|0%|| |DoujinManager.RestAdapter.Dtos.UpdateVariantDto|0%|| |DoujinManager.RestAdapter.Dtos.UploadPagesResponseDto|100%|| |DoujinManager.RestAdapter.Dtos.VariantDetailDto|100%|| |DoujinManager.RestAdapter.Dtos.VariantSummaryDto|100%|| |DoujinManager.RestAdapter.Endpoints.BackupEndpoints|100%|| |DoujinManager.RestAdapter.Endpoints.DoujinEndpoints|81.2%|75%| |DoujinManager.RestAdapter.Endpoints.ImageEndpoints|96.6%|50%| |DoujinManager.RestAdapter.Endpoints.MetadataEndpoints|84.2%|| |DoujinManager.RestAdapter.Endpoints.PaginationParams|100%|| |DoujinManager.RestAdapter.Endpoints.SearchEndpoints|100%|50%| |DoujinManager.RestAdapter.Endpoints.VariantEndpoints|69.9%|37.5%| |DoujinManager.RestAdapter.Envelopes.CollectionResponse`1|83.3%|| |DoujinManager.RestAdapter.Envelopes.EnvelopeDefaults|0%|| |DoujinManager.RestAdapter.Envelopes.EnvelopeJsonOptions|100%|| |DoujinManager.RestAdapter.Envelopes.ErrorResponse|100%|| |DoujinManager.RestAdapter.Envelopes.HypermediaAction|100%|| |DoujinManager.RestAdapter.Envelopes.HypermediaHelpers|86.1%|100%| |DoujinManager.RestAdapter.Envelopes.Link|100%|| |DoujinManager.RestAdapter.Envelopes.PageInfo|100%|| |DoujinManager.RestAdapter.Envelopes.ResourceResponse`1|80%|| |DoujinManager.RestAdapter.Envelopes.ValidationError|100%|| |DoujinManager.RestAdapter.Envelopes.ValidationErrorResponse|100%|| |DoujinManager.RestAdapter.Middleware.GlobalExceptionMiddleware|100%|50%| |DoujinManager.RestAdapter.Middleware.RequestLoggingMiddleware|100%|100%| |DoujinManager.RestAdapter.RestAdapterExtensions|100%|100%| |Microsoft.Extensions.Validation.Generated|78.6%|78.2%| |Microsoft.Extensions.Validation.Generated.<ValidatableInfoResolver_g>FB9B0C<br/>E5CB12CEAC5BBBEA65844E1C05F9ADA4DA38B848075C41304FC6A1ABF77__ValidationAttr<br/>ibuteCache|100%|62.5%| |System.Runtime.CompilerServices|0%|| </details> <details><summary>DoujinManager.Server - 16.6%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**DoujinManager.Server**|**16.6%**|**0%**| |DoujinManager.Server.ImageInfrastructureRegistration|100%|| |DoujinManager.Server.ScalarUi|0%|| |DoujinManager.Server.UseCaseRegistrationHelper|100%|| |Microsoft.AspNetCore.OpenApi.Generated|0%|0%| |Program|0%|0%| |System.Runtime.CompilerServices|0%|| </details>
Author
Member

🤖 Hermes automated review: no blocking issues found

Reviewed all 12 changed files (+302/-18) on head a0fca665. The PR adds structured logging infrastructure cleanly and safely. Static security scan on added lines: clean (no secrets, shell injection, eval/exec, or SQL injection).

What's good

  • RequestLoggingMiddleware — try/finally guarantees the status code is logged even when downstream throws; status-based log levels (5xx→Error, 4xx→Warning, 2xx/3xx→Info, /health&docs→Debug) are sensible. Structured placeholders ({Method}, {Path}, {StatusCode}, {ElapsedMs}) are used correctly as template parameters.
  • Error-path logging improvement — ImageUseCases.cs converts previously-swallowed catch {} blocks into catch (Exception ex) + logger.LogError(ex, ...) in UploadPagesUseCase, UploadZipPagesUseCase, and DeletePageUseCase. This strictly improves observability without changing control flow (rollback + best-effort cleanup behavior is unchanged). 👍
  • Dependency layering — adding Microsoft.Extensions.Logging.Abstractions to ApplicationCore (via CPM) makes ILogger<T> available across layers without pulling the full logging implementation. Clean.
  • Tests — 4 new RequestLoggingMiddlewareTests cover the status→log-level routing and structured fields; existing use-case tests updated with NullLogger<T>.Instance.

Minor notes (non-blocking)

  1. RestAdapter.Tests/Middleware/RequestLoggingMiddlewareTests.cs is missing a trailing newline (\ No newline at end of file). Trivial.
  2. BackupService.cs:71 logs the full filesystem backup path at Information (Database backup created: {BackupPath}). Fine for ops, but be aware it exposes the on-disk directory layout in shared log sinks.
  3. Pre-existing (not this PR's regression): the ExecuteSqlRawAsync($"VACUUM INTO '{escaped}'", ct) in BackupService uses string interpolation with single-quote-doubling escaping. It's unchanged by this PR (only a nearby log line was added), and was covered in prior reviews. Mentioning only for completeness.

Verification

  • CI/CD: forgejo-actions coverage comment #281 is current for head a0fca665 (line 83.5%, branch 49.4%; coverage dated 10:46:45–10:46:57 UTC, matching PR creation at 12:46 CEST). The repo's ci.yml posts coverage only on successful build+test, so build+test passed. Local build/test skipped per CI-evidence policy.
  • RequestLoggingMiddleware shows 100% line / 100% branch coverage in the CI report.

Note: this is not a formal Forgejo review approval — the current MCP integration can post conversation comments but cannot create inline review comments or approval states.


Automated review. I never merge PRs.

## 🤖 Hermes automated review: no blocking issues found Reviewed all 12 changed files (+302/-18) on head `a0fca665`. The PR adds structured logging infrastructure cleanly and safely. Static security scan on added lines: **clean** (no secrets, shell injection, eval/exec, or SQL injection). ### What's good - **`RequestLoggingMiddleware`** — try/finally guarantees the status code is logged even when downstream throws; status-based log levels (5xx→Error, 4xx→Warning, 2xx/3xx→Info, `/health`&docs→Debug) are sensible. Structured placeholders (`{Method}`, `{Path}`, `{StatusCode}`, `{ElapsedMs}`) are used correctly as template parameters. - **Error-path logging improvement** — `ImageUseCases.cs` converts previously-swallowed `catch {}` blocks into `catch (Exception ex)` + `logger.LogError(ex, ...)` in `UploadPagesUseCase`, `UploadZipPagesUseCase`, and `DeletePageUseCase`. This strictly improves observability without changing control flow (rollback + best-effort cleanup behavior is unchanged). 👍 - **Dependency layering** — adding `Microsoft.Extensions.Logging.Abstractions` to ApplicationCore (via CPM) makes `ILogger<T>` available across layers without pulling the full logging implementation. Clean. - **Tests** — 4 new `RequestLoggingMiddlewareTests` cover the status→log-level routing and structured fields; existing use-case tests updated with `NullLogger<T>.Instance`. ### Minor notes (non-blocking) 1. **`RestAdapter.Tests/Middleware/RequestLoggingMiddlewareTests.cs`** is missing a trailing newline (`\ No newline at end of file`). Trivial. 2. **`BackupService.cs:71`** logs the full filesystem backup path at Information (`Database backup created: {BackupPath}`). Fine for ops, but be aware it exposes the on-disk directory layout in shared log sinks. 3. **Pre-existing (not this PR's regression):** the `ExecuteSqlRawAsync($"VACUUM INTO '{escaped}'", ct)` in `BackupService` uses string interpolation with single-quote-doubling escaping. It's unchanged by this PR (only a nearby log line was added), and was covered in prior reviews. Mentioning only for completeness. ### Verification - **CI/CD**: forgejo-actions coverage comment #281 is current for head `a0fca665` (line 83.5%, branch 49.4%; coverage dated 10:46:45–10:46:57 UTC, matching PR creation at 12:46 CEST). The repo's `ci.yml` posts coverage only on successful build+test, so build+test passed. **Local build/test skipped per CI-evidence policy.** - `RequestLoggingMiddleware` shows **100% line / 100% branch** coverage in the CI report. Note: this is not a formal Forgejo review approval — the current MCP integration can post conversation comments but cannot create inline review comments or approval states. --- *Automated review. I never merge PRs.*
bjoern merged commit 515ff98162 into main 2026-06-29 13:00:48 +02:00
bjoern deleted branch feat/structured-logging 2026-06-29 13:00:48 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
TeamAI/doujin-manager!15
No description provided.