TGStat
TGStat
Введите текст для поиска
Расширенный поиск каналов
  • flag Russian
    Язык сайта
    flag Russian flag English flag Uzbek
  • Вход на сайт
  • Каталог
    Каталог каналов и чатов Региональные подборки Тематические подборки Платные каналы Поиск каналов
    Добавить канал/чат
  • Рейтинги
    Рейтинг каналов Рейтинг чатов Рейтинг публикаций
    Рейтинги брендов и персон
  • Аналитика
  • Поиск по публикациям
  • Мониторинг Telegram
  • Продвижение
    Реклама через Яндекс Бизнес Реклама в каналах через TGStat Agency Реклама на сайте TGStat.ru
.NET sh blog

28 Aug 2025, 10:03

Открыть в Telegram Поделиться Пожаловаться

Как отслеживать все исключения которые возникают в .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
Каталог
Каталог каналов и чатов Подборки каналов Поиск каналов Добавить канал/чат
Рейтинги
Рейтинг каналов Telegram Рейтинг чатов Telegram Рейтинг публикаций Рейтинги брендов и персон
API
API статистики API поиска публикаций API Callback
Наши каналы
@TGStat @TGStat_Chat @telepulse @TGStatAPI
Почитать
Академия TGStat Исследование Telegram 2019 Исследование Telegram 2021 Исследование Telegram 2023
Контакты
Справочный центр Поддержка Почта Вакансии
Всякая всячина
Пользовательское соглашение Политика конфиденциальности Публичная оферта
Наши боты
@TGStat_Bot @SearcheeBot @TGAlertsBot @tg_analytics_bot @TGStatChatBot