Created
October 28, 2025 17:56
-
-
Save boarnoah/7413168d37e8d8558ccf6a4979e7463e to your computer and use it in GitHub Desktop.
Experimenting with using Log
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #:sdk Microsoft.NET.Sdk.Worker | |
| #:package Microsoft.Extensions.Hosting@9.0.10 | |
| #:package Microsoft.Extensions.Telemetry@9.10.0 | |
| // https://github.com/dotnet/runtime/issues/35995 | |
| // https://learn.microsoft.com/en-us/dotnet/core/enrichment/custom-enricher | |
| using System.Text.Json; | |
| using Microsoft.Extensions.Diagnostics.Enrichment; | |
| var builder = Host.CreateApplicationBuilder(args); | |
| builder.Services.AddScoped<MyScopedService>(); | |
| // Adds a service, very similar to HttpContextAccessor, in this case to hold tenant context info | |
| builder.Services.AddSingleton<TenantContextAccessor>(); | |
| builder.Services.AddLogEnricher<TenantLogEnricher>(); | |
| builder.Logging.AddJsonConsole( | |
| options => options.JsonWriterOptions = new JsonWriterOptions() | |
| { | |
| Indented = true | |
| } | |
| ); | |
| builder.Logging.EnableEnrichment(); | |
| builder.Services.AddHostedService<Worker>(); | |
| var host = builder.Build(); | |
| host.Run(); | |
| public class Worker( | |
| ILogger<Worker> logger, | |
| IHostApplicationLifetime appLifeTime, | |
| TenantContextAccessor tenantContextAccessor, | |
| IServiceScopeFactory scopeFactory | |
| ) : BackgroundService | |
| { | |
| protected override async Task ExecuteAsync(CancellationToken stoppingToken) | |
| { | |
| var tenants = new List<string>{ "TenantA", "TenantB"}; | |
| var i = 0; | |
| await Parallel.ForEachAsync(tenants, stoppingToken, async (tenant, token) => | |
| { | |
| var tenantLetter = tenant.Last().ToString(); | |
| logger.LogInformation("Starting processing for a tenant {TenantLetter} in separate thread", tenantLetter); | |
| // ILogEnrichers have to be registered as singletons to work -> which in turn restricts us to only inject | |
| // other singletons to them, hence the need for this HttpContextAccessor-like pattern | |
| tenantContextAccessor.TenantContext = new TenantContextObject() | |
| { | |
| Tenant = tenant | |
| }; | |
| using var scope = scopeFactory.CreateScope(); | |
| var myService = scope.ServiceProvider.GetRequiredService<MyScopedService>(); | |
| myService.DoTheThing(tenantLetter); | |
| }); | |
| logger.LogInformation("All done"); | |
| appLifeTime.StopApplication(); | |
| } | |
| } | |
| public class MyScopedService( | |
| ILogger<MyScopedService> logger | |
| ) { | |
| public void DoTheThing(string i){ | |
| logger.LogInformation("Done the thing: {thing}", i); | |
| } | |
| } | |
| internal class TenantLogEnricher(TenantContextAccessor tenantContext) : ILogEnricher | |
| { | |
| public void Enrich(IEnrichmentTagCollector collector) | |
| { | |
| if (!string.IsNullOrEmpty(tenantContext.TenantContext?.Tenant)) | |
| { | |
| collector.Add("Tenant", tenantContext.TenantContext.Tenant); | |
| } | |
| } | |
| } | |
| // Ripped from https://github.com/dotnet/aspnetcore/blob/5dde6e4691f73763cc31ce3934e6a37fd9707188/src/Http/Http/src/HttpContextAccessor.cs | |
| public class TenantContextAccessor | |
| { | |
| private static readonly AsyncLocal<TenantContextHolder> _tenantContextCurrent = new(); | |
| public TenantContextObject? TenantContext | |
| { | |
| get | |
| { | |
| return _tenantContextCurrent.Value?.Context; | |
| } | |
| set | |
| { | |
| if (_tenantContextCurrent.Value is not null) | |
| { | |
| _tenantContextCurrent.Value.Context = null; | |
| } | |
| if (value != null) | |
| { | |
| // Use an object indirection to hold the HttpContext in the AsyncLocal, | |
| // so it can be cleared in all ExecutionContexts when its cleared. | |
| _tenantContextCurrent.Value = new TenantContextHolder { Context = value }; | |
| } | |
| } | |
| } | |
| private sealed class TenantContextHolder | |
| { | |
| public TenantContextObject? Context; | |
| } | |
| } | |
| public class TenantContextObject | |
| { | |
| public string Tenant { get; set; } | |
| } |
Author
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
You get nice structured logs with your properties like:
A much better state than how something similar with scopes look: