Как использовать свойство Exception.Data, чтобы логировать дополнительные сведения об исключениях

Хорошее, подробное исключение — мощный инструмент, который помогает найти и исправить проблему. Поэтому в исключения стоит вносить больше деталей. 

Один из способов — добавить в текст сообщения исключения дополнительные сведения. Как в коде ниже.

try
{ return await _ordersRepository.Get(id, cancellationToken);
}
catch (Exception exception)
{ throw new Exception($"Unable to get order info, user {userName}, order id {id}", exception);
}

Но при таком подходе затруднительно создавать сообщения. И небезопасно: сообщение может быть отправлено пользователю в случаях, когда исключение не было корректно обработано. Нам следует избегать отправки пользователю такой информации, как идентификаторы. Все, что нужно, чтобы исправить ошибку, — залогировать дополнительные сведения и информацию об оригинальном исключении на стороне сервера.

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

Класс .NET Framework Exception имеет свойство Data, которое уже обеспечивает хранение дополнительных сведений в виде коллекции заданных пар «ключ — значение». Так как это свойство базового класса, я уверен, вы сможете настроить логгер. В наших примерах ниже мы будем использовать NLog, который настраивается достаточно легко. Чтобы избежать конфликта ключей и обработать ошибку наиболее эффективным способом, советую также создать свое исключение.

Половина классов исключений .NET Framework имеет свойства, которые не логируются, — вы можете добавить эти данные в свойство Exception.Data вашего нового исключения. Свойство InnerException будет содержать ссылку на оригинальное исключение. 

Код ниже демонстрирует, как использовать свойство Exception.Data.

try
{ return await _ordersRepository.Get(id, cancellationToken);
}
catch (Exception exception)
{ const string message = "Unable to get order info"; var yourException = new YourAppException(message, exception); yourException.Data[nameof(userName)] = userName; yourException.Data[nameof(id)] = id; throw yourException;
}

Упс. Похоже, мы добавили больше кода — но можем исправить это, создав расширения, которые позволят задействовать паттерн Fluent interface. Пример ниже демонстрирует, как сделать код более читабельным и простым в использовании.

try
{ return await _ordersRepository.Get(id, cancellationToken);
}
catch (Exception exception)
{ throw exception.With("Unable to get order info") .DetailData(nameof(userName), userName) .DetailData(nameof(id), id);
}

Давайте рассмотрим пример настройки NLog layout для логирования свойства Exception.Data.

${shortdate} ${time} [${level:uppercase=true}]: ${message:withException=true}${when:when=length('${exception:format=Data}')>0:Inner=${newline}--- Exception Data ---${newline}${exception:format=Data:exceptionDataSeparator=,\r\n}}

Как это выглядит в консоли.

Этот пример работает замечательно, если добавлять простые структуры: NLog вызывает ToString(), чтобы записать значения в targets. Поэтому мы можем правильно залогировать объекты, только если они корректно переопределяют метод ToString().

Но переопределить ToString для всех классов практически невозможно. Наиболее простой способ представить объект в виде строки — сериализовать его в JSON. Код ниже добавляет C#-класс, который это делает.

/// <summary>
/// Defines a value/json pair to represent an exception data value as JSON
/// </summary>
public record ExceptionDataEntry
{ private static readonly JsonSerializerOptions SerializerOptions = new() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase, Converters = {new JsonStringEnumConverter(JsonNamingPolicy.CamelCase)}, WriteIndented = true }; private ExceptionDataEntry(in object value, in string json) { Value = value; Json = json; } public object Value { get; } public string Json { get; } public static ExceptionDataEntry FromValue(in object value) { if (value == null) { throw new ArgumentNullException(nameof(value)); } var json = JsonSerializer.Serialize(value, SerializerOptions); return new ExceptionDataEntry(value, json); } /// <summary> /// Represents an exception data value as JSON /// </summary> /// <returns></returns> public override string ToString() { return Json; }
}

А этот код добавляет расширение.

public static YourAppException DetailData(this YourAppException exception, in string key, in object value)
{ try { exception.Data[key] = ExceptionDataEntry.FromValue(value); } catch { // ignored, because we use it inside another exception catch block // so, we should avoid throwing a new exception to keep the original exception } return exception;
}

Как это будет выглядеть в консоли в нашем примере.

Я надеюсь, этот подход к использованию Exception.Data для логирования дополнительных сведений поможет вам в поддержке приложений. Если у вас есть идеи, как улучшить предложенный подход, пожалуйста, пишите в комментариях. Спасибо =)

Читайте так же:

  • Как компании пытаются заманить сотрудников обратно в офисКак компании пытаются заманить сотрудников обратно в офис Еда! Сильнейший мотиватор. А в условиях, когда большинство людей хотят продолжать работать из дома, похоже, единственный. Тысячи компаний, начиная от американской Goldman Sachs и заканчивая французской Havas, надеются, что путь к сердцам их сотрудников лежит через желудок. И […]
  • Директор по маркетингу Описание «Директор по маркетингу» — комплексный курс Русской Школы Управления, на котором вы освоите инструменты маркетингового управления компанией и узнаете, как вывести свое подразделение на новый уровень в жесткой конкурентной среде. Курс разработан аккредитованными […]
  • Google: плотность ключевых слов не является фактором ранжирования В конце декабря один из вебмастеров спросил в Reddit, является ли плотность ключевых слов фактором ранжирования. Обсуждение получилось не очень активным, но мнения специалистов разделились. Свой комментарий также оставил сотрудник Google Джон Мюллер. Он ограничился кратким «Нет»: […]
  • Самые востребованные IT-профессии 2022 годаСамые востребованные IT-профессии 2022 года После стресса 2020, этот год стал решающим для небольших IT-компаний и агентств. Ведь богатели и развивались только крупные компании, а команды на несколько десятков спецов были вынуждены выживать. Естественно, это отразилось на IT-сфере в целом и на востребованности профессий в […]