Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/Orbit.Api/Controllers/OAuthController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -322,7 +322,7 @@ public async Task<IActionResult> Token(
await apiKeyRepository.AddAsync(apiKey, ct);
await unitOfWork.SaveChangesAsync(ct);

if (logger.IsEnabled(LogLevel.Information))
if (logger.IsEnabled(LogLevel.Debug))
LogOAuthApiKeyCreated(logger, entry.UserId, entry.ClientId);

var response = new Dictionary<string, object>
Expand All @@ -347,6 +347,6 @@ private bool IsRedirectUriAllowed(string redirectUri)
[LoggerMessage(EventId = 1, Level = LogLevel.Error, Message = "Failed to create OAuth API key for user {UserId}: {Error}")]
private static partial void LogFailedToCreateOAuthApiKey(ILogger logger, Guid userId, string? error);

[LoggerMessage(EventId = 2, Level = LogLevel.Information, Message = "OAuth API key created for user {UserId} via {ClientId}")]
[LoggerMessage(EventId = 2, Level = LogLevel.Debug, Message = "OAuth API key created for user {UserId} via {ClientId}")]
private static partial void LogOAuthApiKeyCreated(ILogger logger, Guid userId, string? clientId);
}
6 changes: 3 additions & 3 deletions src/Orbit.Infrastructure/AI/AiRetryLoggingPolicy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,9 @@ private void LogFailedAttempt(PipelineMessage message, Exception? exception, boo
counter.Value++;

var failure = exception?.GetType().Name ?? $"HTTP {response!.Status}";
LogAiAttemptFailed(logger, counter.Value, failure, retriable, exception?.Message);
LogAiAttemptFailed(logger, counter.Value, failure, retriable);
}

[LoggerMessage(EventId = 1, Level = LogLevel.Warning, Message = "AI attempt {Attempt} failed ({Failure}); retriable: {Retriable}. Detail: {Detail}")]
private static partial void LogAiAttemptFailed(ILogger logger, int attempt, string failure, bool retriable, string? detail);
[LoggerMessage(EventId = 1, Level = LogLevel.Warning, Message = "AI attempt {Attempt} failed ({Failure}); retriable: {Retriable}")]
private static partial void LogAiAttemptFailed(ILogger logger, int attempt, string failure, bool retriable);
}
4 changes: 2 additions & 2 deletions src/Orbit.Infrastructure/Services/AccountDeletionService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ private async Task ProcessScheduledDeletions(CancellationToken ct)
if (userIds.Count == 0)
return;

if (logger.IsEnabled(LogLevel.Information))
if (logger.IsEnabled(LogLevel.Debug))
LogProcessingDeletions(logger, userIds.Count);

foreach (var userId in userIds)
Expand Down Expand Up @@ -153,7 +153,7 @@ internal async Task CleanupStaleSentRecords(CancellationToken ct)
[LoggerMessage(EventId = 3, Level = LogLevel.Error, Message = "Error in account deletion service")]
private static partial void LogServiceError(ILogger logger, Exception ex);

[LoggerMessage(EventId = 4, Level = LogLevel.Information, Message = "Processing {Count} scheduled account deletions")]
[LoggerMessage(EventId = 4, Level = LogLevel.Debug, Message = "Processing {Count} scheduled account deletions")]
private static partial void LogProcessingDeletions(ILogger logger, int count);

[LoggerMessage(EventId = 5, Level = LogLevel.Information, Message = "Deleted deactivated account {UserId}")]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ internal async Task AdvanceStaleDueDates(CancellationToken ct)
if (advanced > 0)
{
await dbContext.SaveChangesAsync(ct);
if (logger.IsEnabled(LogLevel.Information))
if (logger.IsEnabled(LogLevel.Debug))
LogDueDatesAdvanced(logger, advanced);
}
}
Expand All @@ -119,7 +119,7 @@ internal async Task AdvanceStaleDueDates(CancellationToken ct)
[LoggerMessage(EventId = 3, Level = LogLevel.Error, Message = "Error in habit due date advancement")]
private static partial void LogServiceError(ILogger logger, Exception ex);

