Browse Source

Stabilize logger-based unit tests by enabling fake `ILogger` in affected suites (#1318)

* Add source-generated logging methods

* Validate generated logging changes

* Centralize logger messages per project

* Remove redundant partial modifier after logging refactor

* Extract DomainObject log message to centralized LogMessages

* Centralize remaining ILogger log statements in infrastructure and web

* Extend [LoggerMessage] source generation to all projects using ILogger

* fix: enable logger in migrator tests

* fix: enable fake loggers in entities warning-log tests

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
master
Copilot 2 weeks ago
committed by GitHub
parent
commit
0668c09ae6
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 2
      backend/extensions/Squidex.Extensions/Actions/Kafka/KafkaProducer.cs
  2. 2
      backend/extensions/Squidex.Extensions/Assets/Azure/AzureMetadataSource.cs
  3. 19
      backend/extensions/Squidex.Extensions/LogMessages.cs
  4. 16
      backend/src/Squidex.Data.MongoDb/Infrastructure/Counts/LogMessages.cs
  5. 2
      backend/src/Squidex.Data.MongoDb/Infrastructure/Counts/MongoCountCollection.cs
  6. 6
      backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleService.cs
  7. 22
      backend/src/Squidex.Domain.Apps.Core.Operations/LogMessages.cs
  8. 2
      backend/src/Squidex.Domain.Apps.Entities/Assets/RecursiveDeleter.cs
  9. 8
      backend/src/Squidex.Domain.Apps.Entities/Backup/RestoreJob.cs
  10. 2
      backend/src/Squidex.Domain.Apps.Entities/Collaboration/CommentCollaborationHandler.cs
  11. 6
      backend/src/Squidex.Domain.Apps.Entities/Collaboration/EmailUserNotifications.cs
  12. 4
      backend/src/Squidex.Domain.Apps.Entities/Contents/ContentSchedulerProcess.cs
  13. 4
      backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/ErrorVisitor.cs
  14. 8
      backend/src/Squidex.Domain.Apps.Entities/History/NotifoService.cs
  15. 4
      backend/src/Squidex.Domain.Apps.Entities/Invitation/InvitationEventConsumer.cs
  16. 10
      backend/src/Squidex.Domain.Apps.Entities/Jobs/JobProcessor.cs
  17. 92
      backend/src/Squidex.Domain.Apps.Entities/LogMessages.cs
  18. 6
      backend/src/Squidex.Domain.Apps.Entities/Rules/RuleQueueWriter.cs
  19. 6
      backend/src/Squidex.Domain.Apps.Entities/Rules/Runner/RuleRunnerJob.cs
  20. 2
      backend/src/Squidex.Domain.Apps.Entities/Search/SearchManager.cs
  21. 2
      backend/src/Squidex.Domain.Users/DefaultUserService.cs
  22. 19
      backend/src/Squidex.Domain.Users/LogMessages.cs
  23. 2
      backend/src/Squidex.Domain.Users/UserManagerExtensions.cs
  24. 2
      backend/src/Squidex.Infrastructure/Commands/DomainObject.cs
  25. 10
      backend/src/Squidex.Infrastructure/Commands/LogCommandMiddleware.cs
  26. 2
      backend/src/Squidex.Infrastructure/Commands/Rebuilder.cs
  27. 11
      backend/src/Squidex.Infrastructure/EventSourcing/Consume/EventConsumerProcessor.cs
  28. 2
      backend/src/Squidex.Infrastructure/Log/BackgroundRequestLogStore.cs
  29. 64
      backend/src/Squidex.Infrastructure/LogMessages.cs
  30. 8
      backend/src/Squidex.Infrastructure/Migrations/Migrator.cs
  31. 2
      backend/src/Squidex.Infrastructure/UsageTracking/BackgroundUsageTracker.cs
  32. 2
      backend/src/Squidex.Web/ApiExceptionFilterAttribute.cs
  33. 35
      backend/src/Squidex.Web/LogMessages.cs
  34. 2
      backend/src/Squidex.Web/Pipeline/ApiKeyHandler.cs
  35. 12
      backend/src/Squidex.Web/Pipeline/AppResolver.cs
  36. 2
      backend/src/Squidex.Web/Pipeline/FileCallbackResultExecutor.cs
  37. 2
      backend/src/Squidex.Web/Pipeline/RequestExceptionMiddleware.cs
  38. 10
      backend/src/Squidex.Web/Pipeline/TeamResolver.cs
  39. 6
      backend/src/Squidex/Areas/Api/Controllers/Users/UsersController.cs
  40. 2
      backend/src/Squidex/Areas/IdentityServer/Config/CreateAdminInitializer.cs
  41. 25
      backend/src/Squidex/LogMessages.cs
  42. 3
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Collaboration/EmailUserNotificationsTests.cs
  43. 3
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Invitation/InvitationEventConsumerTests.cs
  44. 3
      backend/tests/Squidex.Infrastructure.Tests/Migrations/MigratorTests.cs

2
backend/extensions/Squidex.Extensions/Actions/Kafka/KafkaProducer.cs

@ -104,7 +104,7 @@ public sealed class KafkaProducer
private static void LogError(ILogger<KafkaProducer> log, Error error)
{
log.LogWarning("Kafka error with {code} and {reason}.", error.Code, error.Reason);
LogMessages.LogKafkaError(log, error.Code, error.Reason);
}
public async Task SendAsync(KafkaMessageRequest job,

2
backend/extensions/Squidex.Extensions/Assets/Azure/AzureMetadataSource.cs

@ -84,7 +84,7 @@ public sealed class AzureMetadataSource : IAssetMetadataSource
}
catch (Exception ex)
{
log.LogError(ex, "Failed to enrich asset.");
LogMessages.LogFailedToEnrichAsset(log, ex);
}
}

