.NET OpenTelemetry 接入指南

本文档提供 .NET 应用接入 OpenTelemetry 的完整指南,包括零代码接入和 SDK 手动接入两种方式,支持 C#、F# 和 VB.NET。

目录


概述

.NET 语言特性

.NET 的 OpenTelemetry 支持具有以下特点:

  • 成熟稳定: API 和 SDK 已达到 1.0 稳定版本
  • 零代码接入: 通过自动埋点实现零侵入
  • 多语言支持: 支持 C#、F#、VB.NET
  • 丰富的埋点库: 支持 30+ 主流框架和库
  • ASP.NET Core 集成: 原生支持 ASP.NET Core
  • 性能优异: 低开销,高性能

版本要求

  • .NET 版本: .NET 6.0+ (推荐 .NET 8.0+)
  • OpenTelemetry .NET: v1.0.0+
  • 支持架构: x64, arm64

支持的信号

  • ✅ Traces: 完全支持
  • ✅ Metrics: 完全支持
  • ✅ Logs: 完全支持

适用场景

  • ASP.NET Core Web 应用
  • gRPC 服务
  • 控制台应用
  • 后台服务
  • 微服务架构

零代码接入(推荐)

方式 1: 使用 OpenTelemetry .NET Automatic Instrumentation

OpenTelemetry .NET 提供了自动埋点工具,无需修改代码即可接入。

安装

Windows:

# 下载安装脚本
Invoke-WebRequest -Uri "https://raw.githubusercontent.com/open-telemetry/opentelemetry-dotnet-instrumentation/main/docs/install.ps1" -OutFile install.ps1
# 运行安装脚本
.\install.ps1

Linux/macOS:

# 下载安装脚本
curl -L https://raw.githubusercontent.com/open-telemetry/opentelemetry-dotnet-instrumentation/main/docs/install.sh | sh

配置环境变量

# 服务信息
export OTEL_SERVICE_NAME=my-dotnet-service
export OTEL_RESOURCE_ATTRIBUTES=service.version=1.0.0,deployment.environment=production
# OTLP Exporter
export OTEL_EXPORTER_OTLP_ENDPOINT=uptrace-grpc.yodao.cn:4317
export OTEL_EXPORTER_OTLP_PROTOCOL=grpc
export OTEL_EXPORTER_OTLP_HEADERS=uptrace-dsn=http://token@uptrace-grpc.yodao.cn/project
# Traces 配置
export OTEL_TRACES_EXPORTER=otlp
export OTEL_TRACES_SAMPLER=parentbased_traceidratio
export OTEL_TRACES_SAMPLER_ARG=0.1
# Metrics 配置
export OTEL_METRICS_EXPORTER=otlp
export OTEL_METRIC_EXPORT_INTERVAL=60000
# Logs 配置
export OTEL_LOGS_EXPORTER=otlp

启动应用

# 使用 dotnet-instrument 启动
dotnet-instrument --output myapp.dll
# 或使用环境变量
export OTEL_DOTNET_AUTO_ENABLED=true
dotnet run

方式 2: 使用 NuGet 包自动配置

安装 NuGet 包

# 安装 OpenTelemetry 自动配置包
dotnet add package OpenTelemetry.Instrumentation.AspNetCore
dotnet add package OpenTelemetry.Exporter.OpenTelemetryProtocol
dotnet add package OpenTelemetry.Extensions.Hosting

配置 Program.cs

