Files
ScadaBridge/src/ZB.MOM.WW.ScadaBridge.SiteRuntime/Scripts/TraceStepScope.cs
T

45 lines
2.2 KiB
C#

namespace ZB.MOM.WW.ScadaBridge.SiteRuntime.Scripts;
/// <summary>
/// Flow Debugger step logs (design 2026-09-09 D6): the ambient "which step is running right now"
/// marker used to attribute runtime-sourced log lines to a step node.
///
/// <para>A <c>ScriptLog</c> row names its owning step explicitly (D2), but the runtime lines the
/// node logs on the script's behalf are produced deep inside a helper — the helper knows its
/// pre-minted step <c>EventId</c>, the logging call site does not. Rather than thread that id
/// through every internal call, the helpers open a scope around their body and
/// <see cref="ScriptLogger"/> reads <see cref="Current"/> when no explicit id is supplied.</para>
///
/// <para><see cref="AsyncLocal{T}"/> so the value flows through the helper's own awaits and
/// nowhere else: it is captured by continuations started inside the scope and is invisible to
/// concurrent runs on other threads of the script execution pool. Lines logged outside any scope
/// see <see langword="null"/> and fall back to the execution's anchor row (D2).</para>
/// </summary>
public static class TraceStepScope
{
private static readonly AsyncLocal<Guid?> _current = new();
/// <summary>The step <c>EventId</c> owning the currently executing helper body, or <see langword="null"/> outside any scope.</summary>
public static Guid? Current => _current.Value;
/// <summary>
/// Sets the current step for the scope's lifetime; restores the previous value on dispose,
/// so nested scopes (a helper calling another helper) unwind correctly.
/// </summary>
/// <param name="stepEventId">The pre-minted <c>EventId</c> of the step row this body belongs to.</param>
/// <returns>A handle that restores the previous ambient value when disposed.</returns>
public static IDisposable Enter(Guid stepEventId)
{
var prev = _current.Value;
_current.Value = stepEventId;
return new Restore(prev);
}
/// <summary>Restores the ambient step id captured at <see cref="Enter"/> time.</summary>
private sealed class Restore(Guid? prev) : IDisposable
{
/// <inheritdoc />
public void Dispose() => _current.Value = prev;
}
}