feat(telemetry): AddZbSerilog minimum-level governance hook; bump to 0.2.0 (scadaproj#12)
Adds an additive AddZbSerilog(builder, configure, configureSerilog) overload taking ZbSerilogOptions: - MinimumLevelGovernor: Func<IConfiguration, LogEventLevel?> applied with MinimumLevel.Is AFTER ReadFrom.Configuration, so an app-owned key (e.g. ScadaBridge:Logging:MinimumLevel) outranks Serilog:MinimumLevel; null defers to config; Serilog:MinimumLevel:Override:* entries still apply. - ConfigureLogger: runs last, for app filters/enrichers/sinks (ScadaBridge's standby active-node exclusion). The two-argument overload is unchanged. 8 new tests (4 fail with the hook neutralized). Telemetry + Telemetry.Serilog 0.1.0 -> 0.2.0, not published. Claude-Session: https://claude.ai/code/session_01GT67AEHd2Acv1YPwGfXEoV
This commit is contained in:
@@ -57,7 +57,35 @@ public static class ZbSerilogExtensions
|
||||
/// <param name="configure">Populates the <see cref="ZbTelemetryOptions"/>.</param>
|
||||
public static IHostApplicationBuilder AddZbSerilog(
|
||||
this IHostApplicationBuilder builder,
|
||||
Action<ZbTelemetryOptions> configure)
|
||||
Action<ZbTelemetryOptions> configure) =>
|
||||
AddZbSerilogCore(builder, configure, new ZbSerilogOptions());
|
||||
|
||||
/// <summary>
|
||||
/// Overload of <see cref="AddZbSerilog(IHostApplicationBuilder, Action{ZbTelemetryOptions})"/>
|
||||
/// that additionally accepts caller-supplied hooks (<see cref="ZbSerilogOptions"/>): a
|
||||
/// minimum-level governor that has the last word on the default floor (it runs AFTER
|
||||
/// <c>ReadFrom.Configuration</c>, so it overrides <c>Serilog:MinimumLevel</c>), and a final
|
||||
/// <see cref="LoggerConfiguration"/> callback for app-specific filters/enrichers/sinks.
|
||||
/// With every hook left null this is identical to the two-argument overload.
|
||||
/// </summary>
|
||||
/// <param name="builder">The host application builder.</param>
|
||||
/// <param name="configure">Populates the <see cref="ZbTelemetryOptions"/>.</param>
|
||||
/// <param name="configureSerilog">Populates the <see cref="ZbSerilogOptions"/> hooks.</param>
|
||||
public static IHostApplicationBuilder AddZbSerilog(
|
||||
this IHostApplicationBuilder builder,
|
||||
Action<ZbTelemetryOptions> configure,
|
||||
Action<ZbSerilogOptions> configureSerilog)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(configureSerilog);
|
||||
var serilogOptions = new ZbSerilogOptions();
|
||||
configureSerilog(serilogOptions);
|
||||
return AddZbSerilogCore(builder, configure, serilogOptions);
|
||||
}
|
||||
|
||||
private static IHostApplicationBuilder AddZbSerilogCore(
|
||||
IHostApplicationBuilder builder,
|
||||
Action<ZbTelemetryOptions> configure,
|
||||
ZbSerilogOptions serilogOptions)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(builder);
|
||||
ArgumentNullException.ThrowIfNull(configure);
|
||||
@@ -79,7 +107,17 @@ public static class ZbSerilogExtensions
|
||||
.MinimumLevel.Is(LogEventLevel.Information)
|
||||
.ReadFrom.Configuration(builder.Configuration);
|
||||
|
||||
// The governor runs AFTER ReadFrom.Configuration on purpose: that ordering is what
|
||||
// lets an app-owned key outrank Serilog:MinimumLevel. Namespace overrides read from
|
||||
// configuration are untouched — MinimumLevel.Is only sets the default floor.
|
||||
if (serilogOptions.MinimumLevelGovernor?.Invoke(builder.Configuration) is { } governed)
|
||||
{
|
||||
loggerConfiguration.MinimumLevel.Is(governed);
|
||||
}
|
||||
|
||||
ZbSerilogConfig.Apply(loggerConfiguration, options, serviceProvider);
|
||||
|
||||
serilogOptions.ConfigureLogger?.Invoke(loggerConfiguration, serviceProvider);
|
||||
},
|
||||
preserveStaticLogger: true);
|
||||
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Serilog;
|
||||
using Serilog.Events;
|
||||
|
||||
namespace ZB.MOM.WW.Telemetry.Serilog;
|
||||
|
||||
/// <summary>
|
||||
/// Optional, caller-supplied hooks for <see cref="ZbSerilogExtensions.AddZbSerilog(Microsoft.Extensions.Hosting.IHostApplicationBuilder, System.Action{ZbTelemetryOptions}, System.Action{ZbSerilogOptions})"/>.
|
||||
/// Every member defaults to <see langword="null"/>, and with all of them null the bootstrap is
|
||||
/// byte-for-byte the two-argument <c>AddZbSerilog</c>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// <b>Why this exists.</b> The default bootstrap pins a code floor of
|
||||
/// <see cref="LogEventLevel.Information"/> and then lets <c>ReadFrom.Configuration</c> override it,
|
||||
/// so <c>Serilog:MinimumLevel</c> is the source of truth. An app whose documented contract puts the
|
||||
/// floor somewhere else (ScadaBridge: <c>ScadaBridge:Logging:MinimumLevel</c> wins over
|
||||
/// <c>Serilog:MinimumLevel</c>) cannot express that with the default ordering — it needs to have
|
||||
/// the LAST word on the floor. <see cref="MinimumLevelGovernor"/> is that last word.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public sealed class ZbSerilogOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Governs the default minimum level. Invoked with the host's <see cref="IConfiguration"/>
|
||||
/// each time the logger is built; a non-null result is applied with
|
||||
/// <c>MinimumLevel.Is(level)</c> <b>after</b> <c>ReadFrom.Configuration</c>, so it overrides
|
||||
/// <c>Serilog:MinimumLevel</c> / <c>Serilog:MinimumLevel:Default</c>. Returning
|
||||
/// <see langword="null"/> defers to the default behaviour (code floor Information, config wins).
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Only the DEFAULT level is governed: per-namespace <c>Serilog:MinimumLevel:Override:*</c>
|
||||
/// entries read from configuration still apply on top of it, exactly as they do when the app
|
||||
/// builds its own <see cref="LoggerConfiguration"/> and calls <c>MinimumLevel.Is</c> after
|
||||
/// <c>ReadFrom.Configuration</c>. The governor is the place for any operator-facing warning
|
||||
/// (unparseable value, conflicting <c>Serilog:MinimumLevel</c> key): it runs before the logger
|
||||
/// exists, so it should write to its own sink (e.g. <see cref="System.Console.Error"/>).
|
||||
/// </remarks>
|
||||
public Func<IConfiguration, LogEventLevel?>? MinimumLevelGovernor { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Final, app-specific adjustments to the <see cref="LoggerConfiguration"/> — filters,
|
||||
/// extra enrichers, extra sinks — run after the governed level and the shared
|
||||
/// enricher/redaction/export set. Receives the application <see cref="IServiceProvider"/>.
|
||||
/// </summary>
|
||||
public Action<LoggerConfiguration, IServiceProvider>? ConfigureLogger { get; set; }
|
||||
}
|
||||
Reference in New Issue
Block a user