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:
@@ -28,15 +28,21 @@ public class NovelAgentService(
|
||||
private readonly AgentOptions _options = options.Value;
|
||||
|
||||
public async Task<IReadOnlyList<ConversationSummaryDto>> ListConversationsAsync(
|
||||
Guid projectId, CancellationToken ct = default) =>
|
||||
await db.Conversations
|
||||
Guid projectId, CancellationToken ct = default)
|
||||
{
|
||||
logger.LogInformation("Listing agent conversations for project {ProjectId}", projectId);
|
||||
|
||||
return await db.Conversations
|
||||
.Where(c => c.ProjectId == projectId)
|
||||
.OrderByDescending(c => c.UpdatedAt)
|
||||
.Select(c => new ConversationSummaryDto(c.Id, c.ProjectId, c.Title, c.Messages.Count, c.UpdatedAt))
|
||||
.ToListAsync(ct);
|
||||
}
|
||||
|
||||
public async Task<ConversationDto> GetConversationAsync(Guid conversationId, CancellationToken ct = default)
|
||||
{
|
||||
logger.LogInformation("Getting agent conversation {ConversationId}", conversationId);
|
||||
|
||||
var conversation = await LoadConversationAsync(conversationId, ct);
|
||||
|
||||
return new ConversationDto(
|
||||
@@ -49,6 +55,8 @@ public class NovelAgentService(
|
||||
|
||||
public async Task DeleteConversationAsync(Guid conversationId, CancellationToken ct = default)
|
||||
{
|
||||
logger.LogInformation("Deleting agent conversation {ConversationId}", conversationId);
|
||||
|
||||
var conversation = await LoadConversationAsync(conversationId, ct);
|
||||
db.Conversations.Remove(conversation);
|
||||
await db.SaveChangesAsync(ct);
|
||||
@@ -61,6 +69,10 @@ public class NovelAgentService(
|
||||
public async Task<AgentTurnDto> SendMessageAsync(
|
||||
Guid projectId, SendAgentMessageRequest request, CancellationToken ct = default)
|
||||
{
|
||||
logger.LogInformation(
|
||||
"Sending agent message for project {ProjectId}, conversation {ConversationId}, message length {MessageLength}",
|
||||
projectId, request.ConversationId, request.Message.Length);
|
||||
|
||||
var conversation = request.ConversationId is { } id
|
||||
? await LoadConversationAsync(id, ct)
|
||||
: await StartConversationAsync(projectId, request.Message, ct);
|
||||
@@ -77,6 +89,8 @@ public class NovelAgentService(
|
||||
|
||||
for (var iteration = 0; iteration < _options.MaxIterations; iteration++)
|
||||
{
|
||||
logger.LogDebug("Agent iteration {Iteration} for project {ProjectId}", iteration, projectId);
|
||||
|
||||
var response = await model.CompleteAsync(systemPrompt, transcript, toolset.Definitions, ct);
|
||||
|
||||
foreach (var block in response.Content.OfType<AgentTextBlock>())
|
||||
@@ -142,6 +156,8 @@ public class NovelAgentService(
|
||||
private async Task<AgentMessage> AppendMessageAsync(
|
||||
AgentConversation conversation, AgentRole role, string content, string? toolCallsJson, CancellationToken ct)
|
||||
{
|
||||
logger.LogDebug("Appending {Role} message to conversation {ConversationId}, content length {ContentLength}", role, conversation.Id, content.Length);
|
||||
|
||||
var message = new AgentMessage
|
||||
{
|
||||
ConversationId = conversation.Id,
|
||||
@@ -170,8 +186,11 @@ public class NovelAgentService(
|
||||
private async Task<AgentConversation> StartConversationAsync(
|
||||
Guid projectId, string firstMessage, CancellationToken ct)
|
||||
{
|
||||
logger.LogDebug("Starting new agent conversation for project {ProjectId}", projectId);
|
||||
|
||||
if (!await db.Projects.AnyAsync(p => p.Id == projectId, ct))
|
||||
{
|
||||
logger.LogWarning("Project {ProjectId} not found", projectId);
|
||||
throw new NotFoundException(nameof(Project), projectId);
|
||||
}
|
||||
|
||||
@@ -185,11 +204,22 @@ public class NovelAgentService(
|
||||
return conversation;
|
||||
}
|
||||
|
||||
private async Task<AgentConversation> LoadConversationAsync(Guid conversationId, CancellationToken ct) =>
|
||||
await db.Conversations
|
||||
private async Task<AgentConversation> LoadConversationAsync(Guid conversationId, CancellationToken ct)
|
||||
{
|
||||
logger.LogDebug("Loading agent conversation {ConversationId}", conversationId);
|
||||
|
||||
var conversation = await db.Conversations
|
||||
.Include(c => c.Messages)
|
||||
.FirstOrDefaultAsync(c => c.Id == conversationId, ct)
|
||||
?? throw new NotFoundException(nameof(AgentConversation), conversationId);
|
||||
.FirstOrDefaultAsync(c => c.Id == conversationId, ct);
|
||||
|
||||
if (conversation is null)
|
||||
{
|
||||
logger.LogWarning("AgentConversation {ConversationId} not found", conversationId);
|
||||
throw new NotFoundException(nameof(AgentConversation), conversationId);
|
||||
}
|
||||
|
||||
return conversation;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Replays the stored conversation as plain text turns. Tool calls are not replayed —
|
||||
@@ -208,8 +238,14 @@ public class NovelAgentService(
|
||||
|
||||
private async Task<string> BuildSystemPromptAsync(Guid projectId, CancellationToken ct)
|
||||
{
|
||||
var project = await db.Projects.FirstOrDefaultAsync(p => p.Id == projectId, ct)
|
||||
?? throw new NotFoundException(nameof(Project), projectId);
|
||||
logger.LogDebug("Building system prompt for project {ProjectId}", projectId);
|
||||
|
||||
var project = await db.Projects.FirstOrDefaultAsync(p => p.Id == projectId, ct);
|
||||
if (project is null)
|
||||
{
|
||||
logger.LogWarning("Project {ProjectId} not found", projectId);
|
||||
throw new NotFoundException(nameof(Project), projectId);
|
||||
}
|
||||
|
||||
var brief = new StringBuilder();
|
||||
brief.AppendLine($"Title: {project.Title}");
|
||||
|
||||
Reference in New Issue
Block a user