尾部采样

了解如何在 OpenTelemetry .NET 中实现基于尾部的采样以捕获所有失败的 span

本指南描述了一种实现基于尾部的采样形式的可能性,该形式除了基于头部的采样外,还包含所有失败的活动(span)。

什么是基于尾部的采样?

基于尾部的采样在跟踪完成**后**做出采样决策,允许根据跟踪的完整上下文做出更明智的决策。这与基于头部的采样(在跟踪开始时做出决策)形成对比。

此实现使用自定义采样器和 `ActivityProcessor`(span 处理器)的组合来实现混合方法。

  • 基于头部的采样(概率性/无偏采样)
  • 基于尾部的采样(非概率性/有偏采样)

实施方法

SDK 使用一种混合方法,其中我们进行基于头部的采样以获取所有活动的概率子集,包括成功和失败的活动。此外,它还捕获所有失败的活动。

为达到此目的

  1. 如果父级采样器的决策是丢弃活动,SDK 将返回“仅记录”采样结果。这确保活动处理器能够收到该活动。
  2. 在活动处理器中,活动结束时,SDK 会检查它是否是失败活动。如果是,SDK 会将决策从“仅记录”更改为设置 sampled 标志,以便导出器可以接收活动。

在此示例中,每个活动都经过单独过滤,而不考虑任何其他活动。

何时使用基于尾部的采样

如果您希望在基于头部的采样之外还获取所有失败的活动,这是一个不错的选择。通过这种方法,您可以在 SDK 级别实现基本的活动级别基于尾部的采样,而无需安装任何额外的组件。

权衡

以这种方式进行尾部采样涉及几个权衡:

  1. 额外的性能开销:与在活动创建时进行采样决策的基于头部的采样不同,基于尾部的采样仅在活动结束时进行决策,因此存在额外的内存/处理开销。

  2. 部分跟踪:由于这是在活动级别进行采样,因此生成的跟踪将是不完整的。例如,如果调用树的其他部分成功,那些活动可能不会被导出,从而导致跟踪不完整。

  3. 多个导出器:如果使用多个导出器,此决策将影响所有导出器。

示例代码

该实现包含两个主要组件:

1. 允许“仅记录”决策的自定义父级采样器:

public class ParentBasedElseAlwaysRecordSampler : Sampler
{
    private readonly Sampler _rootSampler;

    public ParentBasedElseAlwaysRecordSampler(Sampler rootSampler)
        : base()
    {
        _rootSampler = rootSampler ?? throw new ArgumentNullException(nameof(rootSampler));
    }

    public override SamplingResult ShouldSample(in SamplingParameters samplingParameters)
    {
        // If there's a parent, use its sampling decision
        if (samplingParameters.ParentContext.TraceId != default)
        {
            if (samplingParameters.ParentContext.TraceFlags.HasFlag(ActivityTraceFlags.Recorded))
            {
                return new SamplingResult(SamplingDecision.RecordAndSample);
            }
            else
            {
                // Instead of dropping, we record this activity so that we can process it in our
                // processor
                return new SamplingResult(SamplingDecision.RecordOnly);
            }
        }

        // This is a root activity. Use the root sampler to make the decision.
        return _rootSampler.ShouldSample(samplingParameters);
    }

    public override string Description => $"ParentBasedElseAlwaysRecordSampler({_rootSampler.Description})";
}

2. 选择性采样失败活动的尾部采样处理器:

public class TailSamplingProcessor : BaseProcessor<Activity>
{
    private readonly string _statusTagName;

    public TailSamplingProcessor(string statusTagName = "otel.status_code")
    {
        _statusTagName = statusTagName;
    }

    public override void OnEnd(Activity activity)
    {
        // If the activity is already sampled, we don't need to do anything
        if (activity.ActivityTraceFlags.HasFlag(ActivityTraceFlags.Recorded))
        {
            return;
        }

        // Check if this is an error activity
        bool isError = false;

        if (activity.Status == ActivityStatusCode.Error)
        {
            isError = true;
        }
        else if (activity.TagObjects != null)
        {
            foreach (var tag in activity.TagObjects)
            {
                if (tag.Key == _statusTagName)
                {
                    if (tag.Value?.ToString() == "ERROR")
                    {
                        isError = true;
                        break;
                    }
                }
            }
        }

        if (isError)
        {
            Console.WriteLine($"Including error activity with id {activity.Id} and status {activity.Status}");
            activity.ActivityTraceFlags |= ActivityTraceFlags.Recorded;
        }
        else
        {
            Console.WriteLine($"Dropping activity with id {activity.Id} and status {activity.Status}");
        }
    }
}

示例输出

当您运行使用此采样器和处理器的应用程序时,您应该会看到类似以下的输出:

Including error activity with id
00-404ddff248b8f9a9b21e347d68d2640e-035858bc3c168885-01 and status Error
Activity.TraceId:            404ddff248b8f9a9b21e347d68d2640e
Activity.SpanId:             035858bc3c168885
Activity.TraceFlags:         Recorded
Activity.ActivitySourceName: SDK.TailSampling.POC
Activity.DisplayName:        SayHello
Activity.Kind:               Internal
Activity.StartTime:          2023-02-09T19:05:32.5563112Z
Activity.Duration:           00:00:00.0028144
Activity.Tags:
    foo: bar
StatusCode: Error
Resource associated with Activity:
    service.name: unknown_service:Examples.TailBasedSamplingAtSpanLevel

Dropping activity with id 00-ea861bda268c58d328ab7cbe49851499-daba29055de80a53-00
and status Ok

Including head-sampled activity with id
00-f3c88010615e285c8f3cb3e2bcd70c7f-f9316215f12437c3-01 and status Ok
Activity.TraceId:            f3c88010615e285c8f3cb3e2bcd70c7f
Activity.SpanId:             f9316215f12437c3
Activity.TraceFlags:         Recorded
Activity.ActivitySourceName: SDK.TailSampling.POC
Activity.DisplayName:        SayHello
Activity.Kind:               Internal
Activity.StartTime:          2023-02-09T19:05:32.8519346Z
Activity.Duration:           00:00:00.0000034
Activity.Tags:
    foo: bar
StatusCode: Ok
Resource associated with Activity:
    service.name: unknown_service:Examples.TailBasedSamplingAtSpanLevel

这表明:

  1. 错误活动始终包含在内(通过基于尾部的采样)
  2. 一些 OK 活动被丢弃(如果未被基于头部的采样选中)
  3. 一些 OK 活动被包含在内(通过基于头部的采样)

完整示例

有关包含可运行应用程序的完整示例,请参阅 OpenTelemetry .NET 存储库