Yükleniyor...

ASP.NET Core'da ProblemDetails: TraceId ile Temiz Hata Yanıtları

Exception'ları RFC 7807 ProblemDetails formatına çevirin ve traceId ekleyin. Böylece istemci tutarlı hata alır, loglarda ilgili isteği kolayca bulursunuz.

Bir API’de hata oluştuğunda (exception fırlatıldığında), istemci tarafı yine de tutarlı, güvenli ve kolay tüketilebilir bir hata yanıtına ihtiyaç duyar. Her endpoint’in farklı bir JSON formatı döndürmesi (ya da stack trace sızdırması), istemci kodunu kırılgan hale getirir ve debug sürecini zorlaştırır.

Temiz bir yaklaşım, hataları Problem Details (RFC 7807) formatında döndürmek ve traceId ekleyerek hatalı isteğin sunucu logundaki kaydıyla eşleştirilmesini sağlamaktır. .NET 8’de modern kurgu, yerleşik IExceptionHandler pipeline’ını AddProblemDetails() ile birlikte kullanmaktır.


Amaç


ProblemDetails nedir?

ProblemDetails, HTTP API’ler için standart bir hata yanıtı yapısıdır. Genelde şu alanları içerir:

RFC 7807, ek alanlar (extension members) eklenmesine de izin verir. Bu örnekte traceId ekliyoruz.


Program.cs Kurulumu (AddProblemDetails + IExceptionHandler)

AddProblemDetails() Problem Details altyapısını ekler. AddExceptionHandler<T>() ise exception’ları uygun ProblemDetails yanıtına çeviren handler’ı register eder.


using System.Diagnostics;
using Microsoft.AspNetCore.Diagnostics;
using Microsoft.AspNetCore.Mvc;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers();

// 1) ProblemDetails + global customization (tüm ProblemDetails yanıtlarına traceId ekler)
builder.Services.AddProblemDetails(options =>
{
    options.CustomizeProblemDetails = ctx =>
    {
        var http = ctx.HttpContext;
        var traceId = Activity.Current?.Id ?? http.TraceIdentifier;

        // Tüm ProblemDetails yanıtlarına traceId ekle (uygunsa validation hatalarında da)
        ctx.ProblemDetails.Extensions["traceId"] = traceId;

        // instance için kullanışlı bir default
        ctx.ProblemDetails.Instance ??= http.Request.Path;
    };
});

// 2) Exception handler register et (IExceptionHandler)
builder.Services.AddExceptionHandler<GlobalExceptionHandler>();

var app = builder.Build();

// 3) Yerleşik exception handling middleware
app.UseExceptionHandler();

app.MapControllers();
app.Run();

GlobalExceptionHandler (Exception → Status Code Haritalama)

IExceptionHandler, exception-to-response mapping işini merkezi bir noktada yapmanıza imkan tanır. Bilinen exception’lar anlamlı HTTP status kodlarıyla döner (404/409/400). Bilinmeyen durumlar ise güvenli bir mesajla 500 döner.


using Microsoft.AspNetCore.Diagnostics;
using Microsoft.AspNetCore.Mvc;

public sealed class GlobalExceptionHandler(
    ILogger<GlobalExceptionHandler> logger,
    IProblemDetailsService problemDetailsService) : IExceptionHandler
{
    private readonly ILogger<GlobalExceptionHandler> _logger = logger;
    private readonly IProblemDetailsService _problemDetailsService = problemDetailsService;

    public async ValueTask<bool> TryHandleAsync(
        HttpContext httpContext,
        Exception exception,
        CancellationToken cancellationToken)
    {
        var (status, title, type, detail) = Map(exception);

        // Sunucu tarafında tüm detayları logla (exception response'a taşınmasın)
        _logger.LogError(exception, "Unhandled exception ({Status}).", status);

        var problem = new ProblemDetails
        {
            Status = status,
            Title = title,
            Type = type,
            Detail = detail,
            Instance = httpContext.Request.Path
        };

        httpContext.Response.StatusCode = status;

        // "application/problem+json" yazar ve AddProblemDetails customization'ını uygular (traceId vb.)
        return await _problemDetailsService.TryWriteAsync(new ProblemDetailsContext
        {
            HttpContext = httpContext,
            ProblemDetails = problem,
            Exception = exception
        });
    }

    private static (int status, string title, string type, string detail) Map(Exception ex)
    {
        return ex switch
        {
            NotFoundException nf => (StatusCodes.Status404NotFound, "Not found", "https://httpstatuses.com/404", nf.Message),
            ConflictException cf => (StatusCodes.Status409Conflict, "Conflict", "https://httpstatuses.com/409", cf.Message),
            ValidationException ve => (StatusCodes.Status400BadRequest, "Validation error", "https://httpstatuses.com/400", ve.Message),

            // Bilinmeyen / beklenmeyen
            _ => (StatusCodes.Status500InternalServerError, "Unexpected error", "https://httpstatuses.com/500",
                  "Beklenmeyen bir hata oluştu.")
        };
    }
}

Örnek Exception Sınıfları

Küçük ve net exception türleri, mapping kısmını basit ve okunabilir tutar:

public sealed class NotFoundException(string message) : Exception(message);
public sealed class ConflictException(string message) : Exception(message);
public sealed class ValidationException(string message) : Exception(message);

Örnek Çıktı

Response yapısı tahmin edilebilir kalır ve traceId içerir:


{
  "type": "https://httpstatuses.com/404",
  "title": "Not found",
  "status": 404,
  "detail": "Customer was not found.",
  "instance": "/api/customers/42",
  "traceId": "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01"
}
ASP.NET Core Clean Error Responses with TraceId

İstemci Tarafı: traceId ile Destek / Debug

İstemci kodu kullanıcıya daha anlaşılır bir mesaj gösterebilir ve traceId değerini hata takibi için saklayabilir:


const res = await fetch("/api/customers/42");

if (!res.ok) {
  const contentType = res.headers.get("Content-Type") || "";
  let payload;

  if (contentType.includes("application/problem+json") || contentType.includes("application/json")) {
    payload = await res.json();
  } else {
    // Fallback: HTML/plain-text gibi durumlar
    payload = { title: "Unexpected error", detail: await res.text() };
  }

  console.error("API error:", payload);

  // ProblemDetails ise traceId bulunabilir (yoksa boş geçilir)
  const traceId = payload?.traceId || payload?.extensions?.traceId;

  alert(`Bir hata oluştu.${traceId ? ` Referans: ${traceId}` : ""}`);
}

Sık Yapılan İyileştirmeler


TL;DR

  • Tutarlı hata yanıtları için ProblemDetails (RFC 7807) kullanın.
  • .NET 8’de modern yaklaşım: IExceptionHandler + AddProblemDetails().
  • CustomizeProblemDetails ile traceId ekleyerek hataları loglarla eşleştirin.
  • Production ortamında stack trace döndürmeyin.