Skip to content

Commit

Permalink
improve logging for hangfire and audit
Browse files Browse the repository at this point in the history
  • Loading branch information
pwelter34 committed Aug 23, 2024
1 parent 806bd44 commit 7b9b544
Show file tree
Hide file tree
Showing 18 changed files with 256 additions and 41 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -349,3 +349,4 @@ MigrationBackup/
*.GhostDoc.xml
*.csv
*.txt
*.clef
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using MediatR;
using MediatR.CommandQuery.Models;

namespace Tracker.WebService.Domain.Commands;

public class BackgroundUpdateCommand : IRequest<CompleteModel>
{

public BackgroundUpdateCommand(int id)
{
Id = id;
}

public int Id { get; }


public override string ToString()
{
return $"Background Update {Id}";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
using System.Threading;
using System.Threading.Tasks;

using MediatR.CommandQuery.Handlers;
using MediatR.CommandQuery.Models;

using Microsoft.Extensions.Logging;

using Tracker.WebService.Domain.Commands;

namespace Tracker.WebService.Domain.Handlers;

public class BackgroundUpdateHandler : RequestHandlerBase<BackgroundUpdateCommand, CompleteModel>
{
public BackgroundUpdateHandler(ILoggerFactory loggerFactory) : base(loggerFactory)
{
}

protected override async Task<CompleteModel> Process(BackgroundUpdateCommand request, CancellationToken cancellationToken)
{
Logger.LogInformation("Process Background Job: {Request}", request);

await Task.Delay(500, cancellationToken);

return new CompleteModel { Successful = true };
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using MediatR;
using MediatR.CommandQuery.Models;

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;

using Tracker.WebService.Domain.Commands;
using Tracker.WebService.Domain.Handlers;

// ReSharper disable once CheckNamespace
namespace Tracker.WebService.Domain;

public class JobServiceRegistration
{
[RegisterServices]
public void Register(IServiceCollection services)
{
services.TryAddTransient<IRequestHandler<BackgroundUpdateCommand, CompleteModel>, BackgroundUpdateHandler>();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
using System;
using System.Security.Claims;
using System.Threading;
using System.Threading.Tasks;

using MediatR;
using MediatR.CommandQuery.Endpoints;
using MediatR.CommandQuery.Hangfire;
using MediatR.CommandQuery.Models;

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Routing;

using Tracker.WebService.Domain.Commands;

namespace Tracker.WebService.Endpoints;

[RegisterTransient<IFeatureEndpoint>(Duplicate = DuplicateStrategy.Append)]
public class JobEndpoint : MediatorEndpointBase
{
public JobEndpoint(IMediator mediator) : base(mediator)
{
EntityName = "Jobs";
RoutePrefix = $"/api/{EntityName}";
}

public string EntityName { get; }

public string RoutePrefix { get; }


public override void AddRoutes(IEndpointRouteBuilder app)
{
var group = app.MapGroup(RoutePrefix);

MapGroup(group);
}

protected virtual void MapGroup(RouteGroupBuilder group)
{
group
.MapPost("{id}", RunJob)
.WithTags(EntityName)
.WithName($"RunJob")
.WithSummary("Run job id")
.WithDescription("Run job id");
}

private async Task RunJob(
[FromRoute] int id,
ClaimsPrincipal? user = default,
CancellationToken cancellationToken = default)
{
var command = new BackgroundUpdateCommand(id);
await Mediator.Enqueue(command, cancellationToken);
}
}
95 changes: 74 additions & 21 deletions samples/Tracker.WebService.EntityFrameworkCore/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,22 @@
using System.Text.Json.Serialization;
using System.Threading.Tasks;

using Hangfire;
using Hangfire.SqlServer;

using MediatR.CommandQuery.Endpoints;
using MediatR.CommandQuery.Hangfire;

using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;

using Serilog;
using Serilog.Events;
using Serilog.Formatting.Compact;

using Tracker.WebService.Domain;

Expand All @@ -22,10 +31,6 @@ public static class Program

public static async Task<int> Main(string[] args)
{
// azure home directory
var homeDirectory = Environment.GetEnvironmentVariable("HOME") ?? ".";
var logDirectory = Path.Combine(homeDirectory, "LogFiles");

Log.Logger = new LoggerConfiguration()
.MinimumLevel.Verbose()
.MinimumLevel.Override("Microsoft", LogEventLevel.Information)
Expand All @@ -39,23 +44,7 @@ public static async Task<int> Main(string[] args)

var builder = WebApplication.CreateBuilder(args);

builder.Host
.UseSerilog((context, services, configuration) => configuration
.ReadFrom.Configuration(context.Configuration)
.ReadFrom.Services(services)
.Enrich.FromLogContext()
.Enrich.WithProperty("ApplicationName", builder.Environment.ApplicationName)
.Enrich.WithProperty("EnvironmentName", builder.Environment.EnvironmentName)
.WriteTo.Console(outputTemplate: OutputTemplate)
.WriteTo.File(
path: $"{logDirectory}/log.txt",
rollingInterval: RollingInterval.Day,
shared: true,
flushToDiskInterval: TimeSpan.FromSeconds(1),
outputTemplate: OutputTemplate,
retainedFileCountLimit: 10
)
);
ConfigureLogging(builder);

ConfigureServices(builder);

Expand All @@ -78,6 +67,34 @@ public static async Task<int> Main(string[] args)
}
}

private static void ConfigureLogging(WebApplicationBuilder builder)
{
string logDirectory = GetLoggingPath();

builder.Host
.UseSerilog((context, services, configuration) => configuration
.ReadFrom.Configuration(context.Configuration)
.ReadFrom.Services(services)
.MinimumLevel.Debug()
.MinimumLevel.Override("Microsoft", LogEventLevel.Information)
.MinimumLevel.Override("Microsoft.AspNetCore", LogEventLevel.Warning)
.MinimumLevel.Override("System.Net.Http.HttpClient", LogEventLevel.Debug)
.Enrich.FromLogContext()
.Enrich.WithProperty("ApplicationName", builder.Environment.ApplicationName)
.Enrich.WithProperty("EnvironmentName", builder.Environment.EnvironmentName)
.Filter.ByExcluding(logEvent => logEvent.Exception is OperationCanceledException)
.WriteTo.Console(outputTemplate: OutputTemplate)
.WriteTo.File(
formatter: new RenderedCompactJsonFormatter(),
path: $"{logDirectory}/log.clef",
shared: true,
flushToDiskInterval: TimeSpan.FromSeconds(1),
rollingInterval: RollingInterval.Day,
retainedFileCountLimit: 10
));
;
}

private static void ConfigureServices(WebApplicationBuilder builder)
{
var services = builder.Services;
Expand All @@ -98,6 +115,32 @@ private static void ConfigureServices(WebApplicationBuilder builder)
options.SerializerOptions.Converters.Add(new JsonStringEnumConverter(JsonNamingPolicy.CamelCase));
options.SerializerOptions.TypeInfoResolverChain.Add(DomainJsonContext.Default);
});

// hangfire options
services.TryAddSingleton(new SqlServerStorageOptions
{
PrepareSchemaIfNecessary = true,
EnableHeavyMigrations = true,
SqlClientFactory = Microsoft.Data.SqlClient.SqlClientFactory.Instance
});

services.AddHangfire((serviceProvider, globalConfiguration) =>
{
var configuration = serviceProvider.GetRequiredService<IConfiguration>();
var stroageOptions = serviceProvider.GetRequiredService<SqlServerStorageOptions>();
var connectionString = configuration.GetConnectionString("Tracker");

globalConfiguration
.SetDataCompatibilityLevel(CompatibilityLevel.Version_180)
.UseSimpleAssemblyNameTypeSerializer()
.UseSqlServerStorage(connectionString, stroageOptions)
.UseMediatR();
});

services.AddMediatorDispatcher();

services.AddHangfireServer();

}

private static void ConfigureMiddleware(WebApplication app)
Expand All @@ -114,6 +157,16 @@ private static void ConfigureMiddleware(WebApplication app)

app.UseAuthorization();

app.MapHangfireDashboard("/hangfire");
app.MapFeatureEndpoints();
}

private static string GetLoggingPath()
{
// azure home directory
var homeDirectory = Environment.GetEnvironmentVariable("HOME") ?? ".";
var logDirectory = Path.Combine(homeDirectory, "LogFiles");

return Path.GetFullPath(logDirectory);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,18 @@

<ItemGroup>
<PackageReference Include="FluentValidation.AspNetCore" Version="11.3.0" />
<PackageReference Include="Hangfire.AspNetCore" Version="1.8.14" />
<PackageReference Include="Hangfire.SqlServer" Version="1.8.14" />
<PackageReference Include="Injectio" Version="3.1.0" PrivateAssets="all" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="8.0.8" />
<PackageReference Include="Serilog.AspNetCore" Version="8.0.2" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.7.0" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.7.1" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\..\src\MediatR.CommandQuery.Endpoints\MediatR.CommandQuery.Endpoints.csproj" />
<ProjectReference Include="..\..\src\MediatR.CommandQuery.EntityFrameworkCore\MediatR.CommandQuery.EntityFrameworkCore.csproj" />
<ProjectReference Include="..\..\src\MediatR.CommandQuery.Hangfire\MediatR.CommandQuery.Hangfire.csproj" />
<ProjectReference Include="..\..\src\MediatR.CommandQuery.Mvc\MediatR.CommandQuery.Mvc.csproj" />
</ItemGroup>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
<PackageReference Include="FluentValidation.AspNetCore" Version="11.3.0" />
<PackageReference Include="Injectio" Version="3.1.0" />
<PackageReference Include="Serilog.AspNetCore" Version="8.0.2" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.7.0" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.7.1" />
</ItemGroup>

<ItemGroup>
Expand Down
1 change: 1 addition & 0 deletions src/Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="AssemblyMetadata.Generators" Version="2.0.0" PrivateAssets="All" />
<PackageReference Include="MinVer" Version="5.0.0" PrivateAssets="All" />
</ItemGroup>

Expand Down
37 changes: 30 additions & 7 deletions src/MediatR.CommandQuery.Audit/ChangeCollector.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@

using MediatR.CommandQuery.Definitions;

using Microsoft.Extensions.Logging;

namespace MediatR.CommandQuery.Audit;

/// <summary>
Expand All @@ -11,18 +13,21 @@ namespace MediatR.CommandQuery.Audit;
/// <typeparam name="TKey">The type of the key.</typeparam>
/// <typeparam name="TEntity">The type of the entity.</typeparam>
/// <seealso cref="MediatR.CommandQuery.Audit.IChangeCollector{TKey, TEntity}" />
public class ChangeCollector<TKey, TEntity> : IChangeCollector<TKey, TEntity>
public partial class ChangeCollector<TKey, TEntity> : IChangeCollector<TKey, TEntity>
where TEntity : IHaveIdentifier<TKey>, ITrackUpdated, ITrackHistory
{
private readonly IEntityComparer _entityComparer;
private readonly ILogger _logger;

/// <summary>
/// Initializes a new instance of the <see cref="ChangeCollector{TKey, TEntity}"/> class.
/// </summary>
/// <param name="entityComparer">The entity comparer.</param>
public ChangeCollector(IEntityComparer entityComparer)
/// <param name="loggerFactory">The logger factory.</param>
public ChangeCollector(IEntityComparer entityComparer, ILoggerFactory loggerFactory)
{
_entityComparer = entityComparer;
_logger = loggerFactory.CreateLogger<ChangeCollector<TKey, TEntity>>();
}

/// <summary>
Expand Down Expand Up @@ -65,12 +70,23 @@ public IReadOnlyList<AuditRecord<TKey>> CollectGroupChanges<TProperty>(IEnumerab

foreach (var group in entities.GroupBy(groupSelector))
{
var groupList = group
.OrderBy(p => p.PeriodEnd)
.ToList();
try
{
LogCollectingChanges(_logger, entityName, group.Key);

Check warning on line 75 in src/MediatR.CommandQuery.Audit/ChangeCollector.cs

View workflow job for this annotation

GitHub Actions / build

Possible null reference argument for parameter 'entityKey' in 'void ChangeCollector<TKey, TEntity>.LogCollectingChanges(ILogger logger, string entityName, object entityKey)'.

Check warning on line 75 in src/MediatR.CommandQuery.Audit/ChangeCollector.cs

View workflow job for this annotation

GitHub Actions / build

Possible null reference argument for parameter 'entityKey' in 'void ChangeCollector<TKey, TEntity>.LogCollectingChanges(ILogger logger, string entityName, object entityKey)'.

Check warning on line 75 in src/MediatR.CommandQuery.Audit/ChangeCollector.cs

View workflow job for this annotation

GitHub Actions / build

Possible null reference argument for parameter 'entityKey' in 'void ChangeCollector<TKey, TEntity>.LogCollectingChanges(ILogger logger, string entityName, object entityKey)'.

Check warning on line 75 in src/MediatR.CommandQuery.Audit/ChangeCollector.cs

View workflow job for this annotation

GitHub Actions / build

Possible null reference argument for parameter 'entityKey' in 'void ChangeCollector<TKey, TEntity>.LogCollectingChanges(ILogger logger, string entityName, object entityKey)'.

Check warning on line 75 in src/MediatR.CommandQuery.Audit/ChangeCollector.cs

View workflow job for this annotation

GitHub Actions / build

Possible null reference argument for parameter 'entityKey' in 'void ChangeCollector<TKey, TEntity>.LogCollectingChanges(ILogger logger, string entityName, object entityKey)'.

var groupList = group
.OrderBy(p => p.PeriodEnd)
.ToList();

var auditList = CollectChanges(groupList, entityName, descriptionFunction);
historyList.AddRange(auditList);
var auditList = CollectChanges(groupList, entityName, descriptionFunction);
historyList.AddRange(auditList);
}
catch (Exception ex)
{
LogCollectingError(_logger, entityName, group.Key, ex.Message, ex);

Check warning on line 86 in src/MediatR.CommandQuery.Audit/ChangeCollector.cs

View workflow job for this annotation

GitHub Actions / build

Possible null reference argument for parameter 'entityKey' in 'void ChangeCollector<TKey, TEntity>.LogCollectingError(ILogger logger, string entityName, object entityKey, string errorMessage, Exception? exception)'.

Check warning on line 86 in src/MediatR.CommandQuery.Audit/ChangeCollector.cs

View workflow job for this annotation

GitHub Actions / build

Possible null reference argument for parameter 'entityKey' in 'void ChangeCollector<TKey, TEntity>.LogCollectingError(ILogger logger, string entityName, object entityKey, string errorMessage, Exception? exception)'.

Check warning on line 86 in src/MediatR.CommandQuery.Audit/ChangeCollector.cs

View workflow job for this annotation

GitHub Actions / build

Possible null reference argument for parameter 'entityKey' in 'void ChangeCollector<TKey, TEntity>.LogCollectingError(ILogger logger, string entityName, object entityKey, string errorMessage, Exception? exception)'.

Check warning on line 86 in src/MediatR.CommandQuery.Audit/ChangeCollector.cs

View workflow job for this annotation

GitHub Actions / build

Possible null reference argument for parameter 'entityKey' in 'void ChangeCollector<TKey, TEntity>.LogCollectingError(ILogger logger, string entityName, object entityKey, string errorMessage, Exception? exception)'.

Check warning on line 86 in src/MediatR.CommandQuery.Audit/ChangeCollector.cs

View workflow job for this annotation

GitHub Actions / build

Possible null reference argument for parameter 'entityKey' in 'void ChangeCollector<TKey, TEntity>.LogCollectingError(ILogger logger, string entityName, object entityKey, string errorMessage, Exception? exception)'.

throw;
}
}

return historyList;
Expand Down Expand Up @@ -160,4 +176,11 @@ public IEnumerable<AuditRecord<TKey>> CollectChanges(IEnumerable<TEntity> entiti
return historyRecords;
}


[LoggerMessage(1, LogLevel.Debug, "Collecting changes for {EntityName} with key {EntityKey} ...")]
static partial void LogCollectingChanges(ILogger logger, string entityName, object entityKey);

[LoggerMessage(2, LogLevel.Error, "Error collecting changes for {EntityName} with key {EntityKey}: {ErrorMessage}")]
static partial void LogCollectingError(ILogger logger, string entityName, object entityKey, string errorMessage, Exception? exception);

}
Loading

0 comments on commit 7b9b544

Please sign in to comment.