Diagnostics and logging
Arc4u extends Microsoft.Extensions.Logging with a fluent API. It puts every log entry in one of three categories (Technical, Business or Monitoring), adds a standard set of properties (application, class, method, process, thread, user) and lets you attach your own properties without putting them in the message template. It is the logging layer that the other Arc4u packages use, and it follows the design principles: Arc4u adds a thin layer on top of what .NET already provides and does not replace it.
What it solves
With plain ILogger<T> you get a message and the values of its template. In a business application you also need to:
- tell apart what IT people read (Technical), what business people read (Business) and the periodic measures for dashboards (Monitoring), so each audience gets its own sink or filter;
- find, on every entry, which application, class, method, process, thread and user produced it, without repeating that in each message;
- add a value to one entry (an order number, a duration) without polluting the message text.
Arc4u replaces the ILogger<T> registration with a wrapper that adds these properties and forwards the entry to the logger created by the ILoggerFactory. Everything after that is standard .NET: the providers you configure (Serilog, OpenTelemetry, console) receive the entry and decide what to do with it. Arc4u does not ship a sink you must use, and Arc4u.Diagnostics does not reference Serilog.
flowchart LR
App["Your code<br/>ILogger#lt;T#gt;.Technical().Add(...).LogInformation(...)"] --> Wrapper["Arc4u logger wrapper<br/>adds category and standard properties"]
Provider["IAddPropertiesToLog<br/>(user, tenant, ...)"] --> Wrapper
Wrapper --> Factory["ILoggerFactory"]
Factory --> Serilog["Serilog<br/>(console, Seq, file, ...)"]
Factory --> Otel["OpenTelemetry logging"]
Note
Arc4u 8 also had TraceListener based logging. It was removed in Arc4u 9 because Serilog and OpenTelemetry cover it. See TraceListeners removed in the migration guide.
Packages involved
| Package | Use it for |
|---|---|
Arc4u.Diagnostics |
The fluent API (Technical(), Business(), Monitoring(), Add, AddIf, ...), AddILogger, the process monitoring (AddSystemMonitoring, obsolete) and the IActivitySourceFactory used for OpenTelemetry traces. |
Arc4u.Diagnostics.Serilog |
A one-line text formatter, the category filter and anonymizer sinks, and the helper that reads the Arc4u properties from a Serilog event. See Serilog. |
Arc4u.Diagnostics.Serilog.Sinks.RealmDb |
Sinks that keep the log entries in a Realm database or in memory, so a client application can show its own logs. See Client-side log storage. |
Arc4u.Diagnostics.Serilog and Arc4u.Diagnostics.Serilog.Sinks.RealmDb reference Arc4u.Diagnostics. The Arc4u package also references Arc4u.Diagnostics, and its AddApplicationContext() calls AddILogger() for you.
Install
dotnet add package Arc4u.Diagnostics --prerelease
To write to Serilog sinks, add the Serilog integration and the hosting package that connects Serilog to Microsoft.Extensions.Logging:
dotnet add package Arc4u.Diagnostics.Serilog --prerelease
dotnet add package Serilog.AspNetCore
Configuration
AddILogger() has no configuration section of its own. The only setting it reads is the application name written in the Application property of each entry.
| Key | Type | Default | Description |
|---|---|---|---|
Application.Configuration:Environment:LoggingName |
string |
name of the entry assembly | Value of the Application property. Bound to Arc4u.Configuration.ApplicationConfig by AddApplicationConfig, which is described in the configuration guide. |
Note
The LoggingLevel key that older Arc4u documentation showed in this section is not read by the code. Filter by level in your logging provider (for example the Serilog MinimumLevel).
Code
// Program.cs
using Arc4u.Dependency;
using Arc4u.Diagnostics;
using Arc4u.Diagnostics.Formatter;
using Serilog;
using Serilog.Events;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddILogger();
builder.Host.UseSerilog((context, configuration) => configuration
.MinimumLevel.Information()
.MinimumLevel.Override("Microsoft", LogEventLevel.Warning)
.WriteTo.Console(new SimpleTextFormatter()));
var app = builder.Build();
app.MapGet("/orders/{id:int}", (int id, ILogger<Program> logger) =>
{
logger.Technical().LogInformation("Reading order {OrderId}", id);
logger.Business().Add("Customer", "contoso").LogInformation("Order shipped");
return Results.Ok(id);
});
app.Run();
AddILogger (namespace Arc4u.Dependency) removes the existing ILogger<> registration and registers:
ILogger<T>as a transientLoggerWrapper<T>;- the non-generic
ILoggeras a transientILogger<DefaultLogger>, for static classes; - a
NullLoggerPropertiesunder the keys"Scoped"and"Transient"(see Add the user to every entry), unless you registered your own.
Calling GET /orders/7 writes these two lines (the RequestId, RequestPath and ConnectionId come from the ASP.NET Core logging scope, and the 32-character ActivityId is the trace id of the request; the long padding of the fixed columns is shortened here):
17/10/2026 10:27:15,904 Information Technical 24918 10 6e0460a043e459729ef37849c797cd9d Program - <Main>$ - Reading order 7 - {"OrderId":7,"RequestId":"0HNPC51POMDHV:00000001","RequestPath":"/orders/7","ConnectionId":"0HNPC51POMDHV"}
17/10/2026 10:27:15,912 Information Business 24918 10 6e0460a043e459729ef37849c797cd9d Program - <Main>$ - Order shipped - {"Customer":"contoso","RequestId":"0HNPC51POMDHV:00000001","RequestPath":"/orders/7","ConnectionId":"0HNPC51POMDHV"}
Each property belongs to one entry: OrderId (a template argument of the first entry) and Customer (added to the second) are not written on any other entry. See Properties belong to one entry.
Properties written on every entry
The logger adds these properties to each entry (the names are constants of LoggingConstants, and they cannot be used as your own keys):
| Property | Value |
|---|---|
Category |
Technical, Business, Monitoring (MessageCategory) or the name of your own category, written as a string. |
Application |
The LoggingName described above. |
SourceContext |
Full name of the class that logs (the T of ILogger<T>, or the type you pass for a non-generic ILogger). |
Method |
Name of the calling member, filled in by the compiler ([CallerMemberName]). |
Pid, Tid |
Id of the process and of the managed thread. |
ActivityId |
The TraceId of System.Diagnostics.Activity.Current, the same in every service of a distributed trace. Missing when there is no current activity. |
Identity |
Only when an IAddPropertiesToLog provider supplies it. |
Stacktrace |
Only after AddStackTrace(). |
Common scenarios
Choose the category and add properties
Technical(), Business() and Monitoring() are extension methods on ILogger<T>. Each one selects the category and returns an ILoggerWrapper<T> on which you chain Add... methods, then finish with any standard Log... method (LogTrace, LogDebug, LogInformation, LogWarning, LogError, LogCritical, Log).
flowchart LR
Start["ILogger#lt;T#gt;"] --> Category["Technical() / Business() / Monitoring()"]
Category --> Add["Add / AddIf / AddOrReplace / AddStackTrace / AddMemoryUsage<br/>(zero or more)"]
Add --> Add
Category --> Log["LogInformation(...) and the other Log methods"]
Add --> Log
using Arc4u.Diagnostics;
public class OrderService(ILogger<OrderService> logger)
{
public void Ship(int orderId, TimeSpan duration, bool verbose)
{
logger.Business()
.Add("OrderId", orderId)
.AddIf(verbose, "DurationMs", () => duration.TotalMilliseconds)
.LogInformation("Order shipped");
logger.Technical().AddStackTrace().LogWarning("Shipping took {Duration}", duration);
}
}
| Method | Description |
|---|---|
Add(key, value) |
Sets the property, replacing an existing value for the key. |
AddIf(condition, key, Func<object>) |
Same as Add, and the function is only called when the condition is true. |
AddOrReplace(key, value) |
Same as Add. |
AddOrReplaceIf(condition, key, Func<double>) |
Conditional variant for a numeric value. |
AddIfNotExist(key, value) |
Sets the property only when the key is not present and the value is not null. |
AddStackTrace() |
Adds the Stacktrace property: the stack trace of the logged exception, otherwise the current one. |
AddMemoryUsage() |
Adds a Memory property with GC.GetTotalMemory(false). |
A null or white-space key throws ArgumentNullException, and a reserved key throws ReservedLoggingKeyException.
The values of the message template ({Duration} above) are also written as properties, as with any structured logger. What Add gives you is a property that is not in the message text.
Note
The properties are written with the next entry only. See Properties belong to one entry.
Add your own category
A category is a short flag: Technical is 1, Business 2 and Monitoring 4. Your application can use the other bits (8, 16, ... up to 16384) for its own categories. Give the value a name with MessageCategoryRegistry, and write an extension method that calls Category(...):
using System.Runtime.CompilerServices;
using Arc4u.Diagnostics;
using Microsoft.Extensions.Logging;
public static class SecurityLoggerExtensions
{
public const MessageCategory SecurityCategory = (MessageCategory)8;
static SecurityLoggerExtensions() => MessageCategoryRegistry.Register("Security", SecurityCategory);
public static ILoggerWrapper<T> Security<T>(this ILogger<T> logger, [CallerMemberName] string methodName = "") =>
logger.Category(SecurityCategory, methodName);
}
logger.Security().Add("User", user).LogWarning("Access denied") then writes Category with the value Security, and works with everything that handles the predefined categories:
WriteTo.CategoryFilter(MessageCategory.Business | SecurityLoggerExtensions.SecurityCategory, sink)forwards it (see Serilog), and so does the expression filterCategory = 'Security'.SimpleTextFormatter,MemoryLogDBandRealmDBshowSecurity.
Keep these rules in mind:
- Forward
methodNametoCategory(...), otherwise theMethodproperty is the name of your extension method. - Register the name before the first entry is written. The static constructor above runs the first time
Security()is called, which is early enough. A process that only reads the entries (a log viewer reading the RealmDB store) must register the name too; otherwise it reads the category asTechnical, or as its number when it comes from the RealmDB store. Registerthrows anArgumentExceptionfor a predefined value or name, a numeric name, a value that is not positive, or a name or value already registered with another value or name. Registering the same name and value again does nothing.- Without registration,
Category((MessageCategory)8)still works, and the category is written as8. - For a non-generic
ILogger, useCategory(category, typeof(MyClass)).
Log from a static class
A static class cannot take an ILogger<T>. Inject or resolve the non-generic ILogger, and pass the type that should appear as the SourceContext. The overloads on ILogger exist for the Technical and Monitoring categories, plus Category(category, type) for any other. Technical<T>() is the generic form of Technical(typeof(T)); C# does not accept a static class as T, which is why the sample uses typeof.
using Arc4u.Diagnostics;
public static class Helpers
{
public static void Log(ILogger logger, string path)
{
logger.Technical(typeof(Helpers)).Add("Path", path).LogInformation("Cleaning up");
logger.Monitoring(typeof(Helpers)).Add("Files", 3).LogInformation("Cleanup done");
}
}
Log an exception
LogException is an extension of ILogger (namespace Arc4u.Diagnostics). It writes an Error entry with the message Exception: {Message}. Use the standard LogError(exception, ...) when you need your own message.
using Arc4u.Diagnostics;
public class Importer(ILogger<Importer> logger)
{
public void Run()
{
try
{
throw new InvalidOperationException("boom");
}
catch (Exception ex)
{
logger.Technical().LogException(ex);
}
}
}
When the exception is an AggregateException, the wrapper also writes one entry for each flattened inner exception.
Log with high-performance messages
The LoggerMessage source generator works on top of the fluent API: define the message as an extension method of ILogger and call it at the end of the chain. Arc4u uses this pattern for its own messages (for example LogMonitoring). The EventId you declare is forwarded to the logging providers.
using Arc4u.Diagnostics;
public static partial class OrderMessages
{
[LoggerMessage(EventId = 1001, Level = LogLevel.Information, Message = "Order {OrderId} shipped")]
public static partial void LogOrderShipped(this ILogger logger, int orderId);
}
public class Shipper(ILogger<Shipper> logger)
{
public void Ship(int orderId) => logger.Business().Add("Carrier", "dhl").LogOrderShipped(orderId);
}
Add the user to every entry
The wrapper asks an IAddPropertiesToLog for extra properties each time it writes an entry. When the logger is created, it picks the provider by key, depending on where it is resolved:
| The logger is resolved | Key | Examples |
|---|---|---|
| In a scope | "Scoped" |
A controller, a minimal API handler, a gRPC service, a scoped or transient service of a request, the timing middlewares, a CreateScope() in a background job. |
| From the root | "Transient" |
A singleton (a cache, a hosted service such as SystemResources) and its dependencies, even when the singleton is first requested during a request. |
AddILogger registers a NullLoggerProperties (no property) under both keys, so entries have no Identity until you register a provider.
A "Scoped" provider can depend on scoped services. The Arc4u package contains DefaultLoggingProperties, which reads the user from the scoped IApplicationContext. Register it under "Scoped", after AddApplicationContext() (namespace Arc4u.Security.Principal, package Arc4u), which registers IApplicationContext and calls AddILogger(). With the Arc4u source generators, you can instead list Arc4u.Diagnostics.DefaultLoggingProperties, Arc4u in Application.Dependency:RegisterTypes: it is exported with the key "Scoped".
using Arc4u.Diagnostics;
using Arc4u.Security.Principal;
public static class LoggingRegistration
{
public static void Register(WebApplicationBuilder builder)
{
builder.Services.AddApplicationContext();
builder.Services.AddKeyedScoped<IAddPropertiesToLog, DefaultLoggingProperties>("Scoped");
}
}
public class Handler(ILogger<Handler> logger)
{
public void Handle() => logger.Technical().LogInformation("Handled");
}
DefaultLoggingProperties only returns properties when the application context has data:
Identityis the email of the profile of theIApplicationContext.Principal(theemailclaim, see Claims and authorization), or the name of its identity when the identity provider gives no email. An email is personal data: remove it from the sinks that do not need it with the anonymizer. The principal is set by Arc4u authentication (see the server authentication guide); without one, the provider returns nothing.
ActivityId does not come from a provider: the wrapper writes the TraceId of the current System.Diagnostics.Activity itself. ASP.NET Core and gRPC start an activity for each request and propagate it to the services you call through the W3C traceparent header.
To add your own properties, implement IAddPropertiesToLog. Values that are null are ignored, and the keys of LoggingConstants such as Identity and ActivityId are the ones the Arc4u formatters read.
using Arc4u.Dependency;
using Arc4u.Diagnostics;
public class TenantProperties : IAddPropertiesToLog
{
public IDictionary<string, object> GetProperties() => new Dictionary<string, object>
{
[LoggingConstants.Identity] = "alice",
["Tenant"] = "contoso",
};
}
public static class TenantRegistration
{
public static void Register(IServiceCollection services)
{
services.AddILogger();
services.AddKeyedScoped<IAddPropertiesToLog, TenantProperties>("Scoped");
}
}
With that provider, an entry written by a logger resolved in a scope looks like this:
29/09/2026 14:53:47,920 Information Technical alice 28853 1 Program - <Main>$ - hello - {"Tenant":"contoso"}
Loggers of singletons in an ASP.NET Core host
A "Transient" provider must not depend on scoped services: a singleton's logger is created once from the root, so DefaultLoggingProperties registered as "Transient" makes the host fail to start in the Development environment (Cannot consume scoped service 'Arc4u.Security.Principal.IApplicationContext' from singleton ...), and in Production every singleton would log the principal of the first request.
In an ASP.NET Core host, AddHttpContextLoggingProperties() (namespace Arc4u.OAuth2.AspNetCore.Diagnostics, package Arc4u.OAuth2.AspNetCore) registers HttpContextLoggingProperties under "Transient". It has no scoped dependency: each time an entry is written, it asks the "Scoped" provider of the current HTTP request (HttpContext.RequestServices) for its properties. A singleton's logger then writes the identity of the request it runs in. Outside a request (a background service, startup), it adds nothing.
It only registers HttpContextLoggingProperties when no provider is registered under "Transient" yet. AddILogger(), which AddApplicationContext() calls, registers a NullLoggerProperties under that key, so call AddHttpContextLoggingProperties() before AddApplicationContext() (or AddILogger()): called after them, it does nothing and the loggers of singletons write no Identity.
using Arc4u.Diagnostics;
using Arc4u.OAuth2.AspNetCore.Diagnostics;
using Arc4u.Security.Principal;
builder.Services.AddHttpContextLoggingProperties();
builder.Services.AddApplicationContext();
builder.Services.AddKeyedScoped<IAddPropertiesToLog, DefaultLoggingProperties>("Scoped");
The Arc4u authentication extensions (AddJwtAuthentication, AddOidcAuthentication, AddHybridAuthentication) do not register it: call AddHttpContextLoggingProperties() yourself, before AddApplicationContext().
A provider you registered under "Transient" before is kept; it must not have scoped dependencies (such as TenantProperties above).
Log CPU and memory usage
Warning
AddSystemMonitoring() and SystemResources are obsolete and will be removed in a future major version. They write metrics as log entries: collect the .NET runtime and process metrics with OpenTelemetry instead, as shown below.
AddSystemMonitoring() (namespace Microsoft.Extensions.DependencyInjection) registers the hosted service SystemResources. Ten seconds after the host starts, and then every ten seconds, it writes one Monitoring entry with the message Cpu & Memory. These values are fixed for AddSystemMonitoring, which has no parameter to change them. To change them, register the service yourself: SystemResources has a constructor parameter internalPeriodInSeconds and a StartMonitoringDelayInSeconds property, for example services.AddHostedService(sp => new SystemResources(sp.GetRequiredService<ILogger<SystemResources>>(), 60) { StartMonitoringDelayInSeconds = 30 }). SystemResources needs the Arc4u logger: call AddILogger() (or AddApplicationContext()) as well.
The entry carries these properties: TotalCpuUsed, PrivilegedCpuUsed and UserCpuUsed (percent of the CPU capacity of all processors since the previous measure), and WorkingSet, NonPagedSystemMemory, PagedMemory, PagedSystemMemory, PrivateMemory and VirtualMemoryMemory (bytes, as reported by System.Diagnostics.Process). A real entry, as written by the text formatter:
29/09/2026 14:51:14,024 Information Technical 21459 8 Arc4u.Diagnostics.Monitoring.SystemResources - CollectData - Cpu & Memory - {"TotalCpuUsed":0.11178381082593415,"PrivilegedCpuUsed":0.037256095413329,"UserCpuUsed":0.07455391315139101,"WorkingSet":77561856,"NonPagedSystemMemory":0,"PagedMemory":0,"PagedSystemMemory":0,"PrivateMemory":0,"VirtualMemoryMemory":462388707328}
Replace it with OpenTelemetry metrics
.NET publishes the CPU and memory usage of the process on the built-in System.Runtime meter: dotnet.process.cpu.time (user and system CPU time, the UserCpuUsed and PrivilegedCpuUsed of SystemResources), dotnet.process.memory.working_set (WorkingSet), and the GC, thread pool and exception counters. Subscribe to it with the OpenTelemetry SDK (packages OpenTelemetry.Extensions.Hosting and an exporter such as OpenTelemetry.Exporter.OpenTelemetryProtocol):
using OpenTelemetry.Metrics;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddOpenTelemetry()
.WithMetrics(metrics => metrics.AddMeter("System.Runtime")
.AddOtlpExporter());
The exporter sends the values every 60 seconds by default; your metrics backend computes the CPU percentage from dotnet.process.cpu.time and keeps the history. For the other memory values (PrivateMemory, VirtualMemoryMemory), add the OpenTelemetry.Instrumentation.Process package and call AddProcessInstrumentation(): it publishes process.memory.usage and process.memory.virtual.
Use it next to OpenTelemetry
Arc4u.Diagnostics has no dependency on OpenTelemetry. The two work side by side through standard .NET mechanisms, and this is what the code supports:
- Logs. The wrapper hands the properties to the
ILoggerFactoryas the log state. An OpenTelemetry logging provider (builder.Logging.AddOpenTelemetry(...)) therefore receivesCategory,Application,SourceContext,Method,Pid,Tid,ActivityId(when there is a current activity) and yourAddproperties as log record attributes, and OpenTelemetry adds theTraceIdandSpanIdof the current activity itself. - Traces. Arc4u components that trace their work (token providers, principal creation, middleware) start activities on an
ActivitySourcenamedArc4u. IActivitySourceFactory returns that source (and any other you ask for by name and version) as a singleton, andAddApplicationContext()registers it. Subscribe to the source with the OpenTelemetry SDK:WithTracing(t => t.AddSource("Arc4u")). - Request duration. Arc4u does not log how long a request or a gRPC call takes. ASP.NET Core starts an activity for every request, gRPC calls included, and the OpenTelemetry ASP.NET Core instrumentation (
WithTracing(t => t.AddAspNetCoreInstrumentation()), packageOpenTelemetry.Instrumentation.AspNetCore) turns it into a server span with its duration, route and status code. For dashboards and alerts, use thehttp.server.request.durationhistogram that ASP.NET Core publishes on theMicrosoft.AspNetCore.Hostingmeter (WithMetrics(m => m.AddAspNetCoreInstrumentation())): unlike sampled traces, it counts every request. - Ids. The Arc4u
ActivityIdproperty is theTraceIdof the current activity, the same value as theTraceIdOpenTelemetry adds to the log record.
A log record written with this setup, as printed by the OpenTelemetry console exporter (the OrderId and the Arc4u properties are the attributes):
LogRecord.TraceId: e043c555c72709ccc77e2c854a69a99e
LogRecord.SpanId: f8ef3ec5f8cba95e
LogRecord.CategoryName: Program
LogRecord.FormattedMessage: inside activity
LogRecord.Attributes (Key:Value):
OrderId: 42
OriginalFormat (a.k.a Body): inside activity
Method: <Main>$
SourceContext: Program
Category: Business
Application: App
Tid: 1
Pid: 30603
ActivityId: e043c555c72709ccc77e2c854a69a99e
The OpenTelemetrySettings class (Address, Attributes, Sources) exists in the package, but no Arc4u code reads it. Configure the OpenTelemetry SDK with its own options.
Extensibility points
| Service | Default implementation | Replace it to |
|---|---|---|
IAddPropertiesToLog (keys "Scoped" and "Transient") |
NullLoggerProperties |
Add properties such as the user, a tenant or a correlation id to every entry. |
| IActivitySourceFactory | DefaultActivitySourceFactory |
Control how ActivitySource instances are created. |
ILogger<T> |
LoggerWrapper<T> |
Do not replace it: the fluent API throws an InvalidOperationException on any other implementation. |
In unit tests, use NullLoggerWrapper<T>.Instance where an ILogger<T> is required: it accepts the fluent API and writes nothing.
using Arc4u.Diagnostics;
public static class TestHelpers
{
public static void Use()
{
ILogger<OrderService> logger = NullLoggerWrapper<OrderService>.Instance;
logger.Technical().Add("Key", "value").LogInformation("Nothing is written");
}
}
Troubleshooting
InvalidOperationException: Bad Arc4u usage
Technical(), Business() and Monitoring() only work on a logger created by AddILogger() (or AddApplicationContext(), which calls it). You get this exception with a logger you created yourself (LoggerFactory.CreateLogger, NullLogger<T>) or with a mock. Resolve the logger from the container, or use NullLoggerWrapper<T>.Instance in tests. Also make sure AddILogger() runs after any other code that registers ILogger<>, because it removes the existing registration.
ReservedLoggingKeyException
The key you passed to Add is one of Application, Pid, Tid, Method, SourceContext, ActivityId, Identity, Stacktrace or Category. Use another name.
Nothing is written
AddILogger only prepares the entries. Add a logging provider, for example builder.Host.UseSerilog(...) with a sink, and check its minimum level. See Serilog.
Entries have no Identity or ActivityId
Check, in this order: a provider such as DefaultLoggingProperties is registered under "Scoped" (AddApplicationContext() does not register one); for the logger of a singleton, HttpContextLoggingProperties is registered under "Transient" (AddHttpContextLoggingProperties() called before AddApplicationContext(), see Loggers of singletons in an ASP.NET Core host) and the entry is written during an HTTP or gRPC request; IApplicationContext.Principal is set (Identity is missing when there is no authenticated principal). ActivityId is missing when the entry is written outside a System.Diagnostics.Activity (for example in a background service that starts none). See Add the user to every entry.
Properties belong to one entry
Technical(), Business(), Monitoring() and Category(...) return a new logger for the entry you are writing; the injected ILogger<T> is never modified. The properties you add (Add, AddIf, ...), the values of the message template arguments and the AddStackTrace() request are written with the next entry and cleared once it is written. They never appear on a later entry, even when the ILogger<T> lives as long as the application (injected in a singleton) or is used by several threads at the same time.
If you keep the logger returned by Technical() in a variable, each entry it writes starts again with no property:
var entry = logger.Technical();
entry.Add("OrderId", 7).LogInformation("Order read"); // OrderId = 7
entry.LogInformation("Order processed"); // no OrderId
Versions of 9.x before this fix kept these properties on the logger instance and wrote them on every later entry (Arc4u-org/Arc4u#273).
See also
- Serilog
- Client-side log storage
- Configuration guide for
Application.Configuration - Concepts
- Arc4u.Diagnostics in the API reference