.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-serviceexport OTEL_RESOURCE_ATTRIBUTES=service.version=1.0.0,deployment.environment=production# OTLP Exporterexport OTEL_EXPORTER_OTLP_ENDPOINT=uptrace-grpc.yodao.cn:4317export OTEL_EXPORTER_OTLP_PROTOCOL=grpcexport OTEL_EXPORTER_OTLP_HEADERS=uptrace-dsn=http://token@uptrace-grpc.yodao.cn/project# Traces 配置export OTEL_TRACES_EXPORTER=otlpexport OTEL_TRACES_SAMPLER=parentbased_traceidratioexport OTEL_TRACES_SAMPLER_ARG=0.1# Metrics 配置export OTEL_METRICS_EXPORTER=otlpexport OTEL_METRIC_EXPORT_INTERVAL=60000# Logs 配置export OTEL_LOGS_EXPORTER=otlp
启动应用
# 使用 dotnet-instrument 启动dotnet-instrument --output myapp.dll# 或使用环境变量export OTEL_DOTNET_AUTO_ENABLED=truedotnet run
方式 2: 使用 NuGet 包自动配置
安装 NuGet 包
# 安装 OpenTelemetry 自动配置包dotnet add package OpenTelemetry.Instrumentation.AspNetCoredotnet add package OpenTelemetry.Exporter.OpenTelemetryProtocoldotnet add package OpenTelemetry.Extensions.Hosting
配置 Program.cs
using OpenTelemetry.Resources;using OpenTelemetry.Trace;using OpenTelemetry.Metrics;using OpenTelemetry.Logs;var builder = WebApplication.CreateBuilder(args);// 添加 OpenTelemetrybuilder.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/v1alpha1kind: Instrumentationmetadata:name: my-instrumentationnamespace: defaultspec:exporter:endpoint: uptrace-grpc.yodao.cn:4317env:- name: OTEL_EXPORTER_OTLP_HEADERSvalue: uptrace-dsn=http://token@uptrace-grpc.yodao.cn/projectpropagators:- tracecontext- baggagesampler:type: parentbased_traceidratioargument: "0.1"
部署应用
在 Pod 上添加 annotation:
apiVersion: apps/v1kind: Deploymentmetadata:name: my-appspec:template:metadata:annotations:instrumentation.opentelemetry.io/inject-dotnet: "true"spec:containers:- name: appimage: my-dotnet-app:latest
验证零代码接入
启动应用后,访问服务端点,然后在 APM 平台查看数据。
SDK 手动接入
安装依赖
# 核心 SDKdotnet add package OpenTelemetrydotnet add package OpenTelemetry.Api# OTLP Exporterdotnet add package OpenTelemetry.Exporter.OpenTelemetryProtocol# 埋点库dotnet add package OpenTelemetry.Instrumentation.AspNetCoredotnet add package OpenTelemetry.Instrumentation.Httpdotnet 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);// 创建 Resourcevar 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",});// 配置 Tracesbuilder.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;});});// 配置 Logsbuilder.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) =>{// 自动创建 Spanreturn 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;}}}// 配置 gRPCbuilder.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
// ✅ 正确:在类级别创建 ActivitySourcepublic 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");