using Akka.Actor; using Akka.Cluster.Tools.PublishSubscribe; using ZB.MOM.WW.OtOpcUa.Commons.Messages.Logging; using ZB.MOM.WW.OtOpcUa.Core.Scripting; using ZB.MOM.WW.OtOpcUa.Runtime.VirtualTags; namespace ZB.MOM.WW.OtOpcUa.Runtime.Scripting; /// /// Concrete that routes each /// onto the Akka DistributedPubSub script-logs topic /// (). ScriptLogSignalRBridge subscribes /// to that topic so entries reach the live Script-log Admin UI page. /// /// /// /// The is resolved lazily through a /// so constructing the publisher never races Akka startup — the system only needs to exist /// by the time the first script logs, not at DI-registration time. /// /// /// This type sits behind a Serilog sink (); a sink must /// never throw back into the logging pipeline, so every failure path here is swallowed /// (best-effort logged at Debug to the main Serilog logger). /// /// public sealed class DpsScriptLogPublisher : IScriptLogPublisher { private readonly Func _system; /// Initializes a new instance of the class. /// /// Lazy accessor for the running . Invoked on each /// so registration does not depend on Akka having started yet. /// /// Thrown when is null. public DpsScriptLogPublisher(Func system) => _system = system ?? throw new ArgumentNullException(nameof(system)); /// /// Publishes onto the DPS script-logs topic. Any failure /// (system not yet ready, mediator unavailable) is swallowed so the logging pipeline is /// never disrupted by a transient cluster condition. /// /// The entry to publish. public void Publish(ScriptLogEntry entry) { try { var mediator = DistributedPubSub.Get(_system()).Mediator; mediator.Tell(new Publish(VirtualTagActor.ScriptLogsTopic, entry)); } catch (Exception ex) { // A logging sink must never throw into the logging pipeline. Best-effort note to // the main log at Debug; nothing actionable for the script author here. Serilog.Log.Debug(ex, "DpsScriptLogPublisher could not route a script log entry to the cluster bus."); } } }