OpenTelemetry Tracing Shim
.NET 与其他支持 OpenTelemetry 的语言/运行时有所不同。跟踪是通过 System.Diagnostics API 实现的,它在底层重新利用了 ActivitySource 和 Activity 等旧构造,使其符合 OpenTelemetry 标准。
OpenTelemetry for .NET 还提供了一个基于 System.Diagnostics 的实现之上的 API 适配层。如果您在同一代码库中与其他语言和 OpenTelemetry 协同工作,或者更喜欢使用与 OpenTelemetry 规范一致的术语,那么这个适配层会很有帮助。
初始化跟踪
根据您使用的是控制台应用还是基于 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();
//...
这也是您可以配置检测库的地方。
请注意,此示例使用了控制台导出器。如果您要导出到其他端点,则必须使用不同的导出器。
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
然后在您的 ASP.NET Core 启动例程中进行配置,在那里您可以访问 IServiceCollection。
using OpenTelemetry.Resources;
using OpenTelemetry.Trace;
// These can come from a config file, constants file, etc.
var serviceName = "MyCompany.MyProduct.MyService";
var serviceVersion = "1.0.0";
var builder = WebApplication.CreateBuilder(args);
// Configure important OpenTelemetry settings, the console exporter, and instrumentation library
builder.Services.AddOpenTelemetry().WithTracing(tcb =>
{
tcb
.AddSource(serviceName)
.SetResourceBuilder(
ResourceBuilder.CreateDefault()
.AddService(serviceName: serviceName, serviceVersion: serviceVersion))
.AddAspNetCoreInstrumentation()
.AddConsoleExporter();
});
在前面的示例中,在设置过程中注入了一个与服务对应的Tracer。这使您可以在端点映射(或如果您使用的是旧版本的 .NET,则在控制器)中访问该实例。
注入服务级别的跟踪器不是必需的,也不会提高性能。不过,您需要决定在哪里放置您的跟踪器实例。
这也是您可以配置检测库的地方。
请注意,此示例使用了控制台导出器。如果您要导出到其他端点,则必须使用不同的导出器。
设置跟踪器
跟踪初始化后,您可以配置一个Tracer,您将使用它通过Spans来跟踪操作。
通常,一个 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");
// do stuff
});
从 TracerProvider 获取跟踪器
如果您不使用 ASP.NET Core 或宁愿不注入 Tracer 的实例,请从您已实例化的TracerProvider中创建一个。
// ...
var tracer = tracerProvider.GetTracer(serviceName);
// Assign it somewhere globally
//...
您很可能希望将此 Tracer 实例分配到一个中心位置的变量中,以便在您的服务中随处可用。
您可以在每个服务中实例化任意数量的 Tracer,但通常每个服务只定义一个就足够了。
创建 Span
要创建span,请为其命名并从您的 Tracer 中创建它。
using var span = MyTracer.StartActiveSpan("SayHello");
// do work that 'span' will now track
创建嵌套 Span
如果您有需要跟踪的独立子操作作为另一个操作的一部分,您可以创建 Span 来表示这种关系。
public static void ParentOperation(Tracer tracer)
{
using var parentSpan = tracer.StartActiveSpan("parent-span");
// Do some work tracked by parentSpan
ChildOperation(tracer);
// Finish up work tracked by parentSpan again
}
public static void ChildOperation(Tracer tracer)
{
using var childSpan = tracer.StartActiveSpan("child-span");
// Track work in ChildOperation with childSpan
}
当您在跟踪可视化工具中查看 Span 时,child-span 将被跟踪为 parent-span" 下的一个嵌套操作。
同一作用域内的嵌套 Span
您可能希望在同一个作用域内创建父子关系。虽然可能,但这通常不被推荐,因为您需要小心地结束任何嵌套的 TelemetrySpan,使其在您期望的时间结束。
public static void DoWork(Tracer tracer)
{
using var parentSpan = tracer.StartActiveSpan("parent-span");
// Do some work tracked by parentSpan
using (var childSpan = tracer.StartActiveSpan("child-span"))
{
// Do some "child" work in the same function
}
// Finish up work tracked by parentSpan again
}
在前面的示例中,childSpan 被结束了,因为 using 块的作用域是明确定义的,而不是像 parentSpan 那样作用于 DoWork 本身。
创建独立 Span
前面的示例展示了如何创建遵循嵌套层次结构的Span。在某些情况下,您会希望创建独立的 Span,它们是同一个根的同级,而不是嵌套的。
public static void DoWork(Tracer tracer)
{
using var parent = tracer.StartSpan("parent");
// 'parent' will be the shared parent of both 'child1' and 'child2'
using (var child1 = tracer.StartSpan("child1"))
{
// do some work that 'child1' tracks
}
using (var child2 = tracer.StartSpan("child2"))
{
// do some work that 'child2' tracks
}
}
创建新的根 Span
您也可以创建与当前跟踪完全分离的新根span。
public static void DoWork(Tracer tracer)
{
using var newRoot = tracer.StartRootSpan("newRoot");
}
获取当前 Span
有时,在某个时间点访问当前的 TelemetrySpan 以便用更多信息丰富它会很有帮助。
var span = Tracer.CurrentSpan;
// do cool stuff!
请注意,前面的示例未使用 using。这样做会在 TelemetrySpan 离开作用域时结束它,这可能不是您期望的行为。
向 Span 添加属性
属性允许您将键/值对附加到 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 });
添加事件
一个事件是 TelemetrySpan 上一个人类可读的消息,它表示在其生命周期中“发生了什么”。您可以将其视为一个原始日志。
using var span = tracer.StartActiveSpan("SayHello");
// ...
span.AddEvent("Doing something...");
// ...
span.AddEvent("Dit it!");
事件还可以使用时间戳和一组属性来创建。
using var span = tracer.StartActiveSpan("SayHello");
// ...
span.AddEvent("event-message");
span.AddEvent("event-message2", DateTimeOffset.Now);
// ...
var attributeData = new Dictionary<string, object>
{
{"foo", 1 },
{ "bar", "Hello, World!" },
{ "baz", new int[] { 1, 2, 3 } }
};
span.AddEvent("asdf", DateTimeOffset.Now, new(attributeData));
添加链接
一个 TelemetrySpan 可以与零个或多个因果相关的Links一起创建。
// Get a context from somewhere, perhaps it's passed in as a parameter
var ctx = span.Context;
var links = new List<Link>
{
new(ctx)
};
using var span = tracer.StartActiveSpan("another-span", links: links);
// do some work
设置跨度状态
可以在 Span 上设置状态,通常用于指定 Span 未成功完成 - StatusCode.Error。在极少数情况下,您可以用 Ok 覆盖 Error 状态,但不要对成功完成的 Span 设置 Ok。
可以在 Span 结束前的任何时候设置状态
using var span = tracer.StartActiveSpan("SayHello");
try
{
// do something
}
catch (Exception ex)
{
span.SetStatus(new(StatusCode.Error, "Something bad happened!"));
}
在 Span 中记录异常
在发生异常时记录异常可能是一个好主意。建议结合设置Span 状态来执行此操作。
using var span = tracer.StartActiveSpan("SayHello");
try
{
// do something
}
catch (Exception ex)
{
span.SetStatus(new(StatusCode.Error, "Something bad happened!"));
span.RecordException(ex)
}
这将把当前堆栈跟踪等内容捕获为 Span 中的属性。
下一步
在设置了手动检测后,您可能需要使用检测库。检测库将检测您正在使用的相关库,并为入站和出站 HTTP 请求等生成数据。
您还需要配置适当的导出器,以便将 遥测数据导出到一个或多个遥测后端。