手动仪表化

OpenTelemetry .NET的手动仪表化

Manual instrumentation is the act of adding observability code to an app yourself.

If you’re instrumenting an app, you need to use the OpenTelemetry SDK for your language. You’ll then use the SDK to initialize OpenTelemetry and the API to instrument your code. This will emit telemetry from your app, and any library you installed that also comes with instrumentation.

If you’re instrumenting a library, only install the OpenTelemetry API package for your language. Your library will not emit telemetry on its own. It will only emit telemetry when it is part of an app that uses the OpenTelemetry SDK. For more on instrumenting libraries, see Libraries.

For more information about the OpenTelemetry API and SDK, see the specification.

术语说明

.NET与其他支持OpenTelemetry的语言/运行时有所不同。跟踪 API 是由 System.Diagnostics API 实现的,该 API 重用现有的构造,如 ActivitySourceActivity,以实现 OpenTelemetry 兼容性。

然而,.NET 开发人员仍然需要了解 OpenTelemetry API 和术语,以便能够对其应用程序进行仪表化,这也涵盖了 System.Diagnostics API。

如果您愿意使用 OpenTelemetry API 而不是 System.Diagnostics API,可以查阅 用于跟踪的 OpenTelemetry API Shim 文档

跟踪

初始化跟踪

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

控制台应用

要在控制台应用中开始跟踪,需要创建一个跟踪提供程序。

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

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

然后,在程序开始时的任何重要启动操作中使用以下代码。

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

// ...

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

using var tracerProvider = Sdk.CreateTracerProviderBuilder()
    .AddSource(serviceName)
    .ConfigureResource(resource =>
        resource.AddService(
          serviceName: serviceName,
          serviceVersion: serviceVersion))
    .AddConsoleExporter()
    .Build();

// ...

这也是您可以配置仪表化库的位置。

请注意,此示例使用了 Console Exporter。如果要导出到其他端点,您需要使用不同的导出程序。

ASP.NET Core

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

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

dotnet add package OpenTelemetry
dotnet add package OpenTelemetry.Extensions.Hosting
dotnet add package OpenTelemetry.Exporter.Console

然后,您可以安装 Instrumentation 包

dotnet add package OpenTelemetry.Instrumentation.AspNetCore --prerelease

请注意,所有仪表化包都需要 --prerelease 标志,因为它们都依赖于尚未被归类为稳定的属性/标签(Semantic Conventions)的命名约定。

接下来,在您的 ASP.NET Core 启动程序中配置 OpenTelemetry,您可以在其中访问 IServiceCollection

using OpenTelemetry;
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(b =>
  {
      b
      .AddSource(serviceName)
      .ConfigureResource(resource =>
          resource.AddService(
            serviceName: serviceName,
            serviceVersion: serviceVersion))
      .AddAspNetCoreInstrumentation()
      .AddConsoleExporter();
  });

这也是您可以配置仪表化库的位置。

请注意,此示例使用了 Console Exporter。如果要导出到其他端点,您需要使用不同的导出程序。

设置 ActivitySource

跟踪初始化后,您可以配置 ActivitySource,这将是您使用 Activity 跟踪操作的方式。

通常情况下,每个被仪表化的 app/service 实例化一个 ActivitySource,因此最好在共享位置创建一个实例。它的名称通常与服务名称相同。

using System.Diagnostics;

public static class Telemetry
{
    // ...

    // 使用您的应用程序服务名称命名
    // 它可以来自配置文件、常量文件等。
    public static readonly ActivitySource MyActivitySource = new(TelemetryConstants.ServiceName);

    // ...
}

如果适合您的场景,您可以创建多个 ActivitySource,尽管通常只需要为每个服务定义一个即可。

创建 Activities

要创建一个 Activity,给它一个名称,然后从您的 ActivitySource 创建它。

using var myActivity = MyActivitySource.StartActivity("SayHello");

// 这里可以进行 'myActivity' 将要跟踪的工作

创建嵌套的 Activities

如果您有一个明确的子操作,希望将其作为另一个操作的一部分来跟踪,那么您可以创建代表这种关系的活动。

public static void ParentOperation()
{
    using var parentActivity = MyActivitySource.StartActivity("ParentActivity");

    // 使用 parentActivity 跟踪一些工作

    ChildOperation();

    // 再次使用 parentActivity 跟踪工作
}

public static void ChildOperation()
{
    using var childActivity = MyActivitySource.StartActivity("ChildActivity");

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

在跟踪可视化工具中查看跨度时,ChildActivity 将作为 ParentActivity 的嵌套操作进行跟踪。

同一作用域中的嵌套 Activities

您可能希望在同一作用域中创建一个父子关系。虽然这是可能的,但通常不建议这样做,因为您需要小心确保在预期结束时结束任何嵌套的 Activity

public static void DoWork()
{
    using var parentActivity = MyActivitySource.StartActivity("ParentActivity");

    // 使用 parentActivity 跟踪一些工作

    using (var childActivity = MyActivitySource.StartActivity("ChildActivity"))
    {
        // 在同一个函数中做一些 "child" 工作
    }

    // 再次使用 parentActivity 跟踪工作
}

在上面的示例中,childActivity 被结束,因为 using 块的作用域是显式定义的,而不是像 DoWork 函数本身一样。

创建独立的 Activities

前面的示例演示了如何创建遵循嵌套层次结构的 Activities。但有时候,您可能希望创建独立的 Activities,它们是同一个根活动的同级而不是嵌套的活动。

public static void DoWork()
{
    using var parent = MyActivitySource.StartActivity("parent");

    using (var child1 = DemoSource.StartActivity("child1"))
    {
        // 这里做一些 'child1' 会跟踪的工作
    }

    using (var child2 = DemoSource.StartActivity("child2"))
    {
        // 这里做一些 'child2' 会跟踪的工作
    }

    // 'child1' 和 'child2' 都将 'parent' 作为父级,但它们是彼此独立的
}

创建新的根 Activity

如果希望创建一个新的根 Activity,您需要 “去关联” 当前的 Activity。

public static void DoWork()
{
    var previous = Activity.Current;
    Activity.Current = null;

    var newRoot = MyActivitySource.StartActivity("NewRoot");

    // 重新设置先前的当前 Activity,以便跟踪不会出错
    Activity.Current = previous;
}

获取当前 Activity

有时候,访问特定时间点的当前 Activity 可以帮助您通过添加更多信息来丰富它。

var activity = Activity.Current;
// 如果没有任何活动,则可能为 null

请注意,之前的示例中没有使用 using。使用 using 将结束当前的 Activity,这可能不是您想要的结果。

向 Activity 添加标签

标签(OpenTelemetry 中的 Attributes 等效项)允许您将键/值对附加到 Activity,以便在跟踪当前操作时携带更多信息。

using var myActivity = MyActivitySource.StartActivity("SayHello");

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

我们建议使用常量定义所有标签名称,而不是在其中定义名称,这样既可以提供一致性,又可以提供可发现性。

添加事件

事件Activity 上的人类可读消息,表示其生命周期中的 “某个事情发生”。

using var myActivity = MyActivitySource.StartActivity("SayHello");

// ...

myActivity?.AddEvent(new("Gonna try it!"));

// ...

myActivity?.AddEvent(new("Did it!"));

事件还可以通过时间戳和一组标签创建。

using var myActivity = MyActivitySource.StartActivity("SayHello");

// ...

myActivity?.AddEvent(new("Gonna try it!", DateTimeOffset.Now));

// ...

var eventTags = new Dictionary<string, object?>
{
    { "foo", 1 },
    { "bar", "Hello, World!" },
    { "baz", new int[] { 1, 2, 3 } }
};

myActivity?.AddEvent(new("Gonna try it!", DateTimeOffset.Now, new(eventTags)));

添加链接

一个 Activity 可以使用零个或多个 ActivityLink 建立起因果关系。

// 从某处获取上下文,可能作为参数传递进来
var activityContext = Activity.Current!.Context;

var links = new List<ActivityLink>
{
    new ActivityLink(activityContext)
};

using var anotherActivity =
    MyActivitySource.StartActivity(
        ActivityKind.Internal,
        name: "anotherActivity",
        links: links);

// 做一些工作

设置 Activity 状态

在一个 Activity 上可以设置一个 状态,通常用于指定一个 Activity 未成功完成 - ActivityStatusCode.Error。在极少数情况下,您可以使用 Ok 覆盖 Error 状态,但不要将 Ok 设置在成功完成的跨度上。

可以在跨度完成之前的任何时候设置状态:

using var myActivity = MyActivitySource.StartActivity("SayHello");

try
{
	// 执行一些操作
}
catch (Exception ex)
{
    myActivity.SetStatus(ActivityStatusCode.Error, "Something bad happened!");
}

指标

指标 API 和 SDK 的文档尚缺失,您可以通过编辑此页面来帮助使其可用。

日志

日志 API 和 SDK 目前正在开发中。

下一步

在设置手动仪表化之后,您可能希望使用仪表化库。顾名思义,它们将为您使用的相关库进行仪表化,并为入站和出站的 HTTP 请求等生成跨度(活动)。

您还需要配置合适的导出器,将您的遥测数据导出到一个或多个遥测后端。您可以查阅 遥测数据导出相关文档。

您还可以查看 自动仪表化 .NET,目前正在测试阶段。

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