Hata Mesajı ve Stack Trace Okumak: .NET Exception'ları, Async Stack'ler ve Angular NG Hataları
Stack trace yukarıdan aşağı değil katman katman okunur: asıl sebep çoğu zaman ---> ile başlayan inner exception'dadır ve log'daki 50 aynı hatanın kökü genellikle zaman sırasındaki ilk hatadır. .NET 10'da InnerException zinciri, AggregateException, throw ex ile kaybolan stack, ExceptionDispatchInfo, ProblemDetails traceId'den log'a gitmek, source map ve Angular 22'nin NG0100, NG0200, NG0201 hatalarını gerçek çıktılarla okuyoruz.
Sürüm notu: Bu yazıdaki kodlar .NET 10 ile derlenip denenmiştir; Angular örnekleri Angular 22.2 ve Vitest 5.1 ile, JavaScript örneği Node.js 24 ve esbuild 0.28 ile çalıştırıldı.
Önce Tahmin Et
Bookstore API'sinin (Application Programming Interface) log'unda son beş dakikada 51 tane fail: satırı var. 50'si aynı: System.NullReferenceException: Object reference not set to an instance of an object., hepsi PriceCatalog.GetPrice içinde. Bir tanesi farklı ve en üstte duruyor. Ekipteki herkes 50 kez tekrar eden hataya bakıyor, çünkü ekranı o dolduruyor. Sence bu bug'ı hangi satır çözer: en çok tekrar eden mi, en önce gelen mi?
İkinci soru: bir catch bloğunda exception'ı log'layıp throw ex; ile tekrar fırlatıyorsun. Stack trace'te hatanın asıl çıktığı method kaç satır görünür? Cevapları bu yazıdaki gerçek çıktılarla vereceğiz.
Exception'ın Anatomisi: Type, Message, Stack Trace ve Inner Exception Zinciri
Exception'ları yakalamanın ve fırlatmanın mekaniği C# Temelleri serisinin "C# Exception Handling: try/catch/finally, throw ve Kendi Exception'ın" yazısında. Burada işimiz okumak. Bookstore'da checkout, ödeme sağlayıcısına gidiyor; sağlayıcı 503 dönünce CheckoutService hatayı kendi exception'ına sarıyor:
public sealed class PaymentFailedException(string message, Exception inner) : Exception(message, inner);
public sealed class PaymentGateway
{
public async Task ChargeAsync(int orderId, decimal amount)
{
await Task.Delay(10);
throw new HttpRequestException("Response status code does not indicate success: 503 (Service Unavailable).");
}
}
public sealed class CheckoutService(PaymentGateway gateway)
{
public async Task CheckoutAsync(int orderId)
{
try
{
await gateway.ChargeAsync(orderId, 349.90m);
}
catch (HttpRequestException ex)
{
// asıl sebebi kaybetmemek için inner olarak taşıyoruz
throw new PaymentFailedException($"Payment for order {orderId} failed.", ex);
}
}
}
ex.ToString()'in Debug build'deki gerçek çıktısı (dosya yolları kısaltıldı):
Bookstore.Errors.PaymentFailedException: Payment for order 1042 failed.
---> System.Net.Http.HttpRequestException: Response status code does not indicate success: 503 (Service Unavailable).
at Bookstore.Errors.PaymentGateway.ChargeAsync(Int32 orderId, Decimal amount) in /src/errors/Program.cs:line 44
at Bookstore.Errors.CheckoutService.CheckoutAsync(Int32 orderId) in /src/errors/Program.cs:line 54
--- End of inner exception stack trace ---
at Bookstore.Errors.CheckoutService.CheckoutAsync(Int32 orderId) in /src/errors/Program.cs:line 58
at Program.<Main>$(String[] args) in /src/errors/Program.cs:line 11
Bu metni yukarıdan aşağı değil, katman katman okursun. İlk satır en dıştaki exception: tipi ve mesajı. ---> ile başlayan satır inner exception'ın başladığını söyler; onun stack trace'i --- End of inner exception stack trace --- satırına kadar sürer. Ondan sonraki frame'ler yine dıştaki exception'a ait. Yani asıl sebep ortada, iki ayraç arasında duruyor: ödeme sağlayıcısı 503 döndü, hata 44. satırda doğdu. Zincir daha uzunsa her ---> bir seviye daha içeri iner; en içteki genellikle sebep, dıştakiler bağlam.
Frame'leri okurken üç şeye bak. Birincisi, in ...:line 44 kısmı yalnız PDB (Program Database, sembol dosyası) varsa görünür; Release build'de de PDB üretilir, ama sunucuya kopyalanmadıysa satır numarası kaybolur. İkincisi, async method'lar için .NET derleyicinin ürettiği state machine'in MoveNext frame'lerini görmüyorsun; runtime bunları okunur method adlarına çeviriyor, CheckoutAsync olarak görüyorsun. Üçüncüsü, Program.<Main>$ gibi açılı parantezli adlar derleyicinin ürettiği koddur: burada top-level statement'lar, başka yerde lambda'lar (<>c__DisplayClass) ve local function'lar.
AggregateException ve await'in Seçtiği Tek Hata
Birden fazla task paralel çalışıp ikisi de patladığında okuman gereken şey değişir. Önce senkron bekleme:
public static void AggregateWithWait()
{
Task a = FailAsync("Inventory service timed out.");
Task b = FailAsync("Shipping quote failed.");
Task.WaitAll(a, b);
}
System.AggregateException: One or more errors occurred. (Inventory service timed out.) (Shipping quote failed.)
---> System.TimeoutException: Inventory service timed out.
at Bookstore.Errors.Demos.FailAsync(String message) in /src/errors/Program.cs:line 95
--- End of inner exception stack trace ---
at System.Threading.Tasks.Task.WaitAllCore(ReadOnlySpan`1 tasks, Int32 millisecondsTimeout, CancellationToken cancellationToken)
at System.Threading.Tasks.Task.WaitAll(ReadOnlySpan`1 tasks)
at Bookstore.Errors.Demos.AggregateWithWait() in /src/errors/Program.cs:line 69
at Program.<Main>$(String[] args) in /src/errors/Program.cs:line 14
---> (Inner Exception #1) System.TimeoutException: Shipping quote failed.
at Bookstore.Errors.Demos.FailAsync(String message) in /src/errors/Program.cs:line 95<---
AggregateException'ın mesajı içteki bütün mesajları parantez içinde sıralar; bu, log'da tek satıra bakarak "kaç şey patladı" sorusunun cevabı. İlk inner exception klasik zincir biçiminde, ikincisi ve sonrakiler (Inner Exception #1) başlığıyla en altta. Log'da yalnız ilk inner'a bakıp "inventory timeout" diye ticket açarsan shipping tarafındaki ikinci sorunu kaçırırsın.
Aynı iki task'ı Task.WhenAll ile await edince tablo değişir:
Task all = Task.WhenAll(a, b);
try
{
await all;
}
catch (Exception ex)
{
Console.WriteLine($"caught: {ex.GetType().Name}: {ex.Message}");
Console.WriteLine($"all.Exception: {all.Exception!.InnerExceptions.Count} inner");
foreach (Exception inner in all.Exception.InnerExceptions)
{
Console.WriteLine($" - {inner.Message}");
}
}
caught: TimeoutException: Shipping quote failed.
all.Exception: 2 inner
- Shipping quote failed.
- Inventory service timed out.
await, AggregateException'ı açar ve yalnız birini fırlatır. Hangisinin geleceğine güvenme: bu koşuda "Shipping" önce geldi, çünkü iki task'tan önce o tamamlandı. Diğer hatalar kaybolmadı, task'ın Exception property'sinde duruyor; ama onu log'lamazsan log'da hiç görünmez. Mekaniğin ayrıntısı C# Asenkron serisinin "C# Async'te Exception: AggregateException ve WhenAll'daki Hatalar" yazısında. Okuma kuralı şu: WhenAll kullanan bir kodda log'daki tek exception, birden fazla hatanın yalnız biri olabilir.
throw; mı throw ex; mi: Kaybolan Stack Trace
Log'layıp tekrar fırlatmak sık yapılan bir şey ve tek kelimelik fark stack trace'in yarısını götürüyor:
public static void Rethrow(bool useThrowEx)
{
try
{
LoadPrices();
}
catch (Exception ex)
{
Console.WriteLine("logging and rethrowing...");
if (useThrowEx)
{
throw ex;
}
throw;
}
}
private static void LoadPrices() => ParsePrice("12,5O");
private static decimal ParsePrice(string raw) =>
decimal.Parse(raw, System.Globalization.CultureInfo.InvariantCulture);
throw; ile:
System.FormatException: The input string '12,5O' was not in a correct format.
at System.Number.ThrowFormatException[TChar](ReadOnlySpan`1 value)
at System.Number.ParseDecimal[TChar](ReadOnlySpan`1 value, NumberStyles styles, NumberFormatInfo info)
at Bookstore.Errors.Demos.ParsePrice(String raw) in /src/errors/Program.cs:line 118
at Bookstore.Errors.Demos.LoadPrices() in /src/errors/Program.cs:line 115
at Bookstore.Errors.Demos.Rethrow(Boolean useThrowEx) in /src/errors/Program.cs:line 102
at Program.<Main>$(String[] args) in /src/errors/Program.cs:line 20
throw ex; ile:
System.FormatException: The input string '12,5O' was not in a correct format.
at Bookstore.Errors.Demos.Rethrow(Boolean useThrowEx) in /src/errors/Program.cs:line 109
at Program.<Main>$(String[] args) in /src/errors/Program.cs:line 23
İkinci çıktıya göre hata Rethrow'un 109. satırında doğmuş; oysa o satır yalnızca throw ex;. ParsePrice, LoadPrices ve fiyatın içindeki "O" harfine giden yol yok. Bu stack trace ile debugging'e başlayan biri ilk olarak yanlış method'a bakar. Derleyici bunu sana zaten söylüyor; projeyi derlerken çıkan gerçek uyarı:
Program.cs(109,21): warning CA2200: Re-throwing caught exception changes stack information
Bazen exception'ı yakaladığın yerde değil, başka bir yerde, hatta başka bir thread'de fırlatman gerekir. O durumda ExceptionDispatchInfo var:
public static void CaptureAndThrowLater()
{
ExceptionDispatchInfo? captured = null;
Thread worker = new(() =>
{
try
{
LoadPrices();
}
catch (Exception ex)
{
captured = ExceptionDispatchInfo.Capture(ex);
}
});
worker.Start();
worker.Join();
// orijinal stack trace korunarak ana thread'de fırlatılır
captured?.Throw();
}
System.FormatException: The input string '12,5O' was not in a correct format.
at System.Number.ThrowFormatException[TChar](ReadOnlySpan`1 value)
at System.Number.ParseDecimal[TChar](ReadOnlySpan`1 value, NumberStyles styles, NumberFormatInfo info)
at Bookstore.Errors.Demos.ParsePrice(String raw) in /src/errors/Program.cs:line 118
at Bookstore.Errors.Demos.LoadPrices() in /src/errors/Program.cs:line 115
at Bookstore.Errors.Demos.<>c__DisplayClass6_0.<CaptureAndThrowLater>b__0() in /src/errors/Program.cs:line 127
--- End of stack trace from previous location ---
at Bookstore.Errors.Demos.CaptureAndThrowLater() in /src/errors/Program.cs:line 136
at Program.<Main>$(String[] args) in /src/errors/Program.cs:line 26
--- End of stack trace from previous location --- satırını iyi tanı: "buraya kadar olan kısım exception'ın yakalandığı yerde kaydedildi, sonrası tekrar fırlatıldığı yer" demek. await da içeride aynı mekanizmayı kullanır; async kodun stack trace'lerinde bu satırı sık görürsün ve bir hata değildir. Üstteki frame'ler hatanın doğduğu yeri, alttakiler onu kimin beklediğini gösterir.
İlk Exception Önemlidir: 51 Hatadan Kökü Bulmak
Önce Tahmin Et'teki log'a dönelim. Uygulama açılırken fiyat kataloğunu bir dosyadan yüklüyor; biri bunu "uygulama yine de ayağa kalksın" diye try/catch'e almış:
PriceCatalog catalog = app.Services.GetRequiredService<PriceCatalog>();
try
{
catalog.Load("data/prices.csv");
}
catch (Exception ex)
{
// açılışta yutulan hata: uygulama ayağa kalkıyor ama katalog boş
app.Logger.LogError(ex, "Price catalog warm-up failed");
}
app.MapGet("/api/books/{id:int}/price", (int id, PriceCatalog prices) => Results.Ok(new { id, price = prices.GetPrice(id) }));
PriceCatalog.GetPrice ise _prices![bookId] diye dictionary'ye gidiyor. Uygulamaya 50 istek attıktan sonra log'a iki komutla bakıyoruz:
$ grep -c "fail:" app.log
51
$ grep -oE "^ +System\.[A-Za-z.]+Exception" app.log | sort | uniq -c | sort -rn
50 System.NullReferenceException
1 System.IO.DirectoryNotFoundException
$ grep -m1 -A3 "fail:" app.log
11:55:55.293 fail: Bookstore.Api[0]
Price catalog warm-up failed
System.IO.DirectoryNotFoundException: Could not find a part of the path '/src/api/data/prices.csv'.
at Interop.ThrowExceptionForIoErrno(ErrorInfo errorInfo, String path, Boolean isDirError)
50 NullReferenceException belirti, ilk satırdaki DirectoryNotFoundException sebep. Deploy, CSV (Comma-Separated Values) dosyasını data/ klasörüne kopyalamamış. GetPrice'a null kontrolü eklemek 50 hatayı 50 tane "katalog boş" hatasına çevirir, sorunu çözmez. Kural basit: aynı anda çok sayıda hata görünce önce zaman sırasına göre ilk hatayı bul, sonra en sık olanı say. Sık olan, etkinin büyüklüğünü söyler; ilk olan, çoğu zaman sebebi. Log'dan zaman çizelgesi kurmayı serinin sekizinci yazısı "Log'lardan Sebep Bulmak: Correlation ID, Trace ID ve Zaman Çizelgesi Kurmak" ayrıntılı işliyor.
ProblemDetails'teki traceId ile Log'a Gitmek
Kullanıcı ya da frontend ekibi sana "500 aldım" diye geldiğinde elindeki tek ipucu yanıtın kendisi. AddProblemDetails ve UseExceptionHandler açık bir ASP.NET Core 10 uygulamasında aynı endpoint'in gerçek yanıtı:
$ curl -s -i http://localhost:5201/api/books/12/price
HTTP/1.1 500 Internal Server Error
Content-Type: application/problem+json
{"type":"https://tools.ietf.org/html/rfc9110#section-15.6.1","title":"An error occurred while processing your request.","status":500,"traceId":"00-b336c36b1f3f1102b0216db006c36934-82d5ef02e74e2ce6-00"}
Yanıtta exception mesajı yok; production'da olmaması da gerekiyor. Ama traceId var ve W3C (World Wide Web Consortium) Trace Context biçiminde: 00 sürüm, ardından 32 karakterlik trace ID, 16 karakterlik span ID ve flag'ler. Console logger'da IncludeScopes açıksa aynı trace ID log satırının scope'unda görünür:
11:55:45.111 fail: Microsoft.AspNetCore.Diagnostics.ExceptionHandlerMiddleware[1]
=> SpanId:82d5ef02e74e2ce6, TraceId:b336c36b1f3f1102b0216db006c36934, ParentId:0000000000000000 => ConnectionId:0HNPTEO9M82VJ => RequestPath:/api/books/12/price RequestId:0HNPTEO9M82VJ:00000001
An unhandled exception has occurred while executing the request.
System.NullReferenceException: Object reference not set to an instance of an object.
at Bookstore.Api.PriceCatalog.GetPrice(Int32 bookId) in /src/api/Program.cs:line 42
at Program.<>c.<<Main>$>b__0_1(Int32 id, PriceCatalog prices) in /src/api/Program.cs:line 27
at lambda_method1(Closure, Object, HttpContext)
at Microsoft.AspNetCore.Diagnostics.ExceptionHandlerMiddlewareImpl.Invoke(HttpContext context)
Yanıttaki b336c36b… ile log'daki TraceId:b336c36b… aynı. Log aracında bu değeri arattığında o request'e ait bütün satırları, başka servislere yayıldıysa onların log'larını da bulursun. Bu yüzden destek ekibine "ekran görüntüsü değil, yanıttaki traceId'yi iste" demek debugging süresini dakikalardan saniyelere indirir. Global hata yönetiminin kurulumu ASP.NET Core Web API serisinin "ASP.NET Core'da Global Hata Yönetimi: Exception Middleware ve ProblemDetails" yazısında; log'u üretme tarafı ise "ASP.NET Core'da Logging: ILogger, Log Seviyeleri, Structured Logging ve Serilog" yazısında.
Frontend Tarafı: JavaScript Stack Trace ve Source Map
Production'daki JavaScript minify edilmiş tek satırlık bir bundle. Sepet toplamını hesaplayan küçük bir TypeScript dosyası:
interface CartLine {
bookId: number;
quantity: number;
unitPrice?: { amount: number };
}
function lineTotal(line: CartLine): number {
return line.quantity * line.unitPrice!.amount;
}
export function cartTotal(lines: CartLine[]): number {
return lines.reduce((sum, line) => sum + lineTotal(line), 0);
}
console.log(cartTotal([
{ bookId: 7, quantity: 2, unitPrice: { amount: 349.9 } },
{ bookId: 12, quantity: 1 },
]));
esbuild ile --minify --sourcemap derleyip Node.js 24'te çalıştırınca gelen stack:
TypeError: Cannot read properties of undefined (reading 'amount')
at q (/app/dist/main.js:1:509)
at /app/dist/main.js:1:555
at Array.reduce (<anonymous>)
at i (/app/dist/main.js:1:539)
at Object.<anonymous> (/app/dist/main.js:1:575)
Fonksiyon adları q ve i, her şey 1. satırda. Aynı dosyayı node --enable-source-maps ile çalıştırınca source map devreye giriyor:
TypeError: Cannot read properties of undefined (reading 'amount')
at lineTotal (/app/cart.ts:8:42)
at <anonymous> (/app/cart.ts:12:44)
at Array.reduce (<anonymous>)
at cartTotal (/app/cart.ts:12:16)
at Object.<anonymous> (/app/cart.ts:15:13)
Artık hata cart.ts'in 8. satırında, unitPrice!'ın non-null assertion'ı yalan söylemiş. Tarayıcıda DevTools source map'i otomatik uygular, yeter ki .map dosyasına erişebilsin. Production'da source map'i herkese açmak istemiyorsan hata izleme aracına yükleyip sunucudan kaldırmak yaygın bir yoldur; Sources panelinin ayrıntıları ilerideki Browser DevTools serisinde. JavaScript'te hata zinciri kurmak (cause) JavaScript Orta seviye serisinin "JavaScript Hata Yönetimi: try/catch, Kendi Error Class'ın ve cause ile Hata Zinciri" yazısında.
Angular'ın NG Hata Kodları
Angular hatalarının başında NG ve dört haneli bir kod olur; her kodun angular.dev/errors altında açıklama sayfası var ve mesajın sonundaki "Find more at" bağlantısı seni doğrudan oraya götürür. Üç sık hatayı Angular 22.2'de Vitest testiyle gerçekten ürettik:
@Component({
selector: 'app-cart-badge',
template: `<span>{{ itemCount }} items</span>`,
})
class CartBadge implements AfterViewInit {
itemCount = 0;
ngAfterViewInit(): void {
this.itemCount = 3;
}
}
@Injectable({ providedIn: 'root' })
class CartService {
private readonly pricing = inject(PricingService);
}
@Injectable({ providedIn: 'root' })
class PricingService {
private readonly cart = inject(CartService);
}
@Injectable()
class OrderApi {}
FAIL |ng| src/app/errors.spec.ts > Angular error codes > NG0100 (exhaustive check)
Error: NG0100: ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value: '0'. Current value: '3'. Expression location: _CartBadge component. Find more at https://v22.angular.dev/errors/NG0100
FAIL |ng| src/app/errors.spec.ts > Angular error codes > NG0200
Error: NG0200: Circular dependency detected for `_CartService`. Source: DynamicTestModule. Path: _CartService -> _PricingService -> _CartService. Find more at https://v22.angular.dev/errors/NG0200
FAIL |ng| src/app/errors.spec.ts > Angular error codes > NG0201
ɵNotFound: NG0201: No provider found for `_OrderApi`. Source: DynamicTestModule. Find more at https://v22.angular.dev/errors/NG0201
NG0100, değer change detection kontrol ettikten sonra değişti demek. Mesaj sana önceki ve şimdiki değeri ('0' → '3') ve hangi component'te olduğunu veriyor; Vitest de template'teki ifadeyi işaretliyor. Bu kontrol yalnız development modunda çalışır. Burada bir sürpriz var: aynı component varsayılan ayarlarla hiç hata vermedi, ekranda sessizce 0 items kaldı. Angular 22'de component'ler varsayılan olarak OnPush ve kontrol pass'i exhaustive değil; hatayı görmek için teste provideCheckNoChangesConfig({ exhaustive: true }) ekledik. Yani Angular 22'de bu bug'ın belirtisi çoğu zaman bir hata değil, güncellenmeyen bir ekran. Signal ve change detection kaynaklı bu tür bug avları serinin yedinci yazısında.
NG0200, DI'da (Dependency Injection) döngüsel bağımlılık; Path kısmı döngünün kendisi. NG0201 ise eski adıyla NullInjectorError: istenen token için provider yok. Angular 22'de mesaj "No provider found for" diye başlıyor; eski "No provider for HttpClient!" aramaları bu yüzden artık tam eşleşmeyebilir. Bu arada HttpClient artık root'ta varsayılan olarak provide edildiği için, provideHttpClient() olmadan inject(HttpClient) yapan aynı testimiz hatasız geçti. Class adlarının başındaki alt çizgi (_CartService) derleyicinin ürettiği addır, kodunda ararken alt çizgiyi at.
Üretimde Dikkat
Exception'ı log'larken mesajı değil exception'ın kendisini ver: LogError(ex, "..."). Yalnız ex.Message log'larsan inner exception zinciri ve stack trace kaybolur; yukarıdaki 503 hatası "Payment for order 1042 failed." cümlesine iner.
PDB dosyalarını deploy'a dahil et ya da bir symbol server'da sakla. Satır numarası olmayan bir stack trace'te hangi satırın null olduğunu ancak tahmin edersin. Aynı şey frontend için source map'te geçerli: build'in ürettiği .map dosyalarını sürümle birlikte arşivle, yoksa iki hafta sonraki bir hatayı çözemezsin.
Log hacmini izle. Bir sebep 50 belirti üretti; gerçek trafikte bu 50.000 olur ve log maliyeti, hatta log pipeline'ının kendisi problem hâline gelir. Tekrarlanan hataları toplayan bir hata izleme aracı ya da log sorgusunda uniq -c mantığı (tipe ve mesaja göre gruplama) ilk bakışın parçası olmalı.
Production yanıtlarında exception mesajını ve stack trace'i istemciye dönme. ProblemDetails'teki traceId yeterli; geri kalan her şey log'da. Stack trace içinde dosya yolları, sınıf adları, bazen bağlantı bilgileri olabilir.
Sık Düşülen Tuzaklar
Exception'ı yutan catch. catch (Exception) { } ya da yalnız return null; hatayı yok etmez, yerini değiştirir: sorun bir sonraki satırda NullReferenceException olarak, asıl sebepten uzakta çıkar. Bu yazıdaki warm-up hatası tam olarak bu: log'landı ama yutuldu ve 50 yanıltıcı hata üretti. Uygulama gereken veri olmadan çalışamıyorsa açılışta düşmesi daha dürüsttür.
Mesajı okumadan arama motoruna yapıştırmak. Mesajın çoğu zaman sana söylediği tek şey yeterlidir: The input string '12,5O' kısmında rakam sıfır değil, büyük O harfi var. Aramaya yapıştırınca bu ayrıntı genel sonuçların arasında kaybolur.
Yalnız en üstteki satırı okumak. PaymentFailedException bağlamdır; ---> ile başlayan satırdaki HttpRequestException sebeptir. Zincirin en içine inmeden ticket açma.
throw ex; yazmak ve CA2200 uyarısını görmezden gelmek. Analyzer uyarılarını build'de hata olarak ele almak bu tuzağı tamamen kapatır.
Framework frame'lerinde kaybolmak. Stack trace'te System.Number, Microsoft.AspNetCore gibi frame'ler kalabalık yapar; kendi namespace'ine (Bookstore.*) ait ilk frame genellikle bakman gereken yerdir.
Küçük Bir Deneme
★☆☆ CheckoutService'teki catch bloğunda ex'i inner olarak vermeden new PaymentFailedException(...) fırlat. ToString() çıktısında ne kayboluyor? Log'a bakan biri 503'ü nasıl öğrenebilir?
★★☆ WhenAllAwaitAsync'te iki task'ın Task.Delay sürelerini farklı yap (10 ve 50 ms). catch'e hangi exception düşüyor? Sonra all.Exception'ı log'layan bir satır ekle ve log'da her iki hatanın da göründüğünü doğrula.
★★★ Minimal API'de warm-up hatasını yutmak yerine uygulamanın açılmasını engelle ve bir health check endpoint'i ekle. Deploy sırasında dosya eksikse ne oluyor, kaç hata satırı üretiliyor? Aynı log'u grep ile analiz et ve önceki 51 satırla karşılaştır.
Hata mesajını ve stack trace'i okuyabiliyoruz; çoğu zaman bu, hatanın nerede olduğunu söylemeye yeter ama neden olduğunu söylemez. Bir sonraki yazıda debugger'a geçip conditional breakpoint, tracepoint, data breakpoint ve Exception Settings ile "neden"i çalışan programın içinde arıyoruz.