OpenTelemetry跟踪Shim
.NET与其他支持OpenTelemetry的语言/运行时有所不同。跟踪是通过System.Diagnostics API实现的,将ActivitySource
和Activity
等旧构造重新用于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请求等数据。
您还需要配置适当的导出器,以将您的遥测数据导出到一个或多个遥测后端。