using Serilog; namespace ZB.MOM.WW.OtOpcUa.Core.Scripting; /// /// Creates per-script Serilog instances with the /// ScriptName structured property pre-bound. Every log call from a user /// script carries the owning virtual-tag or alarm name so operators can filter the /// dedicated scripts-*.log sink by script in the Admin UI. /// /// /// /// Factory-based — the engine (Stream B / C) constructs exactly one instance /// from the root script-logger pipeline at startup, then derives a per-script /// logger for each it builds. No per-evaluation /// allocation in the hot path. /// /// /// The wrapped root logger is responsible for output wiring — typically a /// rolling file sink to scripts-*.log plus a /// that forwards Error-or-higher events /// to the main server log at Warning level so operators see script errors /// in the primary log without drowning it in Info noise. /// /// public sealed class ScriptLoggerFactory { /// Structured property name the enricher binds. Stable for log filtering. public const string ScriptNameProperty = "ScriptName"; private readonly ILogger _rootLogger; public ScriptLoggerFactory(ILogger rootLogger) { _rootLogger = rootLogger ?? throw new ArgumentNullException(nameof(rootLogger)); } /// /// Create a per-script logger. Every event it emits carries /// ScriptName= as a structured property. /// public ILogger Create(string scriptName) { if (string.IsNullOrWhiteSpace(scriptName)) throw new ArgumentException("Script name is required.", nameof(scriptName)); return _rootLogger.ForContext(ScriptNameProperty, scriptName); } }