asp.net core / hata yönetimi / problem details·5 dk okuma

global hata yönetimi: hata oluştuğunda api’niz nasıl davranmalı?

beklenen iş sonuçlarını beklenmeyen arızalardan ayırın; merkezi bir işleyici, güvenli problem details yanıtları ve trace id ile hatayı takip edin.

bir sipariş ekranında “bir şeyler ters gitti” mesajını gördüğünüzü düşünün. kullanıcı siparişin kaydedilip kaydedilmediğini bilmiyor. geliştirici ise hangi isteğin başarısız olduğunu arıyor. global hata yönetiminin görevi yalnızca exception yakalamak değildir: istemciye tutarlı bir cevap vermek ve inceleme için yeterli iz bırakmaktır. bu yazıda bu iki ihtiyacı birbirine karıştırmadan ele alacağız.

güncellendi:

beklenen sonuç doğrudan http yanıtına gider. beklenmeyen hata merkezi işleyicide güvenli 500 yanıtına ve aynı trace id ile sunucu kaydına dönüşür.
bu yazıda01/06

01önce karar: bu bir iş sonucu mu, arıza mı?

olmayan bir siparişin aranması, stokun yetersiz olması ve veritabanı bağlantısının kopması aynı durum değildir. ilk ikisi uygulamanın karşılaşmayı beklediği sonuçlardır. üçüncüsü, isteğin normal biçimde tamamlanmasını engelleyen bir arızadır. hepsini exception fırlatıp 500’e çevirdiğinizde istemcinin neyi düzeltebileceği belirsizleşir.

örneğin bulunamayan bir kaynak için 404, mevcut durumla çakışan bir işlem için 409 seçebilirsiniz. giriş doğrulamasında 400 kullanmak da bir sözleşme kararıdır. kimlik doğrulama ve yetkilendirme hataları ise kendi mekanizmalarıyla 401 veya 403 üretir. bilinmeyen exception’ı sırf mesajında “not found” geçtiği için 404’e çevirmeyin; bu, yazılım hatasını gizleyebilir.

beklenen sonucu endpoint’ten açıkça döndürmek çoğu küçük uygulamada yeterlidir. katmanlar arasında ortak bir sonuç modeli gerekiyorsa onu ayrıca tanımlayın. merkezi işleyicinin temel görevi, aşağıdaki örnekte olduğu gibi, öngörülmeyen hatayı güvenli bir 500 yanıtına dönüştürmektir.

02try/catch hangi noktada anlamlı?

her endpoint’i aynı try/catch ile sarmalamak bir süre sonra farklı hata biçimleri ve tekrar eden kayıtlar üretir. bunun yerine istek hattının başına bir exception handler yerleştirin. kendisinden sonra çalışan koddan yukarı taşınan exception burada ele alınır.

yerel catch yine yararlıdır: gerçekten toparlanabiliyorsanız, kaynak temizliği gerekiyorsa veya hatayı anlamlı bir üst seviye hataya dönüştürüyorsanız. yalnızca loglayıp yeniden fırlatmak aynı olayın birkaç kez kaydedilmesine yol açabilir. yeniden fırlatırken “throw;” kullanmak mevcut stack trace’i korur.

03küçük ama bütün bir hata hattı

.net 10 sdk ile “dotnet new web -n ErrorDemo” komutunu çalıştırın. program.cs dosyasını aşağıdaki içerikle değiştirip proje klasöründe “dotnet run --no-launch-profile --urls http://localhost:5080” komutunu kullanın. ek nuget paketi gerekmez. /demo/failure yalnızca arızayı gözlemlemek için vardır; gerçek uygulamaya taşımayın.

42 numaralı sipariş 200, diğer numaralar 404 döner. bu 404 bir exception olmadığı için global işleyiciye girmez ve örnekte gövdesizdir. /demo/failure ise işleyiciye ulaşır. hata cevabının content type’ı açıkça application/problem+json seçilir; title sabit, code uygulamanın belgelenebilir hata anahtarıdır.

AddProblemDetails servisleri kaydeder; bu örnekte beklenmeyen hata gövdesini açıkça kendimiz yazıyoruz. böylece örneğin yanıt sözleşmesi görülebilir. normal 404 gibi gövdesiz yanıtları da ortak biçime çevirmek istiyorsanız ayrıca status code pages politikasını tanımlayın.

program.cs · c# / .net 10
using System.Diagnostics;
using Microsoft.AspNetCore.Diagnostics;
using Microsoft.AspNetCore.Mvc;

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddProblemDetails();
builder.Services.AddExceptionHandler<GlobalExceptionHandler>();

var app = builder.Build();
app.UseExceptionHandler();

app.MapGet("/orders/{id:int}", (int id) =>
    id == 42
        ? Results.Ok(new { id, status = "ready" })
        : Results.NotFound());

app.MapGet("/demo/failure", Failure);
app.Run();

static IResult Failure() =>
    throw new InvalidOperationException("Demo failure");

