OpenTelemetry跟踪Shim

.NET与其他支持OpenTelemetry的语言/运行时有所不同。跟踪是通过System.Diagnostics API实现的,将ActivitySourceActivity等旧构造重新用于OpenTelemetry规范。

对于.NET,OpenTelemetry还在System.Diagnostics为基础的实现之上提供了API shim。如果您的代码库中使用其他语言和OpenTelemetry,或者您更喜欢使用与OpenTelemetry规范一致的术语,这个shim将非常有帮助。

初始化跟踪

有两种主要方法可以初始化跟踪,具体取决于您是使用控制台应用程序还是基于ASP.NET Core。

控制台应用程序

要在控制台应用程序中启动跟踪,您需要创建一个跟踪器提供程序。

首先,确保您安装了正确的包:

dotnet add package OpenTelemetry
dotnet add package OpenTelemetry.Exporter.Console

然后,在程序的开头使用以下代码,在任何重要的启动操作期间执行它。

using OpenTelemetry;
using OpenTelemetry.Trace;
using OpenTelemetry.Resources;

// ...

var serviceName = "MyServiceName";
var serviceVersion = "1.0.0";

using var tracerProvider = Sdk.CreateTracerProviderBuilder()
    .AddSource(serviceName)
    .SetResourceBuilder(
        ResourceBuilder.CreateDefault()
            .AddService(serviceName: serviceName, serviceVersion: serviceVersion))
    .AddConsoleExporter()
    .Build();

// ...

这也是您可以配置仪表库的地方。

请注意,此示例使用了Console Exporter。如果您要导出到其他端点,您将需要使用另一个导出器。

ASP.NET Core

要在基于ASP.NET Core的应用程序中启动跟踪,请使用OpenTelemetry的ASP.NET Core扩展进行设置。

首先,确保您安装了正确的包:

dotnet add package OpenTelemetry --prerelease
dotnet add package OpenTelemetry.Instrumentation.AspNetCore --prerelease
dotnet add package OpenTelemetry.Extensions.Hosting --prerelease
dotnet add package OpenTelemetry.Exporter.Console --prerelease

然后,在您可以访问IServiceCollection的ASP.NET Core启动函数中进行配置。

using OpenTelemetry.Resources;
using OpenTelemetry.Trace;

// 这些可以来自配置文件、常量文件等
var serviceName = "MyCompany.MyProduct.MyService";
var serviceVersion = "1.0.0";

var builder = WebApplication.CreateBuilder(args);

// 配置重要的OpenTelemetry设置、控制台导出器和仪表库
builder.Services.AddOpenTelemetry().WithTracing(tcb =>
{
    tcb
    .AddSource(serviceName)
    .SetResourceBuilder(
        ResourceBuilder.CreateDefault()
            .AddService(serviceName: serviceName, serviceVersion: serviceVersion))
    .AddAspNetCoreInstrumentation()
    .AddConsoleExporter();
});

在上述示例中,服务级别的跟踪器Tracer会在设置期间注入。这样,您就可以在端点映射中(或者如果您使用较旧版本的.NET,则为控制器)中访问一个实例。

不要求注入服务级别的跟踪器,也不会改善性能。但是,您需要决定想要的跟踪器实例存储在哪里。

这也是您可以配置仪表库的地方。

请注意,此示例使用了Console Exporter。如果您要导出到其他端点,您将需要使用另一个导出器。

设置跟踪器

跟踪初始化后,您可以配置一个Tracer,这将是您使用Span跟踪操作的方式。

通常情况下,一个Tracer会为一个被仪表化的应用程序/服务实例化一次,因此在共享位置实例化一次是一个好主意。它通常和服务名称同名。

在ASP.NET Core中注入跟踪器

ASP.NET Core通常鼓励在设置期间注入长期存在的对象实例,如Tracer

using OpenTelemetry.Trace;

var builder = WebApplication.CreateBuilder(args);

// ...

builder.Services.AddSingleton(TracerProvider.Default.GetTracer(serviceName));

// ...

var app = builder.Build();

// ...

app.MapGet("/hello", (Tracer tracer) =>
{
    using var span = tracer.StartActiveSpan("hello-span");

    // 执行操作
});

从TracerProvider中获取跟踪器

如果您不使用ASP.NET Core或不想注入Tracer的实例,可以从已实例化的TracerProvider创建一个跟踪器:

// ...

var tracer = tracerProvider.GetTracer(serviceName);

// 将其分配给全局位置

// ...

您可能希望将此Tracer实例分配给一个变量,在服务中随时都可以访问它。

您可以为每个服务实例化任意数量的Tracer,尽管通常情况下,只需要为每个服务定义一个即可。

创建Spans

要创建一个Span,为其命名并从Tracer中创建。

using var span = MyTracer.StartActiveSpan("SayHello");

// 进行要由`span`跟踪的工作

创建嵌套Spans

如果您有一个明确定义为另一个操作的一部分要跟踪的子操作,可以创建表示关系的Spans。