using OpenTelemetry.Resources;
using OpenTelemetry.Trace;
using OpenTelemetry.Metrics;
using OpenTelemetry.Logs;
var builder = WebApplication.CreateBuilder(args);
// 添加 OpenTelemetry
builder.Services.AddOpenTelemetry()
.ConfigureResource(resource => resource
.AddService(serviceName: "my-dotnet-service",
serviceVersion: "1.0.0"))
.WithTracing(tracing => tracing
.AddAspNetCoreInstrumentation()
.AddHttpClientInstrumentation()
.AddOtlpExporter(options =>
{
options.Endpoint = new Uri("uptrace-grpc.yodao.cn:4317");
options.Headers = "uptrace-dsn=http://token@uptrace-grpc.yodao.cn/project";
}))
.WithMetrics(metrics => metrics
.AddAspNetCoreInstrumentation()
.AddHttpClientInstrumentation()
.AddOtlpExporter(options =>
{
options.Endpoint = new Uri("uptrace-grpc.yodao.cn:4317");
options.Headers = "uptrace-dsn=http://token@uptrace-grpc.yodao.cn/project";
}));
// 添加日志
builder.Logging.AddOpenTelemetry(options =>
{
options.AddOtlpExporter(otlpOptions =>
{
otlpOptions.Endpoint = new Uri("uptrace-grpc.yodao.cn:4317");
otlpOptions.Headers = "uptrace-dsn=http://token@uptrace-grpc.yodao.cn/project";
});
});
var app = builder.Build();
app.MapGet("/", () => "Hello World!");
app.Run();

方式 3: 使用 Kubernetes Operator

如果应用运行在 Kubernetes 中,可以使用 OpenTelemetry Operator。

安装 Operator

kubectl apply -f https://github.com/open-telemetry/opentelemetry-operator/releases/latest/download/opentelemetry-operator.yaml

创建 Instrumentation 资源

apiVersion: opentelemetry.io/v1alpha1
kind: Instrumentation
metadata:
name: my-instrumentation
namespace: default
spec:
exporter:
endpoint: uptrace-grpc.yodao.cn:4317
env:
- name: OTEL_EXPORTER_OTLP_HEADERS
value: uptrace-dsn=http://token@uptrace-grpc.yodao.cn/project
propagators:
- tracecontext
- baggage
sampler:
type: parentbased_traceidratio
argument: "0.1"

部署应用

在 Pod 上添加 annotation:

apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
spec:
template:
metadata:
annotations:
instrumentation.opentelemetry.io/inject-dotnet: "true"
spec:
containers:
- name: app
image: my-dotnet-app:latest

验证零代码接入

启动应用后,访问服务端点,然后在 APM 平台查看数据。


SDK 手动接入

安装依赖

# 核心 SDK
dotnet add package OpenTelemetry
dotnet add package OpenTelemetry.Api
# OTLP Exporter
dotnet add package OpenTelemetry.Exporter.OpenTelemetryProtocol
# 埋点库
dotnet add package OpenTelemetry.Instrumentation.AspNetCore
dotnet add package OpenTelemetry.Instrumentation.Http
dotnet add package OpenTelemetry.Instrumentation.GrpcNetClient

基础初始化

Program.cs 配置

