اتصال ⁦ASP.NET Core⁩ با OpenTelemetry

در این آموزش
  1. گام ۱: نصب پکیج‌ها
  2. گام ۲: پیکربندی در Program.cs
  3. کلید را در کد ننویسید
  4. نوشتن لاگ و خطا
  5. کنترل حجم با سطح لاگ
  6. فقط لاگ، نه trace و metric
  7. عیب‌یابی
  8. منابع و مطالعهٔ بیشتر

این آموزش برای برنامه‌های ⁦ASP.NET Core⁩ و هر برنامهٔ ⁦.NET 8⁩ به بعد است که از ILogger استفاده می‌کند. لاگ‌ها را با OpenTelemetry، استاندارد باز و مستقل از فروشنده، مستقیم به LogMug می‌فرستیم. هیچ پکیج اختصاصی LogMug لازم نیست و همین تنظیمات اگر روزی مقصد را عوض کنید هم به کار می‌آید.

اگر برنامه همین حالا Serilog دارد، راه ساده‌تر اتصال از طریق Serilog است.

گام ۱: نصب پکیج‌ها

در پوشهٔ پروژه دو پکیج رسمی OpenTelemetry را نصب کنید:

dotnet add package OpenTelemetry.Extensions.Hosting
dotnet add package OpenTelemetry.Exporter.OpenTelemetryProtocol

پکیج اول OpenTelemetry را به سیستم لاگ‌گیری ⁦.NET⁩ وصل می‌کند و دومی exporter پروتکل OTLP است؛ exporter بخشی است که داده را به مقصد می‌فرستد.

گام ۲: پیکربندی در Program.cs

این کد را پس از WebApplication.CreateBuilder و پیش از builder.Build() بگذارید. نام سرویس و کلید را عوض کنید:

using OpenTelemetry.Exporter;
using OpenTelemetry.Logs;
using OpenTelemetry.Resources;

var builder = WebApplication.CreateBuilder(args);

builder.Logging.AddOpenTelemetry(o =>
{
    o.IncludeFormattedMessage = true;
    o.IncludeScopes = true;
    o.SetResourceBuilder(ResourceBuilder.CreateDefault()
        .AddService("orders-api")
        .AddAttributes(new Dictionary<string, object>
        {
            ["deployment.environment.name"] = builder.Environment.EnvironmentName
        }));
    o.AddOtlpExporter(e =>
    {
        e.Endpoint = new Uri("https://ingest.logmug.ir/v1/logs");
        e.Protocol = OtlpExportProtocol.HttpProtobuf;
        e.Headers = "x-logmug-key=lm_ingest_...";
    });
});

var app = builder.Build();

سه نکته که بیشترین اشتباه را دارند:

  1. پروتکل باید HttpProtobuf باشد. پیش‌فرض exporter پروتکل gRPC است و LogMug فقط OTLP روی HTTP را می‌پذیرد.
  2. وقتی Endpoint را در کد می‌دهید، مسیر /v1/logs باید در نشانی باشد. exporter در این حالت چیزی به نشانی اضافه نمی‌کند.
  3. هدر به شکل نام=مقدار است: x-logmug-key=lm_ingest_...، بدون فاصله و بدون دو نقطه.

IncludeFormattedMessage باعث می‌شود متن رندرشدهٔ پیام (با مقادیر جاگذاری‌شده) فرستاده شود. پارامترهای قالب پیام، مثل {OrderId}، جداگانه به‌عنوان ویژگی قابل‌فیلتر ذخیره می‌شوند. deployment.environment.name در داشبورد ستون «محیط» را پر می‌کند و service.name ستون «سرویس» را.

کلید را در کد ننویسید

کلید ingest یک راز است. در پروداکشن آن را از پیکربندی یا متغیر محیطی بخوانید:

e.Headers = "x-logmug-key=" + builder.Configuration["LogMug:Key"];

و مقدار را با متغیر محیطی LogMug__Key یا User Secrets در محیط توسعه تنظیم کنید. راه دیگر این است که کل تنظیمات exporter را به متغیرهای محیطی استاندارد OpenTelemetry بسپارید و در کد فقط o.AddOtlpExporter(); بنویسید:

OTEL_SERVICE_NAME=orders-api
OTEL_EXPORTER_OTLP_LOGS_PROTOCOL=http/protobuf
OTEL_EXPORTER_OTLP_LOGS_ENDPOINT=https://ingest.logmug.ir/v1/logs
OTEL_EXPORTER_OTLP_LOGS_HEADERS=x-logmug-key=lm_ingest_...