public static void ParentOperation(Tracer tracer)
{
    using var parentSpan = tracer.StartActiveSpan("parent-span");

    // 使用parentSpan跟踪的工作

    ChildOperation(tracer);

    // 再次使用parentSpan跟踪的工作
}

public static void ChildOperation(Tracer tracer)
{
    using var childSpan = tracer.StartActiveSpan("child-span");

    // 使用childSpan跟踪ChildOperation中的工作
}

在跟踪可视化工具中查看Spans时,child-span将作为parent-span的嵌套操作进行跟踪。

同一作用域的嵌套Spans

您可能希望在同一作用域中创建父子关系。虽然这是可能的,但通常不推荐这样做,因为您需要小心以期望它结束嵌套的每个TelemetrySpan

public static void DoWork(Tracer tracer)
{
    using var parentSpan = tracer.StartActiveSpan("parent-span");

    // 使用parentSpan跟踪的工作

    using (var childSpan = tracer.StartActiveSpan("child-span"))
    {
        // 在同一函数中执行某些'child'工作
    }

    // 再次使用parentSpan跟踪的工作
}

在上面的示例中,childSpan结束,因为using块的范围显式定义,而不像parentSpan那样仅作用于DoWork本身。

创建独立Spans

前面的示例展示了如何创建遵循嵌套层次结构的Spans。在某些情况下,您可能希望创建独立的Spans,它们是具有相同根的同级。

public static void DoWork(Tracer tracer)
{
    using var parent = tracer.StartSpan("parent");
    // 'parent'将是'child1'和'child2'的共享父级

    using (var child1 = tracer.StartSpan("child1"))
    {
        // 做一些'child1'跟踪的工作
    }

    using (var child2 = tracer.StartSpan("child2"))
    {
        // 做一些'child2'跟踪的工作
    }
}

创建新的根Spans

您还可以创建与当前跟踪完全断开的新的根spans

public static void DoWork(Tracer tracer)
{
    using var newRoot = tracer.StartRootSpan("newRoot");
}

获取当前Span

有时,访问当前TelemetrySpan在某个时间点上非常有用,以便您可以为其添加更多信息。

var span = Tracer.CurrentSpan;
// 做一些很酷的事情!

请注意,上面的示例中没有使用using。如果使用它,当超出范围时将结束当前的TelemetrySpan,这通常不是所需的行为。

向Span添加属性

Attributes允许您将键值对附加到TelemetrySpan上,以便它携带有关当前操作的更多信息。

using var span = tracer.StartActiveSpan("SayHello");

span.SetAttribute("operation.value", 1);
span.SetAttribute("operation.name", "Saying hello!");
span.SetAttribute("operation.other-stuff", new int[] { 1, 2, 3 });

添加事件

Event是在TelemetrySpan的生命周期中代表“正在发生某事”的人类可读消息。您可以将其视为原始日志。

using var span = tracer.StartActiveSpan("SayHello");

// ...

span.AddEvent("正在执行某些操作...");

// ...

span.AddEvent("做好了!");

事件还可以带有时间戳和一组attributes来创建。

using var span = tracer.StartActiveSpan("SayHello");

// ...

span.AddEvent("事件消息");
span.AddEvent("事件消息2", DateTimeOffset.Now);

// ...

var attributeData = new Dictionary<string, object>
{
    {"foo", 1 },
    { "bar", "你好,世界!" },
    { "baz", new int[] { 1, 2, 3 } }
};

span.AddEvent("asdf", DateTimeOffset.Now, new(attributeData));

添加链接

可以为一个TelemetrySpan创建零个或多个链接,它们具有因果关系。

// 从某个地方获取上下文,可能作为参数传递
var ctx = span.Context;

var links = new List<Link>
{
    new(ctx)
};

using var span = tracer.StartActiveSpan("another-span", links: links);

// 做一些工作

设置Span状态

可以在Span上设置状态,通常用于指定一个Span尚未成功完成 - Status.Error。在罕见的情况下,您可以使用Ok覆盖Error状态,但不要将Ok设置为已成功完成的Span。

可以在Span结束之前的任何时候设置状态:

using var span = tracer.StartActiveSpan("SayHello");

try
{
	// 做一些事情
}
catch (Exception ex)
{
    span.SetStatus(Status.Error, "出现了问题!");
}

在Spans中记录异常

在异常发生时记录异常是一个好主意。建议在设置span状态时一起使用。

using var span = tracer.StartActiveSpan("SayHello");

try
{
	// 做一些事情
}
catch (Exception ex)
{
    span.SetStatus(Status.Error, "出现了问题!");
    span.RecordException(ex)
}

这将在Span中捕获当前堆栈跟踪等信息。

下一步

在设置手动仪表化之后,您可能希望使用仪表化库。仪表化库将为您使用的相关库提供仪表化,并生成有关入站和出站HTTP请求等数据。

您还需要配置适当的导出器,以将您的遥测数据导出到一个或多个遥测后端。

最后修改 December 10, 2023: translate (a4350d6e)