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"; /// Structured property name carrying the Script row identifier (Script.ScriptId). public const string ScriptIdProperty = "ScriptId"; /// Structured property name carrying the VirtualTag identifier, when the script runs in a virtual-tag context. public const string VirtualTagIdProperty = "VirtualTagId"; /// Structured property name carrying the ScriptedAlarm identifier, when the script runs in an alarm context. public const string AlarmIdProperty = "AlarmId"; /// Structured property name carrying the Equipment identifier for per-equipment script evaluations. public const string EquipmentIdProperty = "EquipmentId"; private readonly ILogger _rootLogger; /// Initializes a new instance of the class. /// The root logger from which per-script loggers are derived. 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. /// /// The name of the script for which to create a logger. /// An ILogger instance bound with the script name. public ILogger Create(string scriptName) { if (string.IsNullOrWhiteSpace(scriptName)) throw new ArgumentException("Script name is required.", nameof(scriptName)); return _rootLogger.ForContext(ScriptNameProperty, scriptName); } /// /// Create a per-evaluation script logger bound with the supplied identity properties. /// is always bound; , /// , and are bound only /// when the corresponding argument is non-null. Every event the returned logger emits /// carries these properties so the can attribute each /// line on the Script-log page. /// /// The Script row identifier; always bound. Required. /// VirtualTag context, when the script runs against a virtual tag. /// ScriptedAlarm context, when the script runs an alarm predicate. /// Equipment scope, for per-equipment script evaluations. /// An ILogger instance bound with the supplied identity properties. /// Thrown when is null or whitespace. public ILogger Create( string scriptId, string? virtualTagId = null, string? alarmId = null, string? equipmentId = null) { if (string.IsNullOrWhiteSpace(scriptId)) throw new ArgumentException("Script id is required.", nameof(scriptId)); var logger = _rootLogger.ForContext(ScriptIdProperty, scriptId); if (virtualTagId is not null) logger = logger.ForContext(VirtualTagIdProperty, virtualTagId); if (alarmId is not null) logger = logger.ForContext(AlarmIdProperty, alarmId); if (equipmentId is not null) logger = logger.ForContext(EquipmentIdProperty, equipmentId); return logger; } }