using System.Diagnostics;
using OpenTelemetry;
namespace GB5Shared.Telemetry;
///
/// Tags every span with gb5.call.origin so Zipkin/Jaeger can filter human-triggered
/// requests separately from system-triggered work — independent of which Host process or
/// business module produced the span (see and the
/// gb5.module tag set in BaseEndpoint for the module-filtering half of this).
///
/// - Interactive — a real API caller (FastEndpoints request) started the trace.
/// - Automated — a Quartz job, hosted background service, Dapr pub/sub
/// subscription, outbox dispatch, action handler, or workflow-engine action started the
/// trace, with no user waiting on it. Automated spans additionally get
/// gb5.automation.type naming the exact mechanism (see )
/// so "Automated" traces can be filtered down to one specific trigger.
///
/// Runs once per span in — zero changes needed in any BLL, DAL, or
/// endpoint file, the same zero-touch approach as .
/// Child spans inherit their root's classification so an entire trace stays one colour; spans
/// with no live in-process parent (e.g. a Dapr-subscribed handler picking up a message
/// published by a different Host) fall back to their own identity,
/// which is exactly the boundary where cross-process inheritance would have broken anyway.
///
public sealed class GB5CallOriginProcessor : BaseProcessor
{
public const string OriginTag = "gb5.call.origin";
public const string Interactive = "Interactive";
public const string Automated = "Automated";
/// Set only on Automated spans — names the exact trigger mechanism.
public const string AutomationTypeTag = "gb5.automation.type";
private static readonly Dictionary AutomationTypeBySource = new(StringComparer.Ordinal)
{
[GB5ActivitySources.Scheduler.Name] = "ScheduledJob",
[GB5ActivitySources.Background.Name] = "BackgroundService",
[GB5ActivitySources.DaprSubscribe.Name] = "EventSubscription",
[GB5ActivitySources.Outbox.Name] = "OutboxDispatch",
[GB5ActivitySources.ActionProcessor.Name] = "ActionHandler",
[GB5ActivitySources.WorkflowAction.Name] = "WorkflowEngine",
};
public override void OnStart(Activity activity)
{
// Inherit from the live parent so an entire trace stays one classification.
if (activity.Parent?.GetTagItem(OriginTag) is string inheritedOrigin)
{
activity.SetTag(OriginTag, inheritedOrigin);
if (activity.Parent?.GetTagItem(AutomationTypeTag) is string inheritedType)
activity.SetTag(AutomationTypeTag, inheritedType);
return;
}
if (AutomationTypeBySource.TryGetValue(activity.Source.Name, out var automationType))
{
activity.SetTag(OriginTag, Automated);
activity.SetTag(AutomationTypeTag, automationType);
}
else
{
activity.SetTag(OriginTag, Interactive);
}
}
}