آموزش OpenTelemetry در C# برای مبتدیها
⚡ خلاصه سریع — اصل کاریها
اینها مهمترین چیزهایی هستن که اگه هیچی ندونی، فقط اینا رو بدونی کافیه:
- OpenTelemetry یک استاندارد باز برای جمعآوری Trace، Metric و Log از برنامههاست.
- Trace یعنی دنبال کردن مسیر یک درخواست از ورود تا خروج.
- Span هر مرحله از کار توی مسیر درخواسته؛ مثل یک تابع یا یک call.
- Activity در داتنت همان Span در OpenTelemetry است.
- Exporter مشخص میکند اطلاعات کجا ارسال بشه؛ مثل Console یا Jaeger.
- Instrumentation کتابخانهای است که بهصورت خودکار Activity میسازد.
- ActivitySource سفارشی را حتماً با
AddSource("...")ثبت کن.
۳ اشتباه رایج که باید ازشون پرهیز کنی:
- فقط پکیج اصلی OpenTelemetry را نصب نکنی؛ حتماً Instrumentation و Exporter هم لازم داری.
- ActivitySource سفارشی را فراموش نکنی یا با
AddSourceثبت نکنی. - Activity را با
usingمدیریت نکنی؛ در نتیجه Span بسته نمیشه.
اگر فقط ۵ دقیقه وقت داری، اینها رو یاد بگیر:
- یک
ActivitySourceبساز و باStartActivityیک Span شروع کن. - از
AddOpenTelemetry()درProgram.csبرای راهاندازی استفاده کن. - یک Exporter مثل
AddConsoleExporter()اضافه کن تا Trace ها را ببینی.
۱. مقدمه
OpenTelemetry یا بهصورت خلاصه OTel یک استاندارد باز است که به برنامهها کمک میکند اطلاعات مشاهدهپذیری (Observability) تولید کنند. یعنی بتوانی بفهمی داخل برنامه چه اتفاقی میافتد، مخصوصاً وقتی یک درخواست از چند سرویس عبور میکند.
سه نوع داده اصلی در OpenTelemetry داریم:
- Trace: دنبال کردن مسیر کامل یک درخواست.
- Metric: عددهایی مثل تعداد درخواست، زمان پاسخ، نرخ خطا.
- Log: همان لاگهای معمولی.
چرا باید یادش بگیری؟
وقتی برنامه ساده است، شاید با Console.WriteLine بشه فهمید چه خبره. اما وقتی چند سرویس داری، یک درخواست ممکنه از ۵ سرویس عبور کنه. اونوقت دیگه نمیدونی مشکل کجاست. OpenTelemetry این دید را بهت میده.
کجا استفاده میشه؟
- API های ASP.NET Core
- میکروسرویسها
- سیستمهای ابری و کانتینری
- برنامههایی که با HTTP یا gRPC با هم صحبت میکنند
۲. پیشنیازها
قبل از شروع، این چیزها کافیه:
- آشنایی ابتدایی با زبان C#
- آشنایی با ASP.NET Core Minimal API
- نصب بودن NET 8 SDK.
- یک IDE مثل Visual Studio 2022 یا VS Code
- اگر میخواهی Trace ها را فقط در Console ببینی، Docker لازم نداری. برای مثال واقعی با Jaeger، Docker اختیاری است.
برای بررسی نسخه dotnet:
dotnet --version
۳. نصب و راهاندازی
گام ۱: ساخت پروژه
dotnet new webapi -n TraceDemo -minimal
cd TraceDemo
گام ۲: نصب پکیجهای لازم
dotnet add package OpenTelemetry.Extensions.Hosting
dotnet add package OpenTelemetry.Instrumentation.AspNetCore
dotnet add package OpenTelemetry.Instrumentation.Http
dotnet add package OpenTelemetry.Exporter.Console
هر پکیج چه کار میکند:
| پکیج | کار |
|---|---|
OpenTelemetry.Extensions.Hosting |
راهاندازی OpenTelemetry در ASP.NET Core |
OpenTelemetry.Instrumentation.AspNetCore |
ساخت خودکار Span برای درخواستهای ورودی ASP.NET Core |
OpenTelemetry.Instrumentation.Http |
ساخت خودکار Span برای خروجیهای HttpClient |
OpenTelemetry.Exporter.Console |
نمایش Trace ها در Console |
گام ۳: اولین راهاندازی
فایل Program.cs را اینطور تغییر بده:
using OpenTelemetry;
using OpenTelemetry.Trace;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddOpenTelemetry()
.WithTracing(tracing =>
{
tracing.AddAspNetCoreInstrumentation();
tracing.AddHttpClientInstrumentation();
tracing.AddConsoleExporter();
});
var app = builder.Build();
app.MapGet("/", () => "Hello OpenTelemetry!");
app.Run();
توضیح کد
AddOpenTelemetry()سرویس OpenTelemetry را به برنامه اضافه میکند.WithTracingیعنی فقط بخش Trace را فعال کن.AddAspNetCoreInstrumentation()برای ساخت خودکار Span مربوط به درخواستهای ASP.NET Core.AddHttpClientInstrumentation()برای ساخت خودکار Span مربوط به HttpClient.AddConsoleExporter()یعنی نتیجه Trace ها را در Console نمایش بده.
گام ۴: اجرا
dotnet run
سپس مرورگر را باز کن یا از دستور زیر استفاده کن:
curl http://localhost:5000/
در Console اطلاعاتی شبیه Trace ID و Span ID میبینی.
۴. مفاهیم پایه
Trace
یک Trace یعنی دنبال کردن کامل یک درخواست. مثلاً وقتی کاربر درخواست /orders میزند، ممکنه این مسیر طی بشه:
- ورود به API
- صدا زدن سرویس پرداخت
- ذخیره در دیتابیس
همه این مراحل در قالب یک Trace نمایش داده میشه.
Span
هر مرحله از Trace یک Span است. مثلاً:
POST /ordersPaymentService.ProcessPaymentDatabase.SaveOrder
هر Span میتونه attributes داشته باشه. مثلاً order.id = 123.
Activity
در داتنت، چیزی که OpenTelemetry به آن Span میگوید، با کلاس Activity نشان داده میشود. این کلاس در System.Diagnostics قرار دارد.
ActivitySource
برای ساخت Span سفارشی، باید یک ActivitySource بسازی:
using System.Diagnostics;
public class OrderService
{
private static readonly ActivitySource ActivitySource = new("OrderService");
public void CreateOrder()
{
using var activity = ActivitySource.StartActivity("CreateOrder");
activity?.SetTag("order.id", 123);
Console.WriteLine("سفارش ساخته شد");
}
}
نکته خیلی مهم
اگه ActivitySource سفارشی داری، باید اسمش را در تنظیمات OpenTelemetry ثبت کنی:
tracing.AddSource("OrderService");
اگر این کار را نکنی، Activity های سفارشی تو Export نمیشوند.
۵. مثالهای کد ساده
مثال ۱: فقط Trace خودکار با ASP.NET Core
using OpenTelemetry;
using OpenTelemetry.Trace;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddOpenTelemetry()
.WithTracing(tracing =>
{
tracing.AddAspNetCoreInstrumentation();
tracing.AddConsoleExporter();
});
var app = builder.Build();
app.MapGet("/hello", () => "سلام!");
app.Run();
توضیح:
هر درخواست به /hello یک Span خودکار با نام GET /hello میسازد و در Console نمایش میدهد.
مثال ۲: ساخت Span سفارشی
using System.Diagnostics;
using OpenTelemetry;
using OpenTelemetry.Trace;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddOpenTelemetry()
.WithTracing(tracing =>
{
tracing.AddAspNetCoreInstrumentation();
tracing.AddSource("MyApi");
tracing.AddConsoleExporter();
});
var app = builder.Build();
var activitySource = new ActivitySource("MyApi");
app.MapGet("/order", () =>
{
using var activity = activitySource.StartActivity("CreateOrder");
activity?.SetTag("order.id", 123);
activity?.SetTag("customer.name", "Ali");
return Results.Ok(new { id = 123, status = "created" });
});
app.Run();
توضیح:
new ActivitySource("MyApi")یک منبع ساخت Activity میسازد.StartActivity("CreateOrder")یک Activity جدید شروع میکند.SetTagاطلاعات اضافی به Activity اضافه میکند.using varباعث میشود Activity بهصورت خودکار بسته شود.AddSource("MyApi")باعث میشود Activity های این منبع جمعآوری شوند.
مثال ۳: افزودن Event و Status
using System.Diagnostics;
using OpenTelemetry;
using OpenTelemetry.Trace;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddOpenTelemetry()
.WithTracing(tracing =>
{
tracing.AddAspNetCoreInstrumentation();
tracing.AddSource("MyApi");
tracing.AddConsoleExporter();
});
var app = builder.Build();
var activitySource = new ActivitySource("MyApi");
app.MapPost("/payment", () =>
{
using var activity = activitySource.StartActivity("PaymentProcess");
try
{
activity?.AddEvent(new ActivityEvent("PaymentStarted"));
// شبیهسازی عملیات پرداخت
Thread.Sleep(100);
activity?.AddEvent(new ActivityEvent("PaymentCompleted"));
return Results.Ok(new { status = "paid" });
}
catch (Exception ex)
{
activity?.SetStatus(ActivityStatusCode.Error, ex.Message);
throw;
}
});
app.Run();
توضیح:
AddEventیک رویداد زمانی داخل Span اضافه میکند.SetStatus(ActivityStatusCode.Error, ...)وقتی خطا رخ بدهد، وضعیت Span را خطا میکند.- این اطلاعات کمک میکند بفهمی پرداخت در چه مرحلهای شکست خورده است.
مثال ۴: Span تودرتو
app.MapPost("/orders", () =>
{
using var orderActivity = activitySource.StartActivity("CreateOrder");
using (var paymentActivity = activitySource.StartActivity("ProcessPayment"))
{
paymentActivity?.SetTag("payment.amount", 25000);
}
using (var dbActivity = activitySource.StartActivity("SaveOrder"))
{
dbActivity?.SetTag("db.table", "orders");
}
return Results.Ok();
});
توضیح:
ProcessPayment و SaveOrder بهصورت خودکار فرزند CreateOrder میشوند. چون هنگام ساخت، داخل همان Activity قبلی هستیم.
۶. مثال واقعی و کاربردی
حالا یک پروژه کوچک واقعی میسازیم و Trace ها را با Jaeger میبینیم.
گام ۱: اجرای Jaeger با Docker
docker run -d --name jaeger \
-p 16686:16686 \
-p 4317:4317 \
-p 4318:4318 \
jaegertracing/all-in-one:latest
پس از اجرا، Jaeger UI روی آدرس زیر در دسترس است:
http://localhost:16686
گام ۲: نصب پکیج OTLP Exporter
dotnet add package OpenTelemetry.Exporter.OpenTelemetryProtocol
گام ۳: کد کامل پروژه
using System.Diagnostics;
using OpenTelemetry;
using OpenTelemetry.Resources;
using OpenTelemetry.Trace;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddOpenTelemetry()
.ConfigureResource(resource => resource.AddService("OrderApi"))
.WithTracing(tracing =>
{
tracing.AddAspNetCoreInstrumentation();
tracing.AddHttpClientInstrumentation();
tracing.AddSource("OrderApi");
tracing.AddOtlpExporter(options =>
{
options.Endpoint = new Uri("http://localhost:4317");
});
});
var app = builder.Build();
var activitySource = new ActivitySource("OrderApi");
app.MapPost("/orders", async (Order order) =>
{
using var activity = activitySource.StartActivity("CreateOrder");
activity?.SetTag("order.id", order.Id);
activity?.SetTag("product.name", order.Product);
var client = new HttpClient();
var result = await client.GetStringAsync("https://jsonplaceholder.typicode.com/todos/1");
return Results.Ok(new
{
orderId = order.Id,
product = order.Product,
externalData = result
});
});
app.Run();
record Order(int Id, string Product);
توضیح کد
ConfigureResourceنام سرویس را مشخص میکند تا در Jaeger قابل تشخیص باشد.AddOtlpExporterاطلاعات را به Jaeger ارسال میکند.- وقتی
/ordersصدا زده میشود، هم Span سفارشیCreateOrderثبت میشود و هم Span خودکار مربوط به خروجی HTTP بهjsonplaceholder. - هر دو در یک Trace نمایش داده میشوند.
گام ۴: اجرا و تست
dotnet run
در Linux/Mac:
curl -X POST http://localhost:5000/orders \
-H "Content-Type: application/json" \
-d '{"id": 10, "product": "book"}'
در PowerShell:
Invoke-RestMethod -Method Post -Uri http://localhost:5000/orders `
-ContentType "application/json" `
-Body '{"id": 10, "product": "book"}'
حالا در Jaeger UI میتوانی سرویس OrderApi را انتخاب کنی و Trace کامل این درخواست را ببینی.
۷. مباحث پیشرفته
Sampling
همیشه لازم نیست همه Trace ها ذخیره شوند. میتوانی فقط درصدی از آنها را نگه داری:
using OpenTelemetry.Trace;
builder.Services.AddOpenTelemetry()
.WithTracing(tracing =>
{
tracing.SetSampler(new TraceIdRatioBasedSampler(0.1));
tracing.AddAspNetCoreInstrumentation();
tracing.AddConsoleExporter();
});
این کد فقط ۱۰٪ Trace ها را نگه میدارد.
Metrics
برای جمعآوری Metric ها:
using OpenTelemetry;
using OpenTelemetry.Metrics;
builder.Services.AddOpenTelemetry()
.WithMetrics(metrics =>
{
metrics.AddAspNetCoreInstrumentation();
metrics.AddConsoleExporter();
});
Logging
میتوانی لاگها را هم به OpenTelemetry بدهی:
using OpenTelemetry.Logs;
builder.Logging.AddOpenTelemetry(logging =>
{
logging.AddConsoleExporter();
});
Baggage
Baggage اطلاعات اضافی را همراه Trace بین سرویسها منتقل میکند:
using System.Diagnostics;
Baggage.Current.SetBaggage("user.id", "42");
۸. اشتباهات رایج
ثبت نکردن
AddSource("...")برای ActivitySource سفارشی
در نتیجه Activity های سفارشی تو Export نمیشوند.نصب نکردن پکیج Instrumentation
فقط با نصبOpenTelemetry.Extensions.Hostingهیچ Span خودکاری ساخته نمیشود.عدم مدیریت عمر Activity
اگرusing var activityرا فراموش کنی، ممکن است Activity بسته نشود و Trace ناقص بماند.مشخص نکردن
service.name
در Jaeger سرویس به صورتunknown_serviceنمایش داده میشود.استفاده از ActivitySource جدید در هر درخواست
ActivitySource باید یک نمونه ثابت باشد، نه اینکه برای هر درخواست ساخته شود.اشتباه در پورت OTLP Exporter
پورت پیشفرض gRPC معمولاً4317است، نه16686.
۹. بهترین روشها (Best Practices)
- همیشه
service.nameرا باConfigureResourceمشخص کن. - از یک
ActivitySourceثابت و استاتیک در هر کلاس استفاده کن. - نام Activity ها را کوتاه و معنادار بگذار؛ مثل
CreateOrder. - اطلاعات مهم را با
SetTagاضافه کن، اما از دادههای حساس یا با تنوع خیلی زیاد پرهیز کن. - برای خطاها از
SetStatus(ActivityStatusCode.Error)استفاده کن. - در محیط Production از Sampling استفاده کن تا حجم دادهها کنترل شود.
- رویدادهای مهم را با
AddEventثبت کن، نه همه چیز را. - همزمان با Trace، Metric ها را هم جمعآوری کن.
- از Semantic Conventions استفاده کن؛ مثلاً
http.request.methodبه جای نامهای دلخواه.
۱۰. منابع و ادامه مسیر
مستندات رسمی OpenTelemetry برای .NET:
https://opentelemetry.io/docs/languages/net/مخزن GitHub نمونهکدها:
https://github.com/open-telemetry/opentelemetry-dotnetJaeger:
https://www.jaegertracing.io/Prometheus برای Metric:
https://prometheus.io/آموزش Distributed Tracing در داتنت:
https://learn.microsoft.com/en-us/dotnet/core/diagnostics/distributed-tracing
قدم بعدی
- Metrics را با Prometheus و Grafana اجرا کن.
- یاد بگیر چطور Baggage و Context Propagation در میکروسرویسها کار میکند.
- یک Exporter سفارشی برای نیاز خودت بنویس.