fix(scripting): explicit companion logger + disposable ScriptRootLogger (T2 review)

This commit is contained in:
Joseph Doherty
2026-06-10 11:56:51 -04:00
parent 73014258ef
commit bf86b3def6
3 changed files with 26 additions and 11 deletions
@@ -11,15 +11,23 @@ namespace ZB.MOM.WW.OtOpcUa.Core.Scripting;
/// file sink, the <see cref="ScriptLogCompanionSink"/> mirroring errors to the main log, /// file sink, the <see cref="ScriptLogCompanionSink"/> mirroring errors to the main log,
/// and the <see cref="ScriptLogTopicSink"/> fanning entries onto the cluster /// and the <see cref="ScriptLogTopicSink"/> fanning entries onto the cluster
/// <c>script-logs</c> topic for the live Script-log Admin UI page. /// <c>script-logs</c> topic for the live Script-log Admin UI page.
/// <para>
/// Implements <see cref="IDisposable"/> so the DI container flushes the rolling-file
/// sink on graceful host shutdown.
/// </para>
/// </remarks> /// </remarks>
public sealed class ScriptRootLogger public sealed class ScriptRootLogger : IDisposable
{ {
/// <summary>Gets the composed root script logger that evaluators derive per-script loggers from.</summary> private readonly Serilog.Core.Logger _logger;
public Serilog.ILogger Logger { get; }
/// <summary>Initializes a new instance of the <see cref="ScriptRootLogger"/> class.</summary> /// <summary>The composed root script logger (file + companion mirror + DPS topic sink).</summary>
/// <param name="logger">The composed root script logger. Must not be <c>null</c>.</param> public Serilog.ILogger Logger => _logger;
/// <exception cref="ArgumentNullException">Thrown when <paramref name="logger"/> is <c>null</c>.</exception>
public ScriptRootLogger(Serilog.ILogger logger) => /// <summary>Initializes a new <see cref="ScriptRootLogger"/>.</summary>
Logger = logger ?? throw new ArgumentNullException(nameof(logger)); /// <param name="logger">The composed Serilog logger; owned + disposed by this wrapper.</param>
public ScriptRootLogger(Serilog.Core.Logger logger) =>
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
/// <summary>Disposes the underlying logger, flushing the rolling-file sink.</summary>
public void Dispose() => _logger.Dispose();
} }
@@ -27,12 +27,19 @@ public static class ScriptRootLoggerFactory
/// Minimum level for events forwarded to the <c>script-logs</c> topic; lower-level events /// Minimum level for events forwarded to the <c>script-logs</c> topic; lower-level events
/// still land in the file sink but are not pushed onto the cluster bus. /// still land in the file sink but are not pushed onto the cluster bus.
/// </param> /// </param>
/// <param name="companionLogger">
/// The application's main logger that receives mirrored Error-or-higher script events at
/// Warning level. Passed explicitly so callers control the source rather than reading the
/// global <see cref="Serilog.Log.Logger"/> static inside the factory.
/// </param>
/// <returns>The composed root script logger.</returns> /// <returns>The composed root script logger.</returns>
public static Serilog.ILogger Build(IScriptLogPublisher publisher, string filePath, LogEventLevel topicMinLevel) public static Serilog.Core.Logger Build(
IScriptLogPublisher publisher, string filePath, LogEventLevel topicMinLevel,
Serilog.ILogger companionLogger)
=> new LoggerConfiguration() => new LoggerConfiguration()
.MinimumLevel.Verbose() .MinimumLevel.Verbose()
.WriteTo.File(filePath, rollingInterval: RollingInterval.Day) .WriteTo.File(filePath, rollingInterval: RollingInterval.Day)
.WriteTo.Sink(new ScriptLogCompanionSink(Serilog.Log.Logger)) .WriteTo.Sink(new ScriptLogCompanionSink(companionLogger))
.WriteTo.Sink(new ScriptLogTopicSink(publisher, topicMinLevel)) .WriteTo.Sink(new ScriptLogTopicSink(publisher, topicMinLevel))
.CreateLogger(); .CreateLogger();
} }
+1 -1
View File
@@ -131,7 +131,7 @@ if (hasDriver)
new DpsScriptLogPublisher(() => sp.GetRequiredService<ActorSystem>())); new DpsScriptLogPublisher(() => sp.GetRequiredService<ActorSystem>()));
builder.Services.AddSingleton(sp => new ScriptRootLogger( builder.Services.AddSingleton(sp => new ScriptRootLogger(
ScriptRootLoggerFactory.Build( ScriptRootLoggerFactory.Build(
sp.GetRequiredService<IScriptLogPublisher>(), scriptLogFilePath, scriptLogTopicMinLevel))); sp.GetRequiredService<IScriptLogPublisher>(), scriptLogFilePath, scriptLogTopicMinLevel, Serilog.Log.Logger)));
builder.Services.AddValidatedOptions<LdapOptions, LdapOptionsValidator>(builder.Configuration, LdapOptions.SectionName); builder.Services.AddValidatedOptions<LdapOptions, LdapOptionsValidator>(builder.Configuration, LdapOptions.SectionName);
// TryAdd so a fused admin+driver node (where AddOtOpcUaAuth also registers these) ends up // TryAdd so a fused admin+driver node (where AddOtOpcUaAuth also registers these) ends up