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

29 Apr 2024, 14:26

Open in Telegram Share Report

Теперь рассмотрим область действия этого LoggingScope.

Метод BeginScope возвращает IDisposable который по-умолчанию мы оборачиваем в using.
После того как LoggingScope будет удален, эти данные из словаря перестанут прикрепляться в логи.

Это очень удобно, но есть нюансы из-за особенности обработки исключений, так как при выбросе исключения CLR выполняет размотку стека вызовов (unwind stack), чтобы найти метод с блоком catch который может поймать этот тип исключения и в большинстве случаев к моменту захода в блок catch, dispose скоупа уже будет выполнен.
Об этом поведении можно подробно почитать в статье от Stephen Cleary - A New Pattern for Exception Logging

Чтобы учесть все эти нюансы, я обычно скоупы объявляю в начале UseCase, сразу же после проверки аргументов и непосредственно перед блоком try catch, который оборачивает все тело метода UseCase.
А чтобы при любой ситуации был присвоен код ошибки, в блоке catch ловлю любые не доменные исключения и
превращаю их в доменное исключение со специальным для данного юзкейса кодом ошибки.

Выглядит это примерно так:
public async Task ExecuteAsync(Command command, CancellationToken ct = default)
{
ArgumentNullException.ThrowIfNull(command);

// здесь объявляем скоупы

try
{
// тело сценария
}
catch (Exception e) when (e is not DomainException)
{
throw new DomainException(ErrorCodes.MyUseCaseFailed, innerException: e);
}
}
При этом явно логировать доменные исключения нет необходимости, так как в их конструкторе вызывается функция OnExceptionCreated, который используется в Composition Root для логирования исключений, еще до выброса этого исключения, а значит в том же контексте.

Теперь представьте что абонент пытается вернуть книгу, которой нет в базе данных вообще. Вернуть несуществующую книгу нельзя, поэтому будет возвращен соответствующий код ошибки и добавлен лог об этом:
{
"Timestamp": "2024-04-29T14:03:44.7722095+05:00",
"Level": "Information",
"MessageTemplate": "{ErrorCode}: {Message}",
"TraceId": "054b1bd4b647d40442ef57bf898e913d",
"SpanId": "0399f7806cbd8721",
"Exception": "BL00032: This book not found or not borrowed by abonent\r\nBookLibrary.Domain.Exceptions.BookLibraryException: This book not found or not borrowed by abonent",
"Properties": {
"ErrorCode": "BL00032",
"Message": "This book not found or not borrowed by abonent",
"EventId": { "Id": 1, "Name": "LogDomainException" },
"SourceContext": "Sstv.DomainExceptions.DomainException",
"AbonentId": "018e8ed6-27a3-75bd-b28e-1afd9f5c3bd6",
"BookId": "018e90a5-88e5-7566-be05-a8a6fc60c932",
"ActionId": "26f62c17-4e2a-4b47-a1af-0623c4d36052",
"ActionName": "BookLibrary.Api.Features.ReturnBookController.ReturnBookAsync (BookLibrary.Api)",
"RequestId": "0HN385D0E35F5:00000001",
"RequestPath": "/api/v1/books/return",
"ConnectionId": "0HN385D0E35F5",
"Host": "My-PC",
"ErrorId": "c508483a-aac7-4bb9-97bb-c398346437dd"
}
}
Как мы видим, в Properties есть и BookId и AbonentId, которые я добавляю в скоупы в начале UseCase и есть много других контекстных данных проставленных самим AspNetCore.

Если выбросить не доменное исключение внутри try catch, то размотка стека будет выполнена частично (не успеем выйти за пределы текущего метода т.к. catch это исключение поймает), поэтому объявленные скоупы все ещё будут доступны при логировании.

Получается что при любом раскладе, скоупы в UseCase работают так как от них ожидается.

Если бы UseCase не был обёрнут в такой try catch или если перенести BeginScope внутрь try catch, то LoggingScope будут удалены к моменту входа в catch блок.

Еще настоятельно рекомендую наименования переменных в LoggingScope писать единообразно, в идеале зафиксировав их имена где-нибудь в константах.
Например идентификатор абонента следует везде указывать строго в поле "AbonentId". Создание новых UseCase почти всегда будет с оглядкой на существующие UseCase, поэтому подход будет распространяться сам по себе через копирование вне зависимости делает это джун или сеньор :)

219 0 1 2 5
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