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;
}
}