در این حالت AddService را از کد بردارید تا OTEL_SERVICE_NAME اعمال شود.

نوشتن لاگ و خطا

از همان ILogger همیشگی استفاده کنید. استثنا را همیشه به‌عنوان آرگومان اول بدهید تا نوع، پیام و stack trace آن جدا ذخیره شوند:

app.MapPost("/orders/{id:int}/pay", (int id, ILogger<Program> logger) =>
{
    try
    {
        // ...
        logger.LogInformation("Order {OrderId} paid", id);
        return Results.Ok();
    }
    catch (Exception ex)
    {
        logger.LogError(ex, "Payment failed for order {OrderId}", id);
        return Results.Problem();
    }
});

⁦ASP.NET Core⁩ برای هر درخواست HTTP یک Activity می‌سازد، پس هر لاگی که در طول درخواست نوشته شود خودکار trace_id و span_id می‌گیرد. در داشبورد با دکمهٔ «همهٔ لاگ‌های این درخواست» همهٔ آن خطوط را کنار هم می‌بینید؛ توضیح بیشتر در دنبال کردن یک درخواست و گروه‌بندی خطاها.

کنترل حجم با سطح لاگ

فیلتر سطح‌ها همان فیلتر معمول appsettings.json است. با نام فراهم‌کنندهٔ OpenTelemetry می‌توانید فقط برای LogMug سطح جدا بگذارید و مثلاً خروجی کنسول را پرحرف‌تر نگه دارید:

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    },
    "OpenTelemetry": {
      "LogLevel": {
        "Default": "Information",
        "Microsoft": "Warning",
        "System.Net.Http": "Warning"
      }
    }
  }
}

سطح‌های ⁦.NET⁩ خودکار نگاشت می‌شوند: Trace و Debug همان‌اند، Information به info، Warning به warn، Error به error و Critical به fatal. برای انتخاب سطح درست، مقالهٔ سطح‌های لاگ را ببینید.

فقط لاگ، نه trace و metric

LogMug فقط سیگنال لاگ را می‌پذیرد. اگر در برنامه WithTracing یا WithMetrics هم دارید، exporter آن‌ها را به LogMug اشاره ندهید؛ یا به ابزار دیگری بفرستید یا خاموش بگذارید. برای ساختن trace_id روی لاگ‌ها نیازی به فرستادن trace نیست.

عیب‌یابی

exporter خطاهایش را در لاگ داخلی OpenTelemetry می‌نویسد، نه در لاگ برنامه. اگر لاگی نمی‌رسد، این‌ها را به ترتیب بررسی کنید:

  • هیچ درخواستی نمی‌رسد: معمولاً Protocol روی پیش‌فرض gRPC مانده یا /v1/logs از انتهای Endpoint افتاده است.
  • 401: کلید نادرست یا باطل‌شده است، یا قالب هدر اشتباه است (مثلاً x-logmug-key: ... به‌جای x-logmug-key=...).
  • 400: بدنه قابل خواندن نبود؛ معمولاً یعنی exporter تنظیم پروتکل دیگری دارد یا یک پراکسی میانی بدنه را تغییر داده است.
  • 403: سهمیهٔ ماهانهٔ پلن رایگان تمام شده و تا دورهٔ بعد لاگ پذیرفته نمی‌شود؛ سهمیه‌ها را ببینید.
  • 429 و 503: نرخ مجاز رد شده یا سرویس موقتاً شلوغ است. پاسخ هدر Retry-After دارد و exporter دوباره تلاش می‌کند؛ کاری لازم نیست مگر اینکه مداوم تکرار شود.
  • لاگ می‌رسد ولی سرویس unknown_service است: AddService یا OTEL_SERVICE_NAME اعمال نشده است.
  • لاگ‌های آخر پیش از خاموش شدن برنامه گم می‌شوند: exporter دسته‌ای می‌فرستد؛ برنامه را با خاموشی عادی (نه kill) ببندید تا صف خالی شود.

برای آشنایی عمیق‌تر با گزینه‌های لاگ در ⁦ASP.NET Core⁩، مقالهٔ راهنمای جامع لاگ در ⁦ASP.NET Core⁩ را بخوانید.

منابع و مطالعهٔ بیشتر

کلید پروژه‌تان را هنوز ندارید؟

در پلن رایگان یک پروژه بسازید؛ راهنمای اتصال با نشانی و کلید واقعی خودتان همان‌جا آماده است.

شروع رایگان