public sealed class GlobalExceptionHandler(
    ILogger<GlobalExceptionHandler> logger) : IExceptionHandler
{
    public async ValueTask<bool> TryHandleAsync(
        HttpContext context,
        Exception exception,
        CancellationToken cancellationToken)
    {
        var traceId = Activity.Current?.TraceId.ToString()
            ?? context.TraceIdentifier;

        // Record classification, not the exception's sensitive payload.
        logger.LogError(
            "Request failed. ErrorCode={ErrorCode} ExceptionType={ExceptionType} TraceId={TraceId}",
            "unexpected_error", exception.GetType().Name, traceId);

        context.Response.StatusCode = StatusCodes.Status500InternalServerError;
        await context.Response.WriteAsJsonAsync(new ProblemDetails
        {
            Status = StatusCodes.Status500InternalServerError,
            Title = "The request could not be completed.",
            Type = "about:blank",
            Extensions =
            {
                ["code"] = "unexpected_error",
                ["traceId"] = traceId
            }
        }, options: null, contentType: "application/problem+json",
           cancellationToken: cancellationToken);

        return true;
    }
}

04kullanıcıya az, inceleme için yeterli bilgi

istemciye exception.Message veya stack trace göndermeyin. bunların içinde tablo adları, dosya yolları ya da dış sistemin hassas cevabı olabilir. örnekte istemci yalnızca sabit bir açıklama ve traceId alır. destek talebindeki bu kimlikle sunucu kaydını ilişkilendirebilirsiniz.

örnekteki log da exception nesnesini kaydetmez; hata türünü ve korelasyon bilgisini tutar. gerçek sistemde kök neden için daha ayrıntılı tanılama gerekebilir. bunu erişimi ve saklama süresi sınırlı, hassas verileri ayıklayan bir kanalda tasarlayın. istek gövdesini, token’ı ve bağlantı dizesini otomatik olarak loglamak iyi bir çözüm değildir.

.net 10’da TryHandleAsync true döndüğünde middleware’in işlenen exception için tanılama üretimi varsayılan olarak bastırılır. bu örnek kendi yapılandırılmış kaydını üretir. framework tanılamasını SuppressDiagnosticsCallback ile yeniden açarsanız çift kaydı kontrol edin. bir trace id üretmek tek başına opentelemetry exporter, kalıcı kayıt veya alarm kurmaz.

05“global” her hatayı kapsamaz

yanıt gövdesi gönderilmeye başladıktan sonra güvenle yeni bir 500 gövdesi yazamazsınız. dosya indirme ve streaming endpoint’lerinde bu sınır özellikle önemlidir. benzer biçimde background service, kuyruk tüketicisi veya uygulama açılışındaki hata http middleware’inin kapsamı dışındadır; kendi hata politikasına ihtiyaç duyar.

istemci bağlantıyı kapattığında oluşan iptali otomatik olarak sunucu arızası saymayın. RequestAborted ve bağımlı işlemlerin cancellation token’larını doğru taşıyın. istemci iptali, kendi zaman aşımınız ve dış servisin zaman aşımı aynı olay değildir.

hata yanıtı işlemi geri almaz. ödeme alındıktan sonra yanıt kaybolmuş olabilir. istemciye her 500’de yeniden dene demeden önce işlemin tekrar güvenliğini düşünün. para tahsilatı gibi işlemlerde idempotency ve transaction sınırları ayrı tasarım kararlarıdır; global catch bunların yerini tutmaz.

06hatayı bilerek üretip sözleşmeyi sınayın

aşağıdaki istek 500 dönmeli. gövdede status 500, code unexpected_error ve boş olmayan traceId bulunmalı. “Demo failure” metni ve stack trace bulunmamalı. aynı traceId sunucu kaydında görülmeli. ardından /orders/42 için 200, /orders/999 için 404 kontrol edin.

otomatik testte gerçek http hattını çalıştırın; handler metodunu doğrudan çağırmak middleware sırasını sınamaz. hatalı isteğin ardından sağlıklı isteğin çalıştığını da doğrulayın. kayıt sisteminde zaman, servis sürümü ve trace id ile arama yapılabildiğini kontrol edin.

bir işletim ortamında hata oranı alarmını kontrollü bir arızayla sınayın. bu örnek öğretici bir başlangıçtır; kalıcı ve kurcalamaya karşı korunan audit kaydı, dağıtık trace aktarımı ve alarm teslimatı ayrıca kurulup doğrulanmalıdır. hata yönetimini başarılı saymak için yalnızca ekranda güzel bir json görmek yeterli değildir.

request.http
GET http://localhost:5080/demo/failure
Accept: application/problem+json
okuduğunuz için teşekkürler← tüm yazılar

bu konuda uygulama desteği

mevcut .net uygulamanız için kod incelemesi, hata giderme ve performans danışmanlığı kapsamını inceleyebilirsiniz.

.net danışmanlığı