TGStat
TGStat
Type to search
Advanced channel search
  • flag English
    Site language
    flag Russian flag English flag Uzbek
  • Sign In
  • Catalog
    Channels and groups catalog Regional compilations Thematic compilations Платные каналы Search for channels
    Add a channel/group
  • Ratings
    Rating of channels Rating of groups Posts rating
    Ratings of brands and people
  • Analytics
  • Search by posts
  • Telegram monitoring
  • Promotion
    Advertising through Yandex Business Advertising in channels through TGStat Agency Advertising on TGStat.ru website
.NET sh blog

28 Aug 2025, 10:03

Open in Telegram Share Report

Как отслеживать все исключения которые возникают в .NET приложении?

Встроенных инструментов 2:

1. С помощью dotnet-counters можно подключиться к работающему .NET процессу и снимать данные с него
2. Метрики приложения (OpenTelemetry / AppMetrics / и т.п.)

Чтобы начать собирать счетчик исключений с OpenTelemetry достаточно вызвать AddRuntimeInstrumentation.
Помимо кол-ва исключений там будет много и других полезных метрик (полный список документируется здесь)

До .NET 9.0 метрика (dotnet_exceptions_total) собирает просто кол-во исключений, а начиная с .NET 9.0 можно смотреть в разрезе по типу исключения, что дает гораздо больше информации о происходящем, но всё еще недостаточно.

Данная метрика собирается по событию AppDomain.Current.FirstChanceException, которая срабатывает на каждый throw любого исключения в текущем процессе.
Поэтому обработчик события должен быть очень быстрым чтобы не тормозить приложение и при этом не выбрасывать новые исключения иначе можно уйти в бесконечный цикл или даже крашнуть рантайм. Полезная ремарка есть в документации события.

Еще важно помнить, что на текущий момент async await разворачивается компилятором в FSM (покрайне мере до async2), внутри которого в случае исключений будут их rethrow на каждый await, а значит чем глубже стек вызовов, тем больше лишних триггеров FirstChanceException. Поэтому метрика с одной стороны врёт, с другой нет. Тут смотря как посмотреть.

А так как на одно исключение может быть создано много событий, то нужно сэмплировать иначе будет очень много бесполезных логов.

Полный листинг доступен на github.
Ниже разберем некоторые моменты:
[ThreadStatic]
private static Dictionary? _sampler;

private void HandleFirstChanceException(object? sender, FirstChanceExceptionEventArgs e)
{
var exceptionType = e.Exception.GetType();

ref var counter = ref CollectionsMarshal.GetValueRefOrAddDefault(_sampler, exceptionType, out var exists);

var needLog = !exists || Interlocked.Increment(ref counter) % LOG_EVERY_N_EXCEPTIONS == 0;

if (needLog)
{
LogExceptionSample(e.Exception, exceptionType.FullName);
}
}
Так как необходимо сэмплировать по каждому типу исключения отдельно, то нужно хранить эти счетчики и здесь подходит словарь (_sampler).
И чтобы атомарно увеличивать счетчики воспользуемся Interlocked. Чтобы с ней работать требуется передать ссылку на значимый тип. Если бы счетчик был просто полем или локальной переменной - то всё было бы просто.
Но так как значение находится в словаре, то воспользуемся CollectionsMarshal.GetValueRefOrAddDefault. Он возьмет ссылку на значение в словаре (если его не было добавит) и дальше передаем в Interlocked.Increment

В лог запишется первое появление исключения и каждые последующие кратные N.

Чтобы приложение не ушло в бесконечный цикл, нужно чтобы метод HandleFirstChanceException вообще не выбрасывал никаких исключений (даже неконтролируемые OOM). Это не всегда возможно обеспечить, особенно если вызывать сторонние библиотеки.

Для этого воспользуемся хаком, который я подглядел в исходниках Microsoft.
Её суть в оборачивании флагом небезопасного участка кода. И пока флаг поднят, порожденные новые исключения будут проигнорированны данным обработчиком в текущем потоке (т.к. ThreadStatic)
[ThreadStatic]
private static bool _handlingFirstChanceException;

private void HandleFirstChanceException(object? sender, FirstChanceExceptionEventArgs e)
{
if (_handlingFirstChanceException)
{
return;
}

_handlingFirstChanceException = true;

try
{
// unsafe code
}
catch (Exception _)
{
// no-op
}
finally
{
_handlingFirstChanceException = false;
}
}
Для любителей high performance кода может быть интересно заглянуть в метод Trim.

В итоге данный ExceptionTracker позволяет залогировать каждые N исключений каждого типа и тем самым иметь больше полезной информации для разбора причин, почему эти исключения возникают.

580 0 8 16
Catalog
Channels and groups catalog Channels compilations Search for channels Add a channel/group
Ratings
Rating of Telegram channels Rating of Telegram groups Posts rating Ratings of brands and people
API
API statistics Search API of posts API Callback
Our channels
@TGStat @TGStat_Chat @telepulse @TGStatAPI
Read
Академия TGStat Telegram Research 2019 Telegram Research 2021 Telegram Research 2023
Contacts
Справочный центр Support Email Jobs
Miscellaneous
Terms and conditions Privacy policy Public offer
Our bots
@TGStat_Bot @SearcheeBot @TGAlertsBot @tg_analytics_bot @TGStatChatBot