19
backend/extensions/Squidex.Extensions/LogMessages.cs

@ -0,0 +1,19 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using Microsoft.Extensions.Logging;
namespace Squidex.Extensions;
internal static partial class LogMessages
{
[LoggerMessage(Level = LogLevel.Warning, Message = "Kafka error with {code} and {reason}.")]
public static partial void LogKafkaError(ILogger logger, object code, string reason);
[LoggerMessage(Level = LogLevel.Error, Message = "Failed to enrich asset.")]
public static partial void LogFailedToEnrichAsset(ILogger logger, Exception exception);
}

16
backend/src/Squidex.Data.MongoDb/Infrastructure/Counts/LogMessages.cs

@ -0,0 +1,16 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using Microsoft.Extensions.Logging;
namespace Squidex.Infrastructure.Counts;
internal static partial class LogMessages
{
[LoggerMessage(Level = LogLevel.Error, Message = "Failed to update count for collection {collection}.")]
public static partial void LogFailedToUpdateCount(ILogger logger, string collection, Exception exception);
}

2
backend/src/Squidex.Data.MongoDb/Infrastructure/Counts/MongoCountCollection.cs

@ -50,7 +50,7 @@ internal sealed class MongoCountCollection(IMongoDatabase database, ILogger log,
}
catch (Exception ex)
{
log.LogError(ex, "Failed to update count for collection {collection}.", collectionName);
LogMessages.LogFailedToUpdateCount(log, collectionName, ex);
}
}

6
backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleService.cs

@ -108,7 +108,7 @@ public sealed class RuleService(
CreateJobs(@event, context, states, ct)
.Catch(ex =>
{
log.LogError(ex, "Failed to create rule job.");
LogMessages.LogFailedToCreateRuleJob(log, ex);
return states.Select(state => JobResult.Skipped(state.Rule, SkipReason.Failed));
});
@ -237,7 +237,7 @@ public sealed class RuleService(
CreateTriggerJobs(typed, triggerHandler, rulesByTrigger, context, ct)
.Catch(ex =>
{
log.LogError(ex, "Failed to create rule jobs from trigger.");
LogMessages.LogFailedToCreateRuleJobsFromTrigger(log, ex);
return states.Select(state => JobResult.Skipped(state.Rule, SkipReason.Failed));
});
@ -263,7 +263,7 @@ public sealed class RuleService(
CreateEventJobs(@event, enrichedEvent, triggerHandler, states, context)
.Catch(ex =>
{
log.LogError(ex, "Failed to create rule jobs from event.");
LogMessages.LogFailedToCreateRuleJobsFromEvent(log, ex);
return states.Select(state =>
new JobResult

22
backend/src/Squidex.Domain.Apps.Core.Operations/LogMessages.cs

@ -0,0 +1,22 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using Microsoft.Extensions.Logging;
namespace Squidex.Domain.Apps.Core;
internal static partial class LogMessages
{
[LoggerMessage(Level = LogLevel.Error, Message = "Failed to create rule job.")]
public static partial void LogFailedToCreateRuleJob(ILogger logger, Exception exception);
[LoggerMessage(Level = LogLevel.Error, Message = "Failed to create rule jobs from trigger.")]
public static partial void LogFailedToCreateRuleJobsFromTrigger(ILogger logger, Exception exception);
[LoggerMessage(Level = LogLevel.Error, Message = "Failed to create rule jobs from event.")]
public static partial void LogFailedToCreateRuleJobsFromEvent(ILogger logger, Exception exception);
}

2
backend/src/Squidex.Domain.Apps.Entities/Assets/RecursiveDeleter.cs

@ -58,7 +58,7 @@ public sealed class RecursiveDeleter(
}
catch (Exception ex)
{
log.LogError(ex, "Failed to delete asset recursively.");
LogMessages.LogFailedToDeleteAssetRecursively(log, ex);
}
}

8
backend/src/Squidex.Domain.Apps.Entities/Backup/RestoreJob.cs

@ -107,7 +107,7 @@ public sealed class RestoreJob(
await context.LogAsync(" * Restore all objects like app, schemas and contents");
await context.LogAsync(" * Complete the restore operation for all objects");
await context.FlushAsync();
log.LogInformation("Backup with job id {backupId} with from URL '{url}' started.", context.Job.Id, state.Url);
LogMessages.LogRestoreJobStarted(log, context.Job.Id, state.Url);
state.Reader = await DownloadAsync(context, state, ct);
@ -147,7 +147,7 @@ public sealed class RestoreJob(
await AssignContributorAsync(context, state);
await context.LogAsync("Completed, Yeah!");
log.LogInformation("Backup with job id {backupId} from URL '{url}' completed.", context.Job.Id, state.Url);
LogMessages.LogRestoreJobCompleted(log, context.Job.Id, state.Url);
}
catch (Exception ex)
{
@ -168,7 +168,7 @@ public sealed class RestoreJob(
await context.LogAsync(message);
log.LogError(ex, "Backup with job id {backupId} from URL '{url}' failed.", context.Job.Id, state.Url);
LogMessages.LogRestoreJobFailed(log, context.Job.Id, state.Url, ex);
throw;
}
finally
@ -231,7 +231,7 @@ public sealed class RestoreJob(
}
catch (Exception ex)
{
log.LogError(ex, "Failed to clean up restore.");
LogMessages.LogFailedToCleanUpRestore(log, ex);
}
}
}

2
backend/src/Squidex.Domain.Apps.Entities/Collaboration/CommentCollaborationHandler.cs

@ -116,7 +116,7 @@ public sealed partial class CommentCollaborationHandler(
catch (Exception ex)
{
// We are in an extra task, so the exception would be probably swallowed.
log.LogError(ex, "Failed to handle yjs event.");
LogMessages.LogFailedToHandleYjsEvent(log, ex);
throw;
}
});

6
backend/src/Squidex.Domain.Apps.Entities/Collaboration/EmailUserNotifications.cs

@ -123,13 +123,13 @@ public sealed class EmailUserNotifications(
{
if (string.IsNullOrWhiteSpace(emailBody))
{
log.LogWarning("Cannot send email to {email}: No email subject configured for template {template}.", template, user.Email);
LogMessages.LogNoEmailSubjectConfigured(log, template, user.Email);
return;
}
if (string.IsNullOrWhiteSpace(emailSubj))
{
log.LogWarning("Cannot send email to {email}: No email body configured for template {template}.", template, user.Email);
LogMessages.LogNoEmailBodyConfigured(log, template, user.Email);
return;
}
@ -146,7 +146,7 @@ public sealed class EmailUserNotifications(
}
catch (Exception ex)
{
log.LogError(ex, "Failed to send notification to {email}.", user.Email);
LogMessages.LogFailedToSendNotification(log, user.Email, ex);
throw;
}
}

4
backend/src/Squidex.Domain.Apps.Entities/Contents/ContentSchedulerProcess.cs

@ -55,7 +55,7 @@ public sealed class ContentSchedulerProcess(
}
catch (Exception ex)
{
log.LogError(ex, "Failed to query scheduled status changes-");
LogMessages.LogFailedToQueryScheduledStatusChanges(log, ex);
}
}
@ -88,7 +88,7 @@ public sealed class ContentSchedulerProcess(
}
catch (Exception ex)
{
log.LogError(ex, "Failed to execute scheduled status change for content '{contentId}'.", content.Id);
LogMessages.LogFailedToExecuteScheduledStatusChange(log, content.Id, ex);
}
}
}

4
backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/ErrorVisitor.cs

@ -26,11 +26,11 @@ internal static class ErrorVisitor
if (!string.IsNullOrWhiteSpace(fieldName))
{
log.LogError(context.OriginalException, "Failed to resolve field {field}.", fieldName);
LogMessages.LogFailedToResolveField(log, fieldName, context.OriginalException);
}
else
{
log.LogError(context.OriginalException, "Failed to resolve execute query.");
LogMessages.LogFailedToResolveQuery(log, context.OriginalException);
}
if (context.OriginalException is ValidationException or DomainException)

8
backend/src/Squidex.Domain.Apps.Entities/History/NotifoService.cs

@ -128,11 +128,11 @@ public class NotifoService : IUserEvents
}
catch (NotifoException ex)
{
log.LogError(ex, "Failed to register user in notifo: {details}.", ex.ToString());
LogMessages.LogFailedToRegisterUserInNotifoWithDetails(log, ex.ToString(), ex);
}
catch (Exception ex)
{
log.LogError(ex, "Failed to register user in notifo.");
LogMessages.LogFailedToRegisterUserInNotifo(log, ex);
}
}
@ -191,11 +191,11 @@ public class NotifoService : IUserEvents
}
catch (NotifoException ex)
{
log.LogError(ex, "Failed to push user to notifo: {details}.", ex.ToString());
LogMessages.LogFailedToPushUserToNotifoWithDetails(log, ex.ToString(), ex);
}
catch (Exception ex)
{
log.LogError(ex, "Failed to push user to notifo.");
LogMessages.LogFailedToPushUserToNotifo(log, ex);
}
}