[LoggerMessage(EventId = 4, Level = LogLevel.Information, Message = "Advanced DueDate for {Count} bad habits")]
[LoggerMessage(EventId = 4, Level = LogLevel.Debug, Message = "Advanced DueDate for {Count} bad habits")]
private static partial void LogDueDatesAdvanced(ILogger logger, int count);

}
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@ await dbContext.Notifications.AddAsync(
catch (DbUpdateException ex) when (DbUniqueViolation.IsUniqueViolation(ex))
{
DetachPendingEntries(dbContext);
if (logger.IsEnabled(LogLevel.Information))
if (logger.IsEnabled(LogLevel.Debug))
LogProactiveCheckinAlreadySent(logger, userId);
return false;
}
Expand All @@ -221,7 +221,7 @@ private static void DetachPendingEntries(OrbitDbContext dbContext)
[LoggerMessage(EventId = 5, Level = LogLevel.Information, Message = "Sent proactive check-in to user {UserId}")]
private static partial void LogSentProactiveCheckin(ILogger logger, Guid userId);

[LoggerMessage(EventId = 6, Level = LogLevel.Information, Message = "Proactive check-in already recorded for user {UserId}; skipping push")]
[LoggerMessage(EventId = 6, Level = LogLevel.Debug, Message = "Proactive check-in already recorded for user {UserId}; skipping push")]
private static partial void LogProactiveCheckinAlreadySent(ILogger logger, Guid userId);

}
12 changes: 6 additions & 6 deletions src/Orbit.Infrastructure/Services/ResendEmailService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,13 +45,13 @@

var tokens = new Dictionary<string, string>
{
["heading"] = copy.Heading,

Check warning on line 48 in src/Orbit.Infrastructure/Services/ResendEmailService.cs

View workflow job for this annotation

GitHub Actions / SonarCloud Analysis

Define a constant instead of using this literal 'heading' 4 times.
["intro"] = copy.Intro,

Check warning on line 49 in src/Orbit.Infrastructure/Services/ResendEmailService.cs

View workflow job for this annotation

GitHub Actions / SonarCloud Analysis

Define a constant instead of using this literal 'intro' 4 times.
["code"] = code,
["cta"] = copy.Cta,
["signInUrl"] = signInUrl,
["warning"] = copy.Warning,
["footer"] = copy.Footer,

Check warning on line 54 in src/Orbit.Infrastructure/Services/ResendEmailService.cs

View workflow job for this annotation

GitHub Actions / SonarCloud Analysis

Define a constant instead of using this literal 'footer' 4 times.
};

var layout = new EmailLayout(LangCode(isPtBr), copy.Preheader, copy.Footer, LogoUrl, GradientHeader: false);
Expand Down Expand Up @@ -146,11 +146,11 @@
$"<a href=\"{encodedUrl}\" style=\"color: #90A1B9; text-decoration: underline;\">{unsubscribeLabel}</a>";
}

private async Task SendMarketingWithBackoffAsync(string to, string subject, string serializedPayload, CancellationToken cancellationToken)

Check warning on line 149 in src/Orbit.Infrastructure/Services/ResendEmailService.cs

View workflow job for this annotation

GitHub Actions / SonarCloud Analysis

