尾部采样
了解如何在 OpenTelemetry .NET 中实现基于尾部的采样以捕获所有失败的 span
本指南描述了一种实现基于尾部的采样形式的可能性,该形式除了基于头部的采样外,还包含所有失败的活动(span)。
什么是基于尾部的采样?
基于尾部的采样在跟踪完成**后**做出采样决策,允许根据跟踪的完整上下文做出更明智的决策。这与基于头部的采样(在跟踪开始时做出决策)形成对比。
此实现使用自定义采样器和 `ActivityProcessor`(span 处理器)的组合来实现混合方法。
- 基于头部的采样(概率性/无偏采样)
- 基于尾部的采样(非概率性/有偏采样)
实施方法
SDK 使用一种混合方法,其中我们进行基于头部的采样以获取所有活动的概率子集,包括成功和失败的活动。此外,它还捕获所有失败的活动。
为达到此目的
- 如果父级采样器的决策是丢弃活动,SDK 将返回“仅记录”采样结果。这确保活动处理器能够收到该活动。
- 在活动处理器中,活动结束时,SDK 会检查它是否是失败活动。如果是,SDK 会将决策从“仅记录”更改为设置 sampled 标志,以便导出器可以接收活动。
在此示例中,每个活动都经过单独过滤,而不考虑任何其他活动。
何时使用基于尾部的采样
如果您希望在基于头部的采样之外还获取所有失败的活动,这是一个不错的选择。通过这种方法,您可以在 SDK 级别实现基本的活动级别基于尾部的采样,而无需安装任何额外的组件。
权衡
以这种方式进行尾部采样涉及几个权衡:
额外的性能开销:与在活动创建时进行采样决策的基于头部的采样不同,基于尾部的采样仅在活动结束时进行决策,因此存在额外的内存/处理开销。
部分跟踪:由于这是在活动级别进行采样,因此生成的跟踪将是不完整的。例如,如果调用树的其他部分成功,那些活动可能不会被导出,从而导致跟踪不完整。
多个导出器:如果使用多个导出器,此决策将影响所有导出器。
示例代码
该实现包含两个主要组件:
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
这表明:
- 错误活动始终包含在内(通过基于尾部的采样)
- 一些 OK 活动被丢弃(如果未被基于头部的采样选中)
- 一些 OK 活动被包含在内(通过基于头部的采样)
完整示例
有关包含可运行应用程序的完整示例,请参阅 OpenTelemetry .NET 存储库。