71 lines
3.5 KiB
C#
71 lines
3.5 KiB
C#
using Serilog.Core;
|
|
using Serilog.Events;
|
|
using ZB.MOM.WW.OtOpcUa.Commons.Messages.Logging;
|
|
|
|
namespace ZB.MOM.WW.OtOpcUa.Core.Scripting;
|
|
|
|
/// <summary>
|
|
/// Serilog sink that converts each script <see cref="LogEvent"/> into a
|
|
/// <see cref="ScriptLogEntry"/> and forwards it to an <see cref="IScriptLogPublisher"/>.
|
|
/// The publisher implementation (supplied by a later task) routes the entry onto the
|
|
/// Akka DPS <c>script-logs</c> topic so the live Script-log Admin UI page can display it.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// <para>
|
|
/// Expected to be registered in the root script-logger pipeline alongside the
|
|
/// rolling <c>scripts-*.log</c> file sink and the
|
|
/// <see cref="ScriptLogCompanionSink"/>. Events below the configured minimum level
|
|
/// are silently discarded so Debug/Verbose noise does not saturate the cluster bus
|
|
/// in production deployments.
|
|
/// </para>
|
|
/// <para>
|
|
/// Identity properties (<c>ScriptId</c>, <c>VirtualTagId</c>, <c>AlarmId</c>,
|
|
/// <c>EquipmentId</c>) are lifted from the event's structured-property bag. If
|
|
/// <c>ScriptId</c> is absent the sink falls back to the legacy <c>ScriptName</c>
|
|
/// property so pipelines that pre-date this sink continue to work correctly. If
|
|
/// neither property is present <c>ScriptId</c> defaults to <c>"unknown"</c>.
|
|
/// </para>
|
|
/// </remarks>
|
|
public sealed class ScriptLogTopicSink : ILogEventSink
|
|
{
|
|
private readonly IScriptLogPublisher _publisher;
|
|
private readonly LogEventLevel _min;
|
|
|
|
/// <summary>Initializes a new instance of the <see cref="ScriptLogTopicSink"/> class.</summary>
|
|
/// <param name="publisher">The publisher that routes entries to the cluster bus. Must not be <c>null</c>.</param>
|
|
/// <param name="min">Minimum log level to forward. Events below this level are discarded (default: <see cref="LogEventLevel.Information"/>).</param>
|
|
/// <exception cref="ArgumentNullException">Thrown when <paramref name="publisher"/> is <c>null</c>.</exception>
|
|
public ScriptLogTopicSink(IScriptLogPublisher publisher, LogEventLevel min = LogEventLevel.Information)
|
|
{
|
|
_publisher = publisher ?? throw new ArgumentNullException(nameof(publisher));
|
|
_min = min;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Converts the <paramref name="logEvent"/> to a <see cref="ScriptLogEntry"/> and
|
|
/// publishes it, unless the event is below the configured minimum level.
|
|
/// </summary>
|
|
/// <param name="logEvent">The Serilog event to process. Silently ignored when <c>null</c>.</param>
|
|
public void Emit(LogEvent logEvent)
|
|
{
|
|
if (logEvent is null || logEvent.Level < _min) return;
|
|
|
|
// Extract a string scalar property by key, returning null when absent or non-string.
|
|
string? P(string key) =>
|
|
logEvent.Properties.TryGetValue(key, out var v) && v is ScalarValue { Value: string s }
|
|
? s
|
|
: null;
|
|
|
|
_publisher.Publish(new ScriptLogEntry(
|
|
ScriptId: P(ScriptLoggerFactory.ScriptIdProperty)
|
|
?? P(ScriptLoggerFactory.ScriptNameProperty)
|
|
?? "unknown",
|
|
Level: logEvent.Level.ToString(),
|
|
Message: logEvent.RenderMessage(),
|
|
TimestampUtc: logEvent.Timestamp.UtcDateTime,
|
|
VirtualTagId: P(ScriptLoggerFactory.VirtualTagIdProperty),
|
|
AlarmId: P(ScriptLoggerFactory.AlarmIdProperty),
|
|
EquipmentId: P(ScriptLoggerFactory.EquipmentIdProperty)));
|
|
}
|
|
}
|