using OpenTelemetry;
using OpenTelemetry.Resources;
using OpenTelemetry.Trace;
using OpenTelemetry.Metrics;
using OpenTelemetry.Logs;
using System.Diagnostics;
var builder = WebApplication.CreateBuilder(args);
// 创建 Resource
var resource = ResourceBuilder.CreateDefault()
.AddService(
serviceName: "my-dotnet-service",
serviceVersion: "1.0.0")
.AddAttributes(new Dictionary<string, object>
{
["deployment.environment"] = "production",
["cloud.provider"] = "aws",
["cloud.region"] = "us-east-1",
});
// 配置 Traces
builder.Services.AddOpenTelemetry()
.WithTracing(tracing =>
{
tracing
.SetResourceBuilder(resource)
.AddAspNetCoreInstrumentation()
.AddHttpClientInstrumentation()
.AddSource("my-service")
.AddOtlpExporter(options =>
{
options.Endpoint = new Uri("uptrace-grpc.yodao.cn:4317");
options.Headers = "uptrace-dsn=http://token@uptrace-grpc.yodao.cn/project";
options.Protocol = OpenTelemetry.Exporter.OtlpExportProtocol.Grpc;
});
})
.WithMetrics(metrics =>
{
metrics
.SetResourceBuilder(resource)
.AddAspNetCoreInstrumentation()
.AddHttpClientInstrumentation()
.AddMeter("my-service")
.AddOtlpExporter(options =>
{
options.Endpoint = new Uri("uptrace-grpc.yodao.cn:4317");
options.Headers = "uptrace-dsn=http://token@uptrace-grpc.yodao.cn/project";
options.Protocol = OpenTelemetry.Exporter.OtlpExportProtocol.Grpc;
});
});
// 配置 Logs
builder.Logging.AddOpenTelemetry(options =>
{
options
.SetResourceBuilder(resource)
.AddOtlpExporter(otlpOptions =>
{
otlpOptions.Endpoint = new Uri("uptrace-grpc.yodao.cn:4317");
otlpOptions.Headers = "uptrace-dsn=http://token@uptrace-grpc.yodao.cn/project";
otlpOptions.Protocol = OpenTelemetry.Exporter.OtlpExportProtocol.Grpc;
});
});
var app = builder.Build();
app.MapGet("/", () => "Hello World!");
app.Run();

Traces - 手动埋点

创建和使用 Activity

using System.Diagnostics;
public class OrderService
{
private static readonly ActivitySource ActivitySource = new("my-service");
public async Task ProcessOrderAsync(string orderId)
{
using var activity = ActivitySource.StartActivity("process_order");
activity?.SetTag("order.id", orderId);
activity?.SetTag("operation", "process");
try
{
activity?.AddEvent(new ActivityEvent("order_validation_started"));
// 验证订单
await ValidateOrderAsync(orderId);
activity?.AddEvent(new ActivityEvent("order_validated"));
// 处理支付
await ProcessPaymentAsync(orderId);
activity?.AddEvent(new ActivityEvent("payment_processed"));
activity?.SetStatus(ActivityStatusCode.Ok);
}
catch (Exception ex)
{
activity?.RecordException(ex);
activity?.SetStatus(ActivityStatusCode.Error, ex.Message);
throw;
}
}
private async Task ValidateOrderAsync(string orderId)
{
using var activity = ActivitySource.StartActivity("validate_order");
activity?.SetTag("order.id", orderId);
// 业务逻辑
await Task.Delay(100);
}
private async Task ProcessPaymentAsync(string orderId)
{
using var activity = ActivitySource.StartActivity("process_payment");
activity?.SetTag("order.id", orderId);
activity?.SetTag("payment.method", "credit_card");
// 支付逻辑
await Task.Delay(200);
}
}

Activity 属性和事件

using var activity = ActivitySource.StartActivity("example_operation");
// 设置属性
activity?.SetTag("user.id", "12345");
activity?.SetTag("user.email", "user@example.com");
activity?.SetTag("user.age", 30);
activity?.SetTag("user.premium", true);
// 添加事件
activity?.AddEvent(new ActivityEvent("cache_miss",
tags: new ActivityTagsCollection
{
{ "cache.key", "user:12345" },
{ "cache.type", "redis" },
}));
// 添加带时间戳的事件
activity?.AddEvent(new ActivityEvent("retry_attempt",
timestamp: DateTimeOffset.UtcNow,
tags: new ActivityTagsCollection
{
{ "retry.count", 2 },
{ "retry.reason", "timeout" },
}));

Metrics - 指标采集

Counter(计数器)

using System.Diagnostics.Metrics;
public class MetricsService
{
private static readonly Meter Meter = new("my-service");
private static readonly Counter<long> RequestCounter = Meter.CreateCounter<long>(
"http.server.requests",
description: "Total number of HTTP requests");
public void RecordRequest(string method, string route, int statusCode)
{
RequestCounter.Add(1, new KeyValuePair<string, object?>[]
{
new("http.method", method),
new("http.route", route),
new("http.status_code", statusCode),
});
}
}