Refactor this method to reduce its Cognitive Complexity from 22 to the 15 allowed.
{
if (IsTestAccount(to))
{
if (logger.IsEnabled(LogLevel.Information))
if (logger.IsEnabled(LogLevel.Debug))
LogSkippingTestEmail(logger, subject);
return;
}
Expand Down Expand Up @@ -179,7 +179,7 @@
{
if (response.IsSuccessStatusCode)
{
if (logger.IsEnabled(LogLevel.Information))
if (logger.IsEnabled(LogLevel.Debug))
LogEmailSent(logger, subject);
return;
}
Expand Down Expand Up @@ -246,7 +246,7 @@
{
if (IsTestAccount(to))
{
if (logger.IsEnabled(LogLevel.Information))
if (logger.IsEnabled(LogLevel.Debug))
LogSkippingTestEmail(logger, subject);
return;
}
Expand All @@ -269,7 +269,7 @@

if (response.IsSuccessStatusCode)
{
if (logger.IsEnabled(LogLevel.Information))
if (logger.IsEnabled(LogLevel.Debug))
LogEmailSent(logger, subject);
}
else
Expand Down Expand Up @@ -305,10 +305,10 @@
return false;
}

[LoggerMessage(EventId = 1, Level = LogLevel.Information, Message = "Skipping email to test account; subject={Subject}")]
[LoggerMessage(EventId = 1, Level = LogLevel.Debug, Message = "Skipping email to test account; subject={Subject}")]
private static partial void LogSkippingTestEmail(ILogger logger, string subject);

[LoggerMessage(EventId = 2, Level = LogLevel.Information, Message = "Email sent; subject={Subject}")]
[LoggerMessage(EventId = 2, Level = LogLevel.Debug, Message = "Email sent; subject={Subject}")]
private static partial void LogEmailSent(ILogger logger, string subject);

[LoggerMessage(EventId = 3, Level = LogLevel.Error, Message = "Email failed; subject={Subject} status={Status}")]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@
return new StagedFreeze(user, missedDate, title, body);
}

private async Task<bool> TrySaveBatchAsync(OrbitDbContext dbContext, CancellationToken ct)

Check warning on line 156 in src/Orbit.Infrastructure/Services/StreakFreezeAutoActivationService.cs

View workflow job for this annotation

GitHub Actions / SonarCloud Analysis

Make 'TrySaveBatchAsync' a static method.
{
try
{
Expand Down Expand Up @@ -193,7 +193,7 @@
LogFreezeActivated(logger, freeze.User.Id, freeze.MissedDate);
}

private async Task ActivatePerUserFallbackAsync(

Check warning on line 196 in src/Orbit.Infrastructure/Services/StreakFreezeAutoActivationService.cs

View workflow job for this annotation

GitHub Actions / SonarCloud Analysis

Method has 8 parameters, which is greater than the 7 authorized.
List<Guid> candidateIds,
bool gamificationFreeTierEnabled,
Dictionary<Guid, List<StreakFreeze>> freezesByUser,
Expand Down Expand Up @@ -260,14 +260,14 @@
catch (DbUpdateConcurrencyException)
{
DiscardPendingChanges(dbContext);
if (logger.IsEnabled(LogLevel.Information))
if (logger.IsEnabled(LogLevel.Debug))
LogFreezeConflictSkipped(logger, userId);
return false;
}
catch (DbUpdateException ex) when (DbUniqueViolation.IsUniqueViolation(ex))
{
DiscardPendingChanges(dbContext);
if (logger.IsEnabled(LogLevel.Information))
if (logger.IsEnabled(LogLevel.Debug))
LogFreezeAlreadyActivated(logger, userId);
return false;
}
Expand Down Expand Up @@ -308,10 +308,10 @@
[LoggerMessage(EventId = 4, Level = LogLevel.Information, Message = "Auto-activated streak freeze for user {UserId} on {FrozenDate}")]
private static partial void LogFreezeActivated(ILogger logger, Guid userId, DateOnly frozenDate);

[LoggerMessage(EventId = 5, Level = LogLevel.Information, Message = "Streak freeze already activated for user {UserId}; skipping")]
[LoggerMessage(EventId = 5, Level = LogLevel.Debug, Message = "Streak freeze already activated for user {UserId}; skipping")]
private static partial void LogFreezeAlreadyActivated(ILogger logger, Guid userId);

[LoggerMessage(EventId = 6, Level = LogLevel.Information, Message = "Streak freeze skipped for user {UserId} due to a concurrent update; will re-evaluate next run")]
[LoggerMessage(EventId = 6, Level = LogLevel.Debug, Message = "Streak freeze skipped for user {UserId} due to a concurrent update; will re-evaluate next run")]
private static partial void LogFreezeConflictSkipped(ILogger logger, Guid userId);

[LoggerMessage(EventId = 7, Level = LogLevel.Warning, Message = "Failed to deliver streak-freeze push for user {UserId}; freeze already persisted")]
Expand Down
45 changes: 45 additions & 0 deletions tests/Orbit.Infrastructure.Tests/AI/AiRetryLoggingPolicyTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,34 @@ public void Send_SuccessfulResponse_LogsNothing()
logger.Entries.Should().BeEmpty();
}

[Fact]
public void Send_TransportThrows_LogsSafeClassificationWithoutRawExceptionMessage()
{
const string secretDetail = "connection to 10.0.0.5:5432 refused; internal token abc123";
var logger = new CollectingLogger();
var pipeline = BuildThrowingPipeline(logger, secretDetail, maxRetries: 1);
var message = CreateMessage(pipeline);

var send = () => pipeline.Send(message);

send.Should().Throw<Exception>();
logger.Entries.Should().NotBeEmpty();
logger.Entries.Should().AllSatisfy(entry =>
{
entry.Should().MatchRegex(@"^AI attempt \d+ failed \([A-Za-z]+\); retriable: (True|False)$");
entry.Should().NotContain(secretDetail);
});
}

private static ClientPipeline BuildThrowingPipeline(ILogger logger, string exceptionMessage, int maxRetries)
{
return ClientPipeline.Create(new ClientPipelineOptions
{
Transport = new HttpClientPipelineTransport(new HttpClient(new ThrowingHandler(exceptionMessage))),
RetryPolicy = new ZeroDelayRetryPolicy(maxRetries, logger),
});
}

private static ClientPipeline BuildPipeline(ILogger logger, HttpStatusCode status, int maxRetries)
{
return ClientPipeline.Create(new ClientPipelineOptions
Expand Down Expand Up @@ -102,6 +130,23 @@ private HttpResponseMessage BuildResponse(HttpRequestMessage request)
}
}

private sealed class ThrowingHandler(string message) : HttpMessageHandler
{
protected override HttpResponseMessage Send(
HttpRequestMessage request,
CancellationToken cancellationToken)
{
throw new HttpRequestException(message);
}

protected override Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request,
CancellationToken cancellationToken)
{
throw new HttpRequestException(message);
}
}

private sealed class CollectingLogger : ILogger
{
public List<string> Entries { get; } = [];
Expand Down
Loading