4
backend/src/Squidex.Domain.Apps.Entities/Invitation/InvitationEventConsumer.cs

@ -106,7 +106,7 @@ public sealed class InvitationEventConsumer(
if (assigner == null)
{
log.LogWarning("Failed to invite user: Assigner {assignerId} not found.", assignerId);
LogMessages.LogInvitationAssignerNotFound(log, assignerId);
return default;
}
@ -114,7 +114,7 @@ public sealed class InvitationEventConsumer(
if (assignee == null)
{
log.LogWarning("Failed to invite user: Assignee {assigneeId} not found.", assigneeId);
LogMessages.LogInvitationAssigneeNotFound(log, assigneeId);
return default;
}

10
backend/src/Squidex.Domain.Apps.Entities/Jobs/JobProcessor.cs

@ -59,7 +59,7 @@ public sealed class JobProcessor
if (pending.Any())
{
// This should actually never happen, so we log with warning.
log.LogWarning("Removed unfinished jobs for owner {ownerId} after start.", ownerId);
LogMessages.LogRemovedUnfinishedJobs(log, ownerId);
foreach (var job in pending.ToList())
{
@ -81,7 +81,7 @@ public sealed class JobProcessor
{
return scheduler.ScheduleAsync(async _ =>
{
log.LogInformation("Clearing jobs for owner {ownerId}.", ownerId);
LogMessages.LogClearingJobs(log, ownerId);
var job = state.Value.Jobs.Find(x => x.Id == jobId);
@ -105,7 +105,7 @@ public sealed class JobProcessor
{
return scheduler.ScheduleAsync(async _ =>
{
log.LogInformation("Clearing jobs for owner {ownerId}.", ownerId);
LogMessages.LogClearingJobs(log, ownerId);
foreach (var job in state.Value.Jobs)
{
@ -164,7 +164,7 @@ public sealed class JobProcessor
OwnerId = ownerId,
};
log.LogInformation("Starting new backup with backup id '{backupId}' for owner {ownerId}.", context.Job.Id, ownerId);
LogMessages.LogStartingJob(log, context.Job.Id, ownerId);
state.Value.Jobs.Insert(0, context.Job);
try
@ -221,7 +221,7 @@ public sealed class JobProcessor
}
catch (Exception ex)
{
log.LogError(ex, "Failed to run job with ID {jobId}.", context.Job.Id);
LogMessages.LogFailedToRunJob(log, context.Job.Id, ex);
await SetStatusAsync(context, JobStatus.Failed);
}

92
backend/src/Squidex.Domain.Apps.Entities/LogMessages.cs

@ -0,0 +1,92 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using Microsoft.Extensions.Logging;
using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Entities;
internal static partial class LogMessages
{
[LoggerMessage(Level = LogLevel.Error, Message = "Failed to register user in notifo: {details}.")]
public static partial void LogFailedToRegisterUserInNotifoWithDetails(ILogger logger, string details, Exception exception);
[LoggerMessage(Level = LogLevel.Error, Message = "Failed to register user in notifo.")]
public static partial void LogFailedToRegisterUserInNotifo(ILogger logger, Exception exception);
[LoggerMessage(Level = LogLevel.Error, Message = "Failed to push user to notifo: {details}.")]
public static partial void LogFailedToPushUserToNotifoWithDetails(ILogger logger, string details, Exception exception);
[LoggerMessage(Level = LogLevel.Error, Message = "Failed to push user to notifo.")]
public static partial void LogFailedToPushUserToNotifo(ILogger logger, Exception exception);
[LoggerMessage(Level = LogLevel.Error, Message = "Failed to delete asset recursively.")]
public static partial void LogFailedToDeleteAssetRecursively(ILogger logger, Exception exception);
[LoggerMessage(Level = LogLevel.Information, Message = "Backup with job id {backupId} with from URL '{url}' started.")]
public static partial void LogRestoreJobStarted(ILogger logger, DomainId backupId, Uri url);
[LoggerMessage(Level = LogLevel.Information, Message = "Backup with job id {backupId} from URL '{url}' completed.")]
public static partial void LogRestoreJobCompleted(ILogger logger, DomainId backupId, Uri url);
[LoggerMessage(Level = LogLevel.Error, Message = "Backup with job id {backupId} from URL '{url}' failed.")]
public static partial void LogRestoreJobFailed(ILogger logger, DomainId backupId, Uri url, Exception exception);
[LoggerMessage(Level = LogLevel.Error, Message = "Failed to clean up restore.")]
public static partial void LogFailedToCleanUpRestore(ILogger logger, Exception exception);
[LoggerMessage(Level = LogLevel.Error, Message = "Failed to handle yjs event.")]
public static partial void LogFailedToHandleYjsEvent(ILogger logger, Exception exception);
[LoggerMessage(Level = LogLevel.Warning, Message = "Cannot send email to {email}: No email subject configured for template {template}.")]
public static partial void LogNoEmailSubjectConfigured(ILogger logger, string email, string template);
[LoggerMessage(Level = LogLevel.Warning, Message = "Cannot send email to {email}: No email body configured for template {template}.")]
public static partial void LogNoEmailBodyConfigured(ILogger logger, string email, string template);
[LoggerMessage(Level = LogLevel.Error, Message = "Failed to send notification to {email}.")]
public static partial void LogFailedToSendNotification(ILogger logger, string email, Exception exception);
[LoggerMessage(Level = LogLevel.Error, Message = "Failed to query scheduled status changes-")]
public static partial void LogFailedToQueryScheduledStatusChanges(ILogger logger, Exception exception);
[LoggerMessage(Level = LogLevel.Error, Message = "Failed to execute scheduled status change for content '{contentId}'.")]
public static partial void LogFailedToExecuteScheduledStatusChange(ILogger logger, DomainId contentId, Exception exception);
[LoggerMessage(Level = LogLevel.Error, Message = "Failed to resolve field {field}.")]
public static partial void LogFailedToResolveField(ILogger logger, string field, Exception exception);
[LoggerMessage(Level = LogLevel.Error, Message = "Failed to resolve execute query.")]
public static partial void LogFailedToResolveQuery(ILogger logger, Exception exception);
[LoggerMessage(Level = LogLevel.Warning, Message = "Failed to invite user: Assigner {assignerId} not found.")]
public static partial void LogInvitationAssignerNotFound(ILogger logger, RefToken assignerId);
[LoggerMessage(Level = LogLevel.Warning, Message = "Failed to invite user: Assignee {assigneeId} not found.")]
public static partial void LogInvitationAssigneeNotFound(ILogger logger, string assigneeId);
[LoggerMessage(Level = LogLevel.Warning, Message = "Removed unfinished jobs for owner {ownerId} after start.")]
public static partial void LogRemovedUnfinishedJobs(ILogger logger, DomainId ownerId);
[LoggerMessage(Level = LogLevel.Information, Message = "Clearing jobs for owner {ownerId}.")]
public static partial void LogClearingJobs(ILogger logger, DomainId ownerId);
[LoggerMessage(Level = LogLevel.Information, Message = "Starting new backup with backup id '{backupId}' for owner {ownerId}.")]
public static partial void LogStartingJob(ILogger logger, DomainId backupId, DomainId ownerId);
[LoggerMessage(Level = LogLevel.Error, Message = "Failed to run job with ID {jobId}.")]
public static partial void LogFailedToRunJob(ILogger logger, DomainId jobId, Exception exception);
[LoggerMessage(Level = LogLevel.Information, Message = "Adding rule job for Rule(trigger={ruleTrigger})")]
public static partial void LogAddingRuleJob(ILogger logger, string ruleTrigger);
[LoggerMessage(Level = LogLevel.Warning, Message = "Failed to run rule with ID {ruleId}, continue with next job.")]
public static partial void LogFailedToRunRule(ILogger logger, DomainId? ruleId, Exception exception);
[LoggerMessage(Level = LogLevel.Error, Message = "Failed to execute search from source {source} with query '{query}'.")]
public static partial void LogFailedToExecuteSearch(ILogger logger, string source, string query, Exception exception);
}

6
backend/src/Squidex.Domain.Apps.Entities/Rules/RuleQueueWriter.cs

@ -35,8 +35,10 @@ public sealed class RuleQueueWriter(IFlowManager<FlowEventContext> flowManager,
writes.Add(result.Job.Value);
log?.LogInformation("Adding rule job for Rule(trigger={ruleTrigger})",
result.Rule.Trigger.GetType().Name);
if (log != null)
{
LogMessages.LogAddingRuleJob(log, result.Rule.Trigger.GetType().Name);
}
var today = Clock.GetCurrentInstant().ToDateOnly();

6
backend/src/Squidex.Domain.Apps.Entities/Rules/Runner/RuleRunnerJob.cs

@ -171,8 +171,7 @@ public sealed class RuleRunnerJob : IJobRunner
throw result.EnrichmentError;
}
log.LogWarning(result.EnrichmentError, "Failed to run rule with ID {ruleId}, continue with next job.",
result.Rule?.Id);
LogMessages.LogFailedToRunRule(log, result.Rule?.Id, result.EnrichmentError);
}
}
}
@ -212,8 +211,7 @@ public sealed class RuleRunnerJob : IJobRunner
throw result.EnrichmentError;
}
log.LogWarning(result.EnrichmentError, "Failed to run rule with ID {ruleId}, continue with next job.",
result.Rule?.Id);
LogMessages.LogFailedToRunRule(log, result.Rule?.Id, result.EnrichmentError);
}
}
}

2
backend/src/Squidex.Domain.Apps.Entities/Search/SearchManager.cs

@ -37,7 +37,7 @@ public sealed class SearchManager(IEnumerable<ISearchSource> searchSources, ILog
}
catch (Exception ex)
{
log.LogError(ex, "Failed to execute search from source {source} with query '{query}'.", source, query);
LogMessages.LogFailedToExecuteSearch(log, source.GetType().Name, query, ex);
return Empty;
}
}

2
backend/src/Squidex.Domain.Users/DefaultUserService.cs

@ -195,7 +195,7 @@ public sealed class DefaultUserService(
}
catch (Exception ex2)
{
log.LogError(ex2, "Failed to cleanup user after creation failed.");
LogMessages.LogFailedToCleanupUser(log, ex2);
}
throw;

19
backend/src/Squidex.Domain.Users/LogMessages.cs

@ -0,0 +1,19 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using Microsoft.Extensions.Logging;
namespace Squidex.Domain.Users;
internal static partial class LogMessages
{
[LoggerMessage(Level = LogLevel.Error, Message = "Failed to cleanup user after creation failed.")]
public static partial void LogFailedToCleanupUser(ILogger logger, Exception exception);
[LoggerMessage(Level = LogLevel.Error, Message = "Identity operation failed: {errorMessage}.")]
public static partial void LogIdentityOperationFailed(ILogger logger, string errorMessage);
}

2
backend/src/Squidex.Domain.Users/UserManagerExtensions.cs

@ -46,7 +46,7 @@ internal static class UserManagerExtensions
var errorMessage = errorMessageBuilder.ToString();
log.LogError("Identity operation failed: {errorMessage}.", errorMessage);
LogMessages.LogIdentityOperationFailed(log, errorMessage);
throw new ValidationException(result.Errors.Select(x => new ValidationError(Localize(x))).ToList());
}

2
backend/src/Squidex.Infrastructure/Commands/DomainObject.cs

@ -152,7 +152,7 @@ public abstract partial class DomainObject<T> : IAggregate where T : Entity, new
}
catch (Exception ex)
{
log.LogError(ex, "Failed to repair snapshot for domain object of type {type} with ID {id}.", GetType(), UniqueId);
LogMessages.LogFailedToRepairDomainObjectSnapshot(log, GetType(), UniqueId, ex);
}
}

10
backend/src/Squidex.Infrastructure/Commands/LogCommandMiddleware.cs

@ -20,7 +20,7 @@ public sealed class LogCommandMiddleware(ILogger<LogCommandMiddleware> log) : IC
{
if (log.IsEnabled(LogLevel.Debug))
{
log.LogDebug("Command {command} with ID {id} started.", type, context.ContextId);
LogMessages.LogCommandStarted(log, type, context.ContextId);
}
var watch = ValueStopwatch.StartNew();
@ -28,22 +28,22 @@ public sealed class LogCommandMiddleware(ILogger<LogCommandMiddleware> log) : IC
{
await next(context, ct);
log.LogInformation("Command {command} with ID {id} succeeded.", type, context.ContextId);
LogMessages.LogCommandSucceeded(log, type, context.ContextId);
}
finally
{
log.LogInformation("Command {command} with ID {id} completed after {time}ms.", type, context.ContextId, watch.Stop());
LogMessages.LogCommandCompleted(log, type, context.ContextId, watch.Stop());
}
}
catch (Exception ex)
{
log.LogError(ex, "Command {command} with ID {id} failed.", type, context.ContextId);
LogMessages.LogCommandFailed(log, type, context.ContextId, ex);
throw;
}
if (!context.IsCompleted)
{
log.LogCritical("Command {command} with ID {id} not handled.", type, context.ContextId);
LogMessages.LogCommandNotHandled(log, type, context.ContextId);
}
}
}

2
backend/src/Squidex.Infrastructure/Commands/Rebuilder.cs

@ -105,7 +105,7 @@ public class Rebuilder(
}
catch (Exception ex)
{
log.LogWarning(ex, "Found corrupt domain object of type {type} with ID {id}.", typeof(T), id);
LogMessages.LogFoundCorruptDomainObject(log, typeof(T), id, ex);
Interlocked.Increment(ref handlerErrors);
}
}

11
backend/src/Squidex.Infrastructure/EventSourcing/Consume/EventConsumerProcessor.cs

@ -69,7 +69,7 @@ public class EventConsumerProcessor : IEventSubscriber<ParsedEvents>
}
catch (Exception ex)
{
log.LogCritical(ex, "Failed to complete consumer.");
LogMessages.LogFailedToCompleteConsumer(log, ex);
}
}
@ -107,7 +107,7 @@ public class EventConsumerProcessor : IEventSubscriber<ParsedEvents>
if (logWindow.CanRetryAfterFailure())
{
log.LogError(exception, "Failed to handle event.");
LogMessages.LogFailedToHandleEvent(log, exception);
}
}, State.Position);
}
@ -215,8 +215,7 @@ public class EventConsumerProcessor : IEventSubscriber<ParsedEvents>
ex = new AggregateException(ex, unsubscribeException);
}
log.LogCritical(ex, "Failed to update consumer {consumer} at position {position} from {caller}.",
eventConsumer.Name, position, caller);
LogMessages.LogFailedToUpdateConsumer(log, eventConsumer.Name, position, caller, ex);
State = previousState.Stopped(ex);
}
@ -233,7 +232,7 @@ public class EventConsumerProcessor : IEventSubscriber<ParsedEvents>
{
if (log.IsEnabled(LogLevel.Debug))
{
log.LogDebug("Event consumer {consumer} reset started", eventConsumer.Name);
LogMessages.LogEventConsumerResetStarted(log, eventConsumer.Name);
}
var watch = ValueStopwatch.StartNew();
@ -243,7 +242,7 @@ public class EventConsumerProcessor : IEventSubscriber<ParsedEvents>
}
finally
{
log.LogDebug("Event consumer {consumer} reset completed after {time}ms.", eventConsumer.Name, watch.Stop());
LogMessages.LogEventConsumerResetCompleted(log, eventConsumer.Name, watch.Stop());
}
}

2
backend/src/Squidex.Infrastructure/Log/BackgroundRequestLogStore.cs

@ -88,7 +88,7 @@ public sealed class BackgroundRequestLogStore : DisposableObjectBase, IRequestLo
}
catch (Exception ex)
{
log.LogError(ex, "Failed to track usage in background.");
LogMessages.LogTrackUsageFailed(log, ex);
}
finally
{

64
backend/src/Squidex.Infrastructure/LogMessages.cs

@ -0,0 +1,64 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using Microsoft.Extensions.Logging;
namespace Squidex.Infrastructure;
internal static partial class LogMessages
{
[LoggerMessage(Level = LogLevel.Debug, Message = "Command {command} with ID {id} started.")]
public static partial void LogCommandStarted(ILogger logger, Type command, DomainId id);
[LoggerMessage(Level = LogLevel.Information, Message = "Command {command} with ID {id} succeeded.")]
public static partial void LogCommandSucceeded(ILogger logger, Type command, DomainId id);
[LoggerMessage(Level = LogLevel.Information, Message = "Command {command} with ID {id} completed after {time}ms.")]
public static partial void LogCommandCompleted(ILogger logger, Type command, DomainId id, long time);
[LoggerMessage(Level = LogLevel.Error, Message = "Command {command} with ID {id} failed.")]
public static partial void LogCommandFailed(ILogger logger, Type command, DomainId id, Exception exception);
[LoggerMessage(Level = LogLevel.Critical, Message = "Command {command} with ID {id} not handled.")]
public static partial void LogCommandNotHandled(ILogger logger, Type command, DomainId id);
[LoggerMessage(Level = LogLevel.Critical, Message = "Failed to complete consumer.")]
public static partial void LogFailedToCompleteConsumer(ILogger logger, Exception exception);
[LoggerMessage(Level = LogLevel.Error, Message = "Failed to handle event.")]
public static partial void LogFailedToHandleEvent(ILogger logger, Exception exception);
[LoggerMessage(Level = LogLevel.Critical, Message = "Failed to update consumer {consumer} at position {position} from {caller}.")]
public static partial void LogFailedToUpdateConsumer(ILogger logger, string consumer, string? position, string? caller, Exception exception);
[LoggerMessage(Level = LogLevel.Debug, Message = "Event consumer {consumer} reset started")]
public static partial void LogEventConsumerResetStarted(ILogger logger, string consumer);
[LoggerMessage(Level = LogLevel.Debug, Message = "Event consumer {consumer} reset completed after {time}ms.")]
public static partial void LogEventConsumerResetCompleted(ILogger logger, string consumer, long time);
[LoggerMessage(Level = LogLevel.Information, Message = "Migration {migration} started.")]
public static partial void LogMigrationStarted(ILogger logger, string migration);
[LoggerMessage(Level = LogLevel.Information, Message = "Migration {migration} completed after {time}ms.")]
public static partial void LogMigrationCompleted(ILogger logger, string migration, long time);
[LoggerMessage(Level = LogLevel.Critical, Message = "Migration {migration} failed.")]
public static partial void LogMigrationFailed(ILogger logger, string migration, Exception exception);
[LoggerMessage(Level = LogLevel.Information, Message = "Could not acquire lock to start migrating. Trying again in {time}ms.")]
public static partial void LogMigrationLockRetry(ILogger logger, int time);
[LoggerMessage(Level = LogLevel.Error, Message = "Failed to track usage in background.")]
public static partial void LogTrackUsageFailed(ILogger logger, Exception exception);
[LoggerMessage(Level = LogLevel.Error, Message = "Failed to repair snapshot for domain object of type {type} with ID {id}.")]
public static partial void LogFailedToRepairDomainObjectSnapshot(ILogger logger, Type type, DomainId id, Exception exception);
[LoggerMessage(Level = LogLevel.Warning, Message = "Found corrupt domain object of type {type} with ID {id}.")]
public static partial void LogFoundCorruptDomainObject(ILogger logger, Type type, DomainId id, Exception exception);
}

8
backend/src/Squidex.Infrastructure/Migrations/Migrator.cs

@ -41,7 +41,7 @@ public sealed class Migrator(
{
var name = migration.ToString()!;
log.LogInformation("Migration {migration} started.", name);
LogMessages.LogMigrationStarted(log, name);
try
{
@ -49,11 +49,11 @@ public sealed class Migrator(
await migration.UpdateAsync(ct);
log.LogInformation("Migration {migration} completed after {time}ms.", name, watch.Stop());
LogMessages.LogMigrationCompleted(log, name, watch.Stop());
}
catch (Exception ex)
{
log.LogCritical(ex, "Migration {migration} failed.", name);
LogMessages.LogMigrationFailed(log, name, ex);
throw new MigrationFailedException(name, ex);
}
}
@ -76,7 +76,7 @@ public sealed class Migrator(
{
while (!await migrationStatus.TryLockAsync(ct))
{
log.LogInformation("Could not acquire lock to start migrating. Trying again in {time}ms.", LockWaitMs);
LogMessages.LogMigrationLockRetry(log, LockWaitMs);
await Task.Delay(LockWaitMs, ct);
}
}

2
backend/src/Squidex.Infrastructure/UsageTracking/BackgroundUsageTracker.cs

@ -82,7 +82,7 @@ public sealed class BackgroundUsageTracker : DisposableObjectBase, IUsageTracker
}
catch (Exception ex)
{
log.LogError(ex, "Failed to track usage in background.");
LogMessages.LogTrackUsageFailed(log, ex);
}
finally
{

2
backend/src/Squidex.Web/ApiExceptionFilterAttribute.cs

@ -34,7 +34,7 @@ public sealed class ApiExceptionFilterAttribute : ActionFilterAttribute, IExcept
{
var log = context.HttpContext.RequestServices.GetRequiredService<ILogger<ApiExceptionFilterAttribute>>();
log.LogError(unhandled, "An unexpected exception has occurred.");
LogMessages.LogUnexpectedException(log, unhandled);
}
context.Result = GetResult(error);

35
backend/src/Squidex.Web/LogMessages.cs

@ -0,0 +1,35 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using Microsoft.Extensions.Logging;
using Squidex.Infrastructure;
namespace Squidex.Web;
internal static partial class LogMessages
{
[LoggerMessage(Level = LogLevel.Error, Message = "An unexpected exception has occurred.")]
public static partial void LogUnexpectedException(ILogger logger, Exception exception);
[LoggerMessage(Level = LogLevel.Critical, Message = "Failed to send result.")]
public static partial void LogFailedToSendResult(ILogger logger, Exception exception);
[LoggerMessage(Level = LogLevel.Warning, Message = "Cannot find app with the given name {name}.")]
public static partial void LogCannotFindAppByName(ILogger logger, string name);
[LoggerMessage(Level = LogLevel.Warning, Message = "Authenticated user has no permission to access the app {name} with ID {id}.")]
public static partial void LogNoPermissionToAccessApp(ILogger logger, DomainId id, string name);
[LoggerMessage(Level = LogLevel.Warning, Message = "Cannot find team with the given id {id}.")]
public static partial void LogCannotFindTeamById(ILogger logger, string id);
[LoggerMessage(Level = LogLevel.Warning, Message = "Authenticated user has no permission to access the team with ID {id}.")]
public static partial void LogNoPermissionToAccessTeam(ILogger logger, DomainId id);
[LoggerMessage(Level = LogLevel.Error, Message = "Error while handling api key.")]
public static partial void LogErrorHandlingApiKey(ILogger logger, Exception exception);
}

2
backend/src/Squidex.Web/Pipeline/ApiKeyHandler.cs

@ -75,7 +75,7 @@ public sealed class ApiKeyHandler(
}
catch (Exception ex)
{
Logger.LogError(ex, "Error while handling api key.");
LogMessages.LogErrorHandlingApiKey(Logger, ex);
throw;
}

12
backend/src/Squidex.Web/Pipeline/AppResolver.cs

@ -41,7 +41,10 @@ public sealed class AppResolver(IAppProvider appProvider) : IAsyncActionFilter
{
var log = context.HttpContext.RequestServices?.GetService<ILogger<AppResolver>>();
log?.LogWarning("Cannot find app with the given name {name}.", appName);
if (log != null)
{
LogMessages.LogCannotFindAppByName(log, appName);
}
context.Result = new NotFoundResult();
return;
@ -97,9 +100,10 @@ public sealed class AppResolver(IAppProvider appProvider) : IAsyncActionFilter
{
var log = context.HttpContext.RequestServices?.GetService<ILogger<AppResolver>>();
log?.LogWarning("Authenticated user has no permission to access the app {name} with ID {id}.",
app.Id,
app.Name);
if (log != null)
{
LogMessages.LogNoPermissionToAccessApp(log, app.Id, app.Name);
}
context.Result = new NotFoundResult();
}

2
backend/src/Squidex.Web/Pipeline/FileCallbackResultExecutor.cs

@ -54,7 +54,7 @@ public sealed class FileCallbackResultExecutor(ILoggerFactory loggerFactory) : F
response.Headers.Clear();
response.StatusCode = 404;
Logger.LogCritical(new EventId(99), e, "Failed to send result.");
LogMessages.LogFailedToSendResult(Logger, e);
}
else
{

2
backend/src/Squidex.Web/Pipeline/RequestExceptionMiddleware.cs

@ -37,7 +37,7 @@ public sealed class RequestExceptionMiddleware(RequestDelegate next)
}
catch (Exception ex)
{
log.LogError(ex, "An unexpected exception has occurred.");
LogMessages.LogUnexpectedException(log, ex);
if (!context.Response.HasStarted)
{

10
backend/src/Squidex.Web/Pipeline/TeamResolver.cs

@ -41,7 +41,10 @@ public sealed class TeamResolver(IAppProvider appProvider) : IAsyncActionFilter
{
var log = context.HttpContext.RequestServices?.GetService<ILogger<TeamResolver>>();
log?.LogWarning("Cannot find team with the given id {id}.", teamId);
if (log != null)
{
LogMessages.LogCannotFindTeamById(log, teamId);
}
context.Result = new NotFoundResult();
return;
@ -69,7 +72,10 @@ public sealed class TeamResolver(IAppProvider appProvider) : IAsyncActionFilter
{
var log = context.HttpContext.RequestServices?.GetService<ILogger<AppResolver>>();
log?.LogWarning("Authenticated user has no permission to access the team with ID {id}.", team.Id);
if (log != null)
{
LogMessages.LogNoPermissionToAccessTeam(log, team.Id);
}
context.Result = new NotFoundResult();
}

6
backend/src/Squidex/Areas/Api/Controllers/Users/UsersController.cs

@ -98,7 +98,7 @@ public sealed class UsersController(
}
catch (Exception ex)
{
log.LogError(ex, "Failed to return users, returning empty results.");
LogMessages.LogFailedToReturnUsers(log, ex);
}
return Ok(Array.Empty<UserDto>());
@ -129,7 +129,7 @@ public sealed class UsersController(
}
catch (Exception ex)
{
log.LogError(ex, "Failed to return user, returning empty results.");
LogMessages.LogFailedToReturnUser(log, ex);
}
return NotFound();
@ -202,7 +202,7 @@ public sealed class UsersController(
}
catch (Exception ex)
{
log.LogError(ex, "Failed to return user picture, returning fallback image.");
LogMessages.LogFailedToReturnUserPicture(log, ex);
}
return new FileStreamResult(new MemoryStream(AvatarBytes), "image/png");

2
backend/src/Squidex/Areas/IdentityServer/Config/CreateAdminInitializer.cs

@ -84,7 +84,7 @@ public sealed class CreateAdminInitializer(IServiceProvider serviceProvider) : I
{
var log = serviceProvider.GetRequiredService<ILogger<CreateAdminInitializer>>();
log.LogError(ex, "Failed to create administrator.");
LogMessages.LogFailedToCreateAdministrator(log, ex);
}
}

25
backend/src/Squidex/LogMessages.cs

@ -0,0 +1,25 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using Microsoft.Extensions.Logging;
namespace Squidex;
internal static partial class LogMessages
{
[LoggerMessage(Level = LogLevel.Error, Message = "Failed to create administrator.")]
public static partial void LogFailedToCreateAdministrator(ILogger logger, Exception exception);
[LoggerMessage(Level = LogLevel.Error, Message = "Failed to return users, returning empty results.")]
public static partial void LogFailedToReturnUsers(ILogger logger, Exception exception);
[LoggerMessage(Level = LogLevel.Error, Message = "Failed to return user, returning empty results.")]
public static partial void LogFailedToReturnUser(ILogger logger, Exception exception);
[LoggerMessage(Level = LogLevel.Error, Message = "Failed to return user picture, returning fallback image.")]
public static partial void LogFailedToReturnUserPicture(ILogger logger, Exception exception);
}

3
backend/tests/Squidex.Domain.Apps.Entities.Tests/Collaboration/EmailUserNotificationsTests.cs

@ -27,6 +27,9 @@ public class EmailUserNotificationsTests : GivenContext
public EmailUserNotificationsTests()
{
A.CallTo(() => log.IsEnabled(A<LogLevel>._))
.Returns(true);
A.CallTo(() => urlGenerator.UI())
.Returns("my-ui");

3
backend/tests/Squidex.Domain.Apps.Entities.Tests/Invitation/InvitationEventConsumerTests.cs

@ -34,6 +34,9 @@ public class InvitationEventConsumerTests : GivenContext
public InvitationEventConsumerTests()
{
A.CallTo(() => log.IsEnabled(A<LogLevel>._))
.Returns(true);
A.CallTo(() => userNotifications.IsActive)
.Returns(true);

3
backend/tests/Squidex.Infrastructure.Tests/Migrations/MigratorTests.cs

@ -75,6 +75,9 @@ public class MigratorTests
{
ct = cts.Token;
A.CallTo(() => log.IsEnabled(A<LogLevel>._))
.Returns(true);
A.CallTo(() => path.GetNext(A<int>._))
.ReturnsLazily((int version) =>
{

Loading…
Cancel
Save