Histogram(直方图)

private static readonly Histogram<double> RequestDuration = Meter.CreateHistogram<double>(
"http.server.duration",
unit: "ms",
description: "HTTP request duration");
public void RecordDuration(double duration, string method, string route)
{
RequestDuration.Record(duration, new KeyValuePair<string, object?>[]
{
new("http.method", method),
new("http.route", route),
});
}

Observable Gauge(可观察仪表)

private static readonly ObservableGauge<long> MemoryUsage = Meter.CreateObservableGauge<long>(
"runtime.memory.heap_used",
unit: "bytes",
description: "Heap memory used");
// 注册回调
Meter.CreateObservableGauge("runtime.memory.heap_used", () =>
{
return new Measurement<long>(
GC.GetTotalMemory(false),
new KeyValuePair<string, object?>("memory.type", "heap"));
});

Logs - 日志集成

使用 ILogger

public class UserController : ControllerBase
{
private readonly ILogger<UserController> _logger;
public UserController(ILogger<UserController> logger)
{
_logger = logger;
}
[HttpGet("{id}")]
public async Task<IActionResult> GetUser(string id)
{
// 日志自动关联 Trace ID
_logger.LogInformation("Getting user {UserId}", id);
var user = await _userService.GetUserAsync(id);
if (user == null)
{
_logger.LogWarning("User {UserId} not found", id);
return NotFound();
}
_logger.LogInformation("User {UserId} retrieved successfully", id);
return Ok(user);
}
}

结构化日志

_logger.LogInformation(
"Processing order {OrderId} for user {UserId}",
orderId,
userId);
// 使用范围
using (_logger.BeginScope(new Dictionary<string, object>
{
["OrderId"] = orderId,
["UserId"] = userId,
}))
{
_logger.LogInformation("Processing order");
// 所有日志都会自动包含 OrderId 和 UserId
}

框架集成示例

ASP.NET Core

基础集成

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddOpenTelemetry()
.WithTracing(tracing => tracing
.AddAspNetCoreInstrumentation()
.AddOtlpExporter())
.WithMetrics(metrics => metrics
.AddAspNetCoreInstrumentation()
.AddOtlpExporter());
var app = builder.Build();
app.MapGet("/users/{id}", async (string id) =>
{
// 自动创建 Span
return await GetUserAsync(id);
});
app.Run();

自定义中间件

public class TracingMiddleware
{
private readonly RequestDelegate _next;
private static readonly ActivitySource ActivitySource = new("my-service");
public TracingMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task InvokeAsync(HttpContext context)
{
using var activity = ActivitySource.StartActivity("custom_middleware");
activity?.SetTag("http.method", context.Request.Method);
activity?.SetTag("http.url", context.Request.Path);
await _next(context);
activity?.SetTag("http.status_code", context.Response.StatusCode);
}
}
// 注册中间件
app.UseMiddleware<TracingMiddleware>();

gRPC

using Grpc.Core;
using OpenTelemetry.Trace;
public class UserService : User.UserBase
{
private static readonly ActivitySource ActivitySource = new("my-service");
public override async Task<GetUserResponse> GetUser(
GetUserRequest request,
ServerCallContext context)
{
using var activity = ActivitySource.StartActivity("GetUser");
activity?.SetTag("user.id", request.Id);
try
{
var user = await _repository.GetUserAsync(request.Id);
activity?.SetStatus(ActivityStatusCode.Ok);
return new GetUserResponse { Id = user.Id, Name = user.Name };
}
catch (Exception ex)
{
activity?.RecordException(ex);
activity?.SetStatus(ActivityStatusCode.Error, ex.Message);
throw;
}
}
}
// 配置 gRPC
builder.Services.AddGrpc();
builder.Services.AddOpenTelemetry()
.WithTracing(tracing => tracing
.AddGrpcClientInstrumentation()
.AddOtlpExporter());

Entity Framework Core

builder.Services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(connectionString));
builder.Services.AddOpenTelemetry()
.WithTracing(tracing => tracing
.AddEntityFrameworkCoreInstrumentation()
.AddOtlpExporter());
// 使用示例
public class UserService
{
private readonly ApplicationDbContext _context;
public async Task<User> GetUserAsync(string id)
{
// 自动追踪数据库查询
return await _context.Users
.FirstOrDefaultAsync(u => u.Id == id);
}
}

HTTP 客户端

builder.Services.AddHttpClient();
builder.Services.AddOpenTelemetry()
.WithTracing(tracing => tracing
.AddHttpClientInstrumentation()
.AddOtlpExporter());
// 使用示例
public class PaymentService
{
private readonly HttpClient _httpClient;
public async Task ProcessPaymentAsync(string orderId)
{
// 自动追踪 HTTP 请求
var response = await _httpClient.PostAsync(
"https://payment-gateway.com/charge",
new StringContent(orderId));
}
}

Redis

builder.Services.AddStackExchangeRedisCache(options =>
{
options.Configuration = "localhost:6379";
});
builder.Services.AddOpenTelemetry()
.WithTracing(tracing => tracing
.AddRedisInstrumentation()
.AddOtlpExporter());
// 使用示例
public class CacheService
{
private readonly IDistributedCache _cache;
public async Task<string> GetAsync(string key)
{
// 自动追踪 Redis 操作
return await _cache.GetStringAsync(key);
}
}

高级配置

自定义采样策略

builder.Services.AddOpenTelemetry()
.WithTracing(tracing => tracing
.SetSampler(new ParentBasedSampler(
new TraceIdRatioBasedSampler(0.1)))
.AddOtlpExporter());

多 Exporter 配置

builder.Services.AddOpenTelemetry()
.WithTracing(tracing => tracing
.AddOtlpExporter() // 主要后端
.AddConsoleExporter() // 控制台输出
.AddJaegerExporter()); // Jaeger

性能优化配置

builder.Services.AddOpenTelemetry()
.WithTracing(tracing => tracing
.AddOtlpExporter(options =>
{
options.Endpoint = new Uri("uptrace-grpc.yodao.cn:4317");
options.Headers = "uptrace-dsn=http://token@uptrace-grpc.yodao.cn/project";
options.BatchExportProcessorOptions = new BatchExportProcessorOptions<Activity>
{
MaxQueueSize = 2048,
ScheduledDelayMilliseconds = 5000,
MaxExportBatchSize = 512,
ExporterTimeoutMilliseconds = 30000,
};
}));

最佳实践

1. 使用 ActivitySource

// ✅ 正确:在类级别创建 ActivitySource
public class OrderService
{
private static readonly ActivitySource ActivitySource = new("my-service");
public async Task ProcessOrderAsync(string orderId)
{
using var activity = ActivitySource.StartActivity("process_order");
// ...
}
}

2. 错误处理

// ✅ 正确:记录异常并设置状态
try
{
await ProcessOrderAsync(orderId);
activity?.SetStatus(ActivityStatusCode.Ok);
}
catch (Exception ex)
{
activity?.RecordException(ex);
activity?.SetStatus(ActivityStatusCode.Error, ex.Message);
throw;
}

3. 资源管理

// ✅ 正确:使用 using 确保 Activity 结束
using var activity = ActivitySource.StartActivity("operation");
// 业务逻辑

故障排查

常见问题

1. 数据未发送到后端

检查配置:

builder.Services.AddOpenTelemetry()
.WithTracing(tracing => tracing
.AddConsoleExporter() // 临时添加,验证数据生成
.AddOtlpExporter());

2. Activity 未关联

确保使用相同的 ActivitySource:

private static readonly ActivitySource ActivitySource = new("my-service");

下一步

参考资源

在Gitlab上编辑本文档