关于 opentelemetry-net-instrumentation
面向 .NET 的 Claude Code 技能,用于添加、审查和优化 OpenTelemetry 埋点。它强调使用 .NET 内置诊断 API,并区分库代码埋点与应用级采集、导出配置。
主要功能
- 01跟踪、指标与日志指导
- 02ActivitySource 和 Meter 模式
- 03SDK、导出器与采样设置
- 04弹性与语义约定检查
适用场景
- 01为 .NET 库埋点且不添加 OTel 包
- 02配置应用遥测导出
- 03审查 Span 错误和属性
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ aaronontheweb/ dotnet-skills --skill opentelemetry-net-instrumentation安装后在 Claude Code 中输入/opentelemetry-net-instrumentation确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
metrics-and-instruments-reference.mdsdk-resources-and-logs-reference.mdtraces-and-propagation-reference.md详细介绍
OpenTelemetry .NET 埋点技能
使用场景
- 为 .NET 代码添加 OpenTelemetry 埋点(跟踪、指标、日志)
- 创建或修改 ActivitySources、Meters 或 ILogger 用法
- 配置 OpenTelemetry SDK、资源、导出器或采样
- 审查遥测实现是否符合规范
- 优化埋点性能
- 设计将成为公共接口一部分的遥测 API
- 实现跨服务边界的上下文传播
架构:.NET 与众不同
重要:.NET 的 OpenTelemetry 实现与其他平台有根本区别。.NET 在框架本身中提供跟踪、指标和日志 API。 这意味着 OTel 不提供独立的埋点 API——它使用内置的 .NET API,并充当数据收集与导出层。
.NET 内置的三种 API(主要方式——零依赖)
| 信号 | .NET 框架 API | 命名空间 |
|---|---|---|
| 跟踪 | ActivitySource / Activity | System.Diagnostics |
| 指标 | Meter / Counter<T> / Histogram<T> / 等 | System.Diagnostics.Metrics |
| 日志 | ILogger<T> | Microsoft.Extensions.Logging |
这些是库作者进行埋点时应使用的主要且唯一的 API。 它们随 .NET 运行时提供——无需 NuGet 包。
OTel 数据收集与导出层(次要——仅用于应用程序根项目)
OTel NuGet 包是数据收集与导出层,仅添加到应用程序组合根项目中(不添加到库中):
| 包 | 用途 | 添加时机 |
|---|---|---|
OpenTelemetry.Extensions.Hosting | 为 ASP.NET Core / 通用主机提供 DI 集成 | 仅应用程序 |
OpenTelemetry.Exporter.Console | 控制台导出器(开发/测试) | 仅应用程序 |
OpenTelemetry.Exporter.OpenTelemetryProtocol | OTLP 导出器(生产环境) | 仅应用程序 |
OpenTelemetry.Exporter.Prometheus* | Prometheus 指标端点 | 仅应用程序 |
OpenTelemetry.Instrumentation.AspNetCore | 自动为 ASP.NET Core 请求添加埋点 | 仅应用程序 |
OpenTelemetry.Instrumentation.Http | 自动为 HttpClient 调用添加埋点 | 仅应用程序 |
OpenTelemetry.Instrumentation.SqlClient | 自动为 SQL 调用添加埋点 | 仅应用程序 |
包选择指南
添加任何 OpenTelemetry NuGet 包之前,请先与用户讨论其中的权衡:
“你即将添加一个 OTel NuGet 包。你是在开发需要将遥测数据导出到可观测性后端(Jaeger、Prometheus、OTLP collector)的应用程序吗? 如果你正在编写库,很可能不需要任何 OTel 包——只需使用
System.Diagnostics.ActivitySource/System.Diagnostics.Metrics.Meter,并让使用该库的应用程序配置导出管道。要继续吗?”
库作者:什么都不添加。只使用 System.Diagnostics.* 和 ILogger。
使用该库的应用程序负责接入 SDK 和导出器。
应用程序作者:添加 OpenTelemetry.Extensions.Hosting,以及所需的导出器和埋点库。完整配置模式请参见 sdk-resources-and-logs-reference.md。
切勿在库中添加 OpenTelemetry.Api——System.Diagnostics.* 就是 API。
有关 SDK 配置、资源配置、导出器、采样和日志集成,请参见 sdk-resources-and-logs-reference.md。
核心原则
弹性优先
重要:诊断/跟踪/指标逻辑中的异常绝不能影响应用程序处理。
- 假设 Activity 实例可能为 null。始终防范 Activity 引用为 null 的情况,但 Activity 扩展方法除外(使用
activity?.ExtensionMethod()) - 对所有埋点代码添加适当的 null 检查
API 表面认知
- 发出的任何遥测数据都会成为公共 API 表面的一部分
- 变更受破坏性变更指南约束
- 默认应发出遥测数据(用户通过 OpenTelemetry 扩展选择加入数据收集)
- 例外:高基数指标维度可能需要显式选择加入
标准合规性
- 遵循 Microsoft 关于分布式跟踪插桩的最佳实践
- 遵循 OpenTelemetry 语义约定
- 属性值支持:字符串、布尔值、double(IEEE 754)、int64、字节数组,以及由这些基本类型构成的同质数组。根据 OTel AnyValue 规范,Null/空值有效且有意义——必须存储并传递给导出器。
- 属性键必须是非 null、非空字符串
跟踪 / Span(Activity)
ActivitySource 设置
// ✅ CORRECT: Use ActivitySource, not DiagnosticSource
public class MyFeature
{
// Primary ActivitySource - name typically matches the component or NuGet package name
private static readonly ActivitySource ActivitySource = new("MyApp.MyComponent", "1.0.0");
// Specialized ActivitySource for opt-in scenarios
private static readonly ActivitySource DetailedActivitySource = new("MyApp.MyComponent.Detailed", "1.0.0");
}
规则:
- 每个组件都为主流 Activity 定义一个主要的
ActivitySource - 名称通常与组件或 NuGet 包相匹配(例如,
"MyCompany.MyLibrary") - 使用 SemVer 为 ActivitySource 设置版本
- 为专用或选择加入的场景创建单独的
ActivitySource。使用分层的源名称,例如MyCompany.MyLibrary和MyCompany.MyLibrary.Detailed,这样使用方应用就能通过AddSource(...)仅订阅所需的源,后端也能按插桩范围进行筛选。
创建 Activity
// ✅ Check HasListeners, null-check, then guard expensive work behind IsAllDataRequested
if (ActivitySource.HasListeners())
{
using var activity = ActivitySource.StartActivity("ProcessItem", ActivityKind.Internal);
if (activity != null && activity.IsAllDataRequested)
{
activity.DisplayName = "Processing order #12345";
activity.SetTag("app.item_id", itemId);
activity.SetTag("app.item_type", itemType);
}
}
// ❌ WRONG: Don't start activities in fire-and-forget tasks where the
// using scope ends before the async work completes (AsyncLocal context is lost)
async Task HelperAsync()
{
using var activity = ActivitySource.StartActivity("Helper");
_ = Task.Run(() => DoWorkAsync()); // ❌ activity disposed before task completes
}
规则:
- 创建前检查
ActivitySource.HasListeners()(零分配快速路径) - 创建后始终检查 Activity 是否为 null(监听器可能会筛选掉它或不对其采样)
- 切勿在异步辅助方法中启动 Activity(
Activity.Current使用AsyncLocal) - 使用
activity.IsAllDataRequested对开销较大的标签计算进行保护 - 使用 W3C TraceContext。.NET Core 3.0+ / .NET 5+ 默认使用该格式;较旧的 TFM 或 .NET Framework 应用可在启动时设置
Activity.DefaultIdFormat = ActivityIdFormat.W3C,并使用Activity.ForceDefaultIdFormat = true覆盖分层父级。
Activity 命名
// ✅ Unique operation name, friendly display name (null-check before accessing)
using var activity = ActivitySource.StartActivity(
name: "ProcessItem", // Unique, identifies class of spans
kind: ActivityKind.Internal
);
if (activity != null)
activity.DisplayName = "Processing order #12345"; // User-friendly, can be specific
// ❌ WRONG: Don't include runtime data in operation name
using var badActivity = ActivitySource.StartActivity($"Process_{itemId}"); // ❌
规则:
- 每种 Span 类型都有唯一的
OperationName(用于标识具有统计意义的一类 Span) - 操作名称不应包含运行时数据(仅包含编译时或配置时信息)
- 使用易于理解的
DisplayName表示具体信息 - 遵循 OpenTelemetry Span 命名约定
SpanKind 选择
选择正确的 ActivityKind,以明确 Span 在分布式跟踪中的作用:
ActivityKind | OTel SpanKind | 使用场景 |
|---|---|---|
Internal | INTERNAL | 默认——不跨越远程边界的进程内操作 |
Server | SERVER | 处理传入的请求/响应调用(HTTP 服务端、gRPC 服务端、RPC 服务端) |
Client | CLIENT | 发起传出的请求/响应调用(HTTP 客户端、数据库客户端、RPC 调用) |
Producer | PRODUCER | 将延迟执行的工作入队/发布(发布到消息队列、发出事件、作业入队) |
Consumer | CONSUMER | 将延迟执行的工作出队/处理(接收消息队列消息、处理事件、作业出队) |
规则:
- 单个 Span 不应承担多种用途
- 在将其
SpanContext注入请求之前,创建传出的 Span。如果先注入,传播的将是父级的上下文,传出的 Span 最终会成为悬空 Span(与下游调用没有关联)。 - 详细的 SpanKind 指南及示例,请参阅 traces-and-propagation-reference.md
Span 属性(标签)
// ✅ Application code: use your own namespace
activity?.SetTag("myapp.order_id", orderId);
activity?.SetTag("myapp.payment.status", "confirmed");
// ✅ Manual infrastructure instrumentation: use semantic conventions
// activity?.SetTag("db.system.name", "postgresql"); // custom database client
// activity?.SetTag("http.request.method", "GET"); // custom HTTP transport
// Values can be strings, numbers, booleans, or homogeneous arrays
activity?.SetTag("app.item_count", 42);
activity?.SetTag("app.related_ids", new int[] { 1, 2, 3 });
// ❌ WRONG: PascalCase, hyphen delimiter, plural, or unrelated namespace
activity?.SetTag("MyApp.OrderId", orderId); // ❌ Wrong case
activity?.SetTag("myapp.order-id", orderId); // ❌ Wrong delimiter
规则:
- 命名空间前缀应与组件匹配:
myapp.*、myapp.db.* - 全部使用小写字母,以下划线(
_)分隔,并采用单数形式 - 属性值:字符串、布尔值、双精度浮点数、int64、字节数组、同质数组(根据 AnyValue 规范,null/空值有效)
- 业务/领域属性:使用自己的命名空间(
myapp.*)。 - 手动埋点的 HTTP、数据库、消息传递或 RPC 概念:使用语义约定。请勿重复记录自动埋点已发出的属性。请勿使用 OTel 命名空间作为自定义属性的前缀。
Activity 状态和错误
try
{
await ProcessItemAsync(); // ✅ success: leave status Unset, do not call SetStatus(Ok) — see rules below
}
catch (Exception ex)
{
if (activity != null)
{
activity.SetStatus(ActivityStatusCode.Error, ex.Message); // modern API
activity.SetTag("error.type", ex.GetType().FullName);
}
throw;
}
规则(依据记录错误和设置状态 API 规范):
- 操作成功时,将 span 状态保持为 Unset——不要调用
SetStatus(ActivityStatusCode.Ok)。 Ok适用于应用程序代码,不适用于埋点库。Trace API 规范规定:“除非经过明确配置,否则埋点库不应将状态码设为Ok(...) 应用程序开发人员和运维人员可以将状态码设为Ok”——通常用于覆盖他们认定并非真实故障的库报告的Error(例如,抑制频繁出现的 404)。一旦设置Ok,状态即为最终状态;后续调用会被忽略。”- 发生故障时:应设置
ActivityStatusCode.Error,应设置error.type标签,应将状态描述设为异常消息。 - 使用
SetStatus——不再需要旧版otel.status_code/otel.status_description标签。 - 对于经过重试或处理后已恢复、并顺利完成操作的错误,不要记录错误——
Error状态和error.type用于描述失败的操作,而非已恢复的操作。
记录异常 — 继续发出 Span 事件,并添加日志启用选项
exceptions-spans——即 Activity.AddEvent(new ActivityEvent("exception", ...)) 背后的约定——已被标记为已弃用,建议改用 exceptions-logs,但 .NET 生态尚未跟进:没有任何 OpenTelemetry.* .NET 包实现规范中的 OTEL_SEMCONV_EXCEPTION_SIGNAL_OPT_IN 启用选项(已针对 OpenTelemetry.Api 1.17.0 验证),而 OpenTelemetry.Instrumentation.AspNetCore 1.17.0 本身仍通过 Activity.AddException 记录异常,也就是记录为 Span 事件。这意味着,典型 .NET 用户如今配置的导出器、后端和仪表板都能读取异常 Span 事件,而不是基于日志的异常。继续默认实现 Span 事件——仅仅因为底层规范文档标记为“已弃用”就移除它们,会导致仍在使用当前工具的用户无法看到异常,且不会收到任何提示。按照规范自身对从 Span 事件迁移的检测工具所给出的过渡指导,在此基础上添加基于日志的新路径,并将其设为启用选项:
// Mirrors the spec's OTEL_SEMCONV_EXCEPTION_SIGNAL_OPT_IN values: unset/anything else → spans only (today's default), "logs/dup" → both, "logs" → logs only.
private static readonly string? ExceptionSignalOptIn = Environment.GetEnvironmentVariable("OTEL_SEMCONV_EXCEPTION_SIGNAL_OPT_IN");
private static readonly bool EmitSpanEvents = ExceptionSignalOptIn != "logs";
private static readonly bool EmitLogs = ExceptionSignalOptIn is "logs" or "logs/dup";
try
{
await ProcessItemAsync();
}
catch (Exception ex)
{
activity?.SetStatus(ActivityStatusCode.Error, ex.Message);
activity?.SetTag("error.type", ex.GetType().FullName);
if (EmitSpanEvents) // ✅ default — what current .NET tooling and dashboards actually consume today
{
activity?.AddEvent(new ActivityEvent("exception", tags: new ActivityTagsCollection
{
["exception.type"] = ex.GetType().FullName,
["exception.message"] = ex.Message,
["exception.stacktrace"] = ex.ToString()
}));
}
if (EmitLogs) // opt-in — the spec's forward direction; pass the exception instance while `activity` is still Activity.Current so the SDK derives trace_id/span_id and exception.type/message/stacktrace from it
{
logger.LogError(ex, "Item processing failed");
}
throw;
}
规则:
- 不要在 Span 事件上设置
exception.escaped——它已被明确弃用:“不再建议记录已处理且未逸出 Span 作用域的异常。” - 支持
OTEL_SEMCONV_EXCEPTION_SIGNAL_OPT_IN(logs/logs/dup),让已准备就绪的使用者可以选择日志或双重发出,但默认仅发出 Span 事件——这是规范自身的过渡指导,并非可有可无的优化;它的存在正是为了让任何人都不必在准备就绪之前被迫二选一。 - 发出日志时(无论是启用日志还是双重发出),按照
exceptions-logs选择严重性:未处理异常使用ERROR(尤其是SERVER/CONSUMERSpan 上的异常),预期由调用方处理的异常使用WARN(尤其是CLIENT/PRODUCERSpan 上的异常),不代表实际问题的异常使用DEBUG(例如在客户端取消请求),只有异常导致应用程序关闭时才使用FATAL。 - 日志调用必须在其关联的 Span 仍为当前 Span 时发生——规范要求:“同时为同一操作记录 Span 的检测工具所发出的异常事件,必须与相应的 Span 上下文关联。”在
usingActivity 作用域结束后,通过已脱离上下文的错误报告回调记录日志,会导致日志错误地关联到其他 Span,或者完全无法关联。请参阅下文的访问 Activity。 - 只有在稳定的主版本上保持双重发出至少六个月(规范自身规定的最短期限),并且确认实际使用者会读取基于日志的异常后,才能改为仅使用
logs——不能仅凭固定时间表迁移。
完整模式请参阅 traces-and-propagation-reference,其中包括没有日志依赖的核心库如何仍能公开回调,以便独立的 OTel 集成包在正确的 Span 仍处于活动状态时捕获异常详情。
访问 Activity
var current = Activity.Current; // ❌ may be a user-created ambient span
using var ownedActivity = ActivitySource.StartActivity("MyOperation"); // ✅ captured reference
ownedActivity?.SetTag("myapp.key", value);
规则:对于自己拥有的 Span,不要依赖 Activity.Current;用户代码可以通过 AsyncLocal 替换它。仅在 Activity 仍有效时传递或存储捕获的 Activity。传播标识时存储 ActivityContext。
Span 链接
链接用于将一个 Span 与其他具有因果关系、但不处于直接父子关系的 Span 关联起来——例如批处理、分发/汇聚以及跨越 Trace 边界。
var links = new List<ActivityLink>
{
new(activityContext1),
new(activityContext2),
};
var activity = ActivitySource.StartActivity(
ActivityKind.Internal, name: "batch-process", links: links);
完整的链接模式请参阅 traces-and-propagation-reference.md,其中包括批处理、分发/汇聚以及跨越 Trace 边界。
上下文传播
分布式追踪需要使用 W3C traceparent 标头跨进程边界(HTTP 调用、消息队列等)传播追踪上下文。在 .NET 中,这由 DistributedContextPropagator 负责处理。OTel SDK 默认配置 W3C TraceContext 传播。
有关传播模式、自定义传播器,以及针对非标准传输方式的手动注入/提取,请参阅 traces-and-propagation-reference.md。
指标
Meter 和 Metrics 类设置
public sealed class OrderProcessingMetrics : IDisposable
{
private readonly Meter meter = new("MyApp.OrderProcessing", "1.0.0");
private readonly Histogram<double> processingDuration =
meter.CreateHistogram<double>("myapp.order.processing.duration", unit: "s");
private readonly Counter<long> itemsProcessed =
meter.CreateCounter<long>("myapp.order.processing.count", unit: "{order}");
public void Dispose() => meter.Dispose();
}
命名约定(遵循 OTel 语义约定):
- 使用单数名称和嵌套层级:
myapp.order.processing.duration - 定义单位(s、ms、{item}、{connection});避免使用技术后缀(
_counter、_histogram) - 在采用情况得到验证之前,使用 pre-1.0.0 版本
仪表类型概览
.NET 提供 7 种指标仪表类型。请根据测量内容选择合适的类型:
| 仪表 | .NET API | 行为 | 典型用途 |
|---|---|---|---|
| Counter | CreateCounter<T> | 单调递增 | 请求计数、错误计数 |
| UpDownCounter | CreateUpDownCounter<T> | 可增可减 | 队列大小、活动连接数 |
| Histogram | CreateHistogram<T> | 值的分布 | 持续时间、响应大小 |
| Gauge | CreateGauge<T> (.NET 9+) | 同步瞬时值 | 记录当前测量值 |
| ObservableCounter | CreateObservableCounter<T> | 异步回调,单调递增 | 定期轮询的总计数 |
| ObservableGauge | CreateObservableGauge<T> | 异步回调,非单调 | CPU/内存使用量 |
| ObservableUpDownCounter | CreateObservableUpDownCounter<T> | 异步回调,双向变化 | 按优先级统计的活动任务数 |
有关完整的创建/记录示例和可观测回调模式,请参阅 metrics-and-instruments-reference.md。
指标记录方法命名
// ✅ Action/outcome-based naming, separate methods per outcome
public void OrderProcessingSucceeded(string orderType, TimeSpan duration) { /* Record */ }
public void OrderProcessingFailed(string orderType, Exception ex, TimeSpan duration) { /* Record */ }
public void ConnectionOpened() => connectionsOpen.Add(1);
public void ConnectionClosed() => connectionsOpen.Add(-1);
// ❌ WRONG: Name after metric, confusing signature
public void RecordOrderProcessingDuration(...) { } // ❌ don't name after metric
public void RecordError(bool succeeded, Exception? ex) { } // ❌ confusing signature
规则:
- 根据操作/结果命名(
OrderProcessingSucceeded),而不是根据指标命名(RecordXxx) - 针对不同结果使用独立的方法(避免使用布尔标志和可选异常)
- 状态变化采用基于事件的命名:
ConnectionOpened()、ItemQueued()
指标维度
// ✅ Low-cardinality, predefined dimensions
processingDuration.Record(duration.TotalSeconds,
new KeyValuePair<string, object?>("myapp.order_type", orderType), // bounded set
new KeyValuePair<string, object?>("outcome", "success")); // bounded set
// ❌ High-cardinality: unbounded values cause cardinality explosion
failureCount.Add(1, new KeyValuePair<string, object?>("order_id", orderId)); // ❌ unbounded
规则:
- 维度 MUST 预先定义且基数较低(项目类型、队列名称、结果)
- 避免使用无界值(每个唯一值都会新增一行时间序列 → 基数爆炸)
- 高基数维度 MUST 通过可选配置启用
- 各组件使用一致的名称:
myapp.region在所有地方都表示相同含义 - 用户可以启用 exemplars 以关联追踪(而非通过维度关联)
性能要求
默认情况下,插桩 MUST 开销低。请遵循以下规则以尽量减少开销:
零分配快速路径
// ✅ CORRECT: Guard with cheap checks
if (ActivitySource.HasListeners())
{
using var activity = ActivitySource.StartActivity("Operation");
// ... expensive work
}
// ✅ CORRECT: Use TagList (struct) for metrics
var tags = new TagList
{
{ "myapp.order_type", orderType },
{ "outcome", "success" }
};
counter.Add(1, tags);
计时
// ✅ Timestamp math (no allocation)
var startTime = Stopwatch.GetTimestamp();
try { await ProcessAsync(); }
finally { var duration = Stopwatch.GetElapsedTime(startTime); metrics.OrderProcessingSucceeded(orderType, duration); }
// ❌ Allocates: Stopwatch.StartNew() or IDisposable timing wrappers
避免隐式分配
// ❌ Allocates: string interpolation without IsAllDataRequested guard
activity?.SetTag("item", $"Processing {itemId}"); // ❌
// ✅ Guard expensive work behind IsAllDataRequested
if (activity?.IsAllDataRequested == true)
activity.SetTag("item", $"Processing {itemId}");
规则:
- 不要使用
Stopwatch.StartNew()(请使用Stopwatch.GetTimestamp()/GetElapsedTime) - 优先使用
TagList(结构体),而非数组/字典 - 热路径中不要使用 LINQ、字符串插值或异步状态机,除非有保护措施
测试要求
Span 测试
[Test]
public async Task Should_create_processing_span_with_correct_parent()
{
// Arrange
using var parent = new Activity("Parent").Start();
// Act
await handler.Handle(item);
// Assert
var processingSpan = recordedActivities.Single(a => a.OperationName == "ProcessItem");
Assert.AreEqual(parent.Id, processingSpan.ParentId);
Assert.AreEqual("myapp.item_type", processingSpan.Tags.First().Key);
}
[Test]
public void Should_not_introduce_breaking_changes_to_span_names()
{
// Ensures string values in span names are under test
Assert.AreEqual("ProcessItem", MyFeature.SpanName);
}
规则:
- 测试哪些 span 与哪些活动关联
- 测试字符串值(span 名称、标签名称),以防止出现破坏性变更
- 请记住:遥测是公共 API 的一部分
版本控制
- 遥测版本与包版本解耦
- 使用 SemVer 语义
- Trace 和 Metrics 使用独立版本(各自演进)
- 在采用情况/实用性得到验证之前,使用 pre-1.0.0 版本
private static readonly ActivitySource ActivitySource = new("MyApp.MyComponent", "0.9.0");
private readonly Meter meter = new("MyApp.MyComponent", "0.8.0");
日志
.NET 日志通过内置的 ILogger API 与 OpenTelemetry 集成。OTel SDK 在日志构建器上提供 AddOpenTelemetry(),用于收集、处理和导出日志。日志记录会通过 TraceId/SpanId 自动与跟踪关联。
有关完整的日志集成模式,包括关联、脱敏、结构化日志和严重性筛选,请参阅 sdk-resources-and-logs-reference.md。
参考文件
- traces-and-propagation-reference.md:深入介绍 SpanKind 并提供示例、Span Links(批处理、散射/收集、跟踪边界)、上下文传播(W3C traceparent、DistributedContextPropagator、自定义传播器)、Baggage,以及完整的现代异常记录模式。
- metrics-and-instruments-reference.md:全部 7 种度量仪器类型及其创建/记录代码和使用场景指南、可观察仪器回调模式、维度深入介绍、示例数据、聚合默认值和单位。
- sdk-resources-and-logs-reference.md:SDK 初始化(ASP.NET Core + Console)、资源配置(ResourceBuilder、AddService、AddDetector、自定义检测器)、导出器(OTLP、Console、Jaeger、Zipkin、Prometheus)、检测库、采样(内置、自定义、环境变量、头部/尾部采样)、日志集成(ILogger、关联、脱敏、结构化日志)和环境变量配置。