Add Serilog console logging across the API

Information at endpoint and service-method boundaries, Debug in deeper
helpers, Warning before expected/recoverable failures (not-found,
validation, agent tool errors), Error on caught exceptions. Serilog
wraps the exception handler so request-completion logs report the
resolved status code rather than the raw exception. Never logs prose
bodies or the Anthropic API key.
This commit is contained in:
James Wampler
2026-08-06 12:11:20 -07:00
parent 4f396bb5f9
commit 04917fa09e
34 changed files with 790 additions and 147 deletions
+32 -8
View File
@@ -4,10 +4,13 @@ using Novelly.Api.Data;
namespace Novelly.Api.Projects;
public class ProjectService(INovelDbContext db)
public class ProjectService(INovelDbContext db, ILogger<ProjectService> logger)
{
public async Task<IReadOnlyList<ProjectSummaryDto>> ListAsync(CancellationToken ct = default) =>
await db.Projects
public async Task<IReadOnlyList<ProjectSummaryDto>> ListAsync(CancellationToken ct = default)
{
logger.LogInformation("Listing projects");
return await db.Projects
.OrderByDescending(p => p.UpdatedAt)
.Select(p => new ProjectSummaryDto(
p.Id,
@@ -21,12 +24,18 @@ public class ProjectService(INovelDbContext db)
p.Chapters.SelectMany(c => c.Scenes).Sum(s => (int?)s.WordCount) ?? 0,
p.UpdatedAt))
.ToListAsync(ct);
}
public async Task<ProjectDto> GetAsync(Guid id, CancellationToken ct = default) =>
(await FindAsync(id, ct)).ToDto();
public async Task<ProjectDto> GetAsync(Guid id, CancellationToken ct = default)
{
logger.LogInformation("Getting project {ProjectId}", id);
return (await FindAsync(id, ct)).ToDto();
}
public async Task<ProjectDto> CreateAsync(CreateProjectRequest request, CancellationToken ct = default)
{
logger.LogInformation("Creating project {Title}", request.Title);
var project = new Project
{
Title = request.Title,
@@ -45,6 +54,8 @@ public class ProjectService(INovelDbContext db)
public async Task<ProjectDto> UpdateAsync(Guid id, UpdateProjectRequest request, CancellationToken ct = default)
{
logger.LogInformation("Updating project {ProjectId}", id);
var project = await FindAsync(id, ct);
project.Title = Patch.Apply(project.Title, request.Title) ?? project.Title;
@@ -62,12 +73,25 @@ public class ProjectService(INovelDbContext db)
public async Task DeleteAsync(Guid id, CancellationToken ct = default)
{
logger.LogInformation("Deleting project {ProjectId}", id);
var project = await FindAsync(id, ct);
db.Projects.Remove(project);
await db.SaveChangesAsync(ct);
}
private async Task<Project> FindAsync(Guid id, CancellationToken ct) =>
await db.Projects.FirstOrDefaultAsync(p => p.Id == id, ct)
?? throw new NotFoundException(nameof(Project), id);
private async Task<Project> FindAsync(Guid id, CancellationToken ct)
{
logger.LogDebug("Finding project {ProjectId}", id);
var project = await db.Projects.FirstOrDefaultAsync(p => p.Id == id, ct);
if (project is null)
{
logger.LogWarning("Project {ProjectId} not found", id);
throw new NotFoundException(nameof(Project), id);
}
logger.LogDebug("Found project {ProjectId}", id);
return project;
}
}