Логирование

Steve Smith

В ASP.NET Core есть встроенная поддержка логирования, и это позволяет разработчикам легко работать с желаемым функционалом логирования. Реализация логирования требует минимального количества кода. После этого логирование может быть добавлено в любом нужном месте.

+.. contents:: Разделы: + :local: + :depth: 1

Просмотрите или скачайте пример с GitHub.

Реализация логирования

Чтобы добавить логирование в компонент приложения, нужно запросить либо ILoggerFactory,либо ILogger<T> через Внедрение зависимостей (Dependency Injection). Если запрашивается ILoggerFactory, логирование создается с помощью метода CreateLogger. В следующем примере показано, как сделать это:

После создания логирования нужно назвать категорию. Имя категории указывает на источник событий логирования. По соглашению эта строка имеет иерархическую структуру, и категории разделены точками (.). У некоторых параметров логирования есть фильтрующая поддержка, которая упрощает определение местоположения результата нужного логирования. В примере выше логирование использует встроенный ConsoleLogger (см. Настройка логирования). Чтобы увидеть, как работает консольное логирование, запустите пример приложения с помощью команды dotnet run и сделайте запрос к настроенному URL (localhost:5000). Вы должны увидеть схожий результат:

../_images/console-logger-output.png

Вы можете увидеть несколько логов для одного веб запроса, который вы делаете в браузере, поскольку большинство браузеров будут делать несколько запросов (например, к файлу favicon) при попытке загрузить страницу. Обратите внимание, что в консоли отобразился уровень лога (info на рисунке сверху), за которым следует категория ([Catchall Endpoint]), а затем загруженное сообщение.

Метод логирования может использовать форматную строку с названными метками-заменителями (например, {path}). Эти заменители заполняются в том порядке, в котором они появляются как значения аргументов, переданных методу. Некоторые провайдеры логирования сохраняют эти имена и значения в словаре, который позже можно запросить. В примере ниже путь запроса передается как метка-заменитель:

await context.Response.WriteAsync("No endpoint found - try /api/todo.");

В реальных приложениях вы будете добавлять логирование на уровне приложения, а не на уровне фреймворка, событий. Например, если вы создали Web API приложение для управления задачами To-Do (см. Создание первого Web API с помощью MVC 6), вы можете добавить логирование для различных операций, касающихся этих задач.

Логика для API содержится внутри TodoController, который использует Внедрение зависимостей (Dependency Injection), чтобы запрашивать сервисы, которые ему требуются, через конструктор. В идеале классы должны следовать этому примеру и использовать свой конструктор, чтобы недвусмысленно определять свои зависимости в качестве параметров. Вместо того чтобы напрямую запрашивать ILoggerFactory и создавать экземпляр ILogger, TodoController показывает другой способ работы с логированием - вы запрашиваете ILogger<T> (где T - это класс, запрашивающий логи).

[Route("api/[controller]")]
public class TodoController : Controller
{
    private readonly ITodoRepository _todoRepository;
    private readonly ILogger<TodoController> _logger;

    public TodoController(ITodoRepository todoRepository, 
        ILogger<TodoController> logger)
    {
        _todoRepository = todoRepository;
        _logger = logger;
    }

    [HttpGet]
    public IEnumerable<TodoItem> GetAll()
    {
        _logger.LogInformation(LoggingEvents.LIST_ITEMS, "Listing all items");
        EnsureItems();
        return _todoRepository.GetAll();
    }

В каждом методе действия контроллера логирование реализуется в локальном поле, _logger, как показано на строке 17. Эта технология не ограничена контроллерами, она может быть использована любым сервисом приложения, который пользуется Внедрение зависимостей (Dependency Injection).

Работа с ILogger<T>

Как мы только что видели, приложение может запрашивать экземпляр ILogger<T> в качестве зависимости в конструктор класса, где T - это тип, запрашивающий логирование. С помощью TodoController показан пример такого подхода. Когда работает такая технология, при логировании автоматически будет использоваться имя типа в качестве имени категории. При запросе экземпляра ILogger<T> вашему классу не нужно будет создавать экземпляр логгера через ILoggerFactory. Вы можете использовать такой подход везде, если вам не нужен дополнительный функционал ILoggerFactory.

Уровни конкретизации логирования (Verbosity Levels)

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

ASP.NET Core определяет 6 уровней конкретизации логов:

Trace
Используется для наиболее детальных логов, и они особенно полезны при отладке. Такие сообщения могут содержать чувствительные данные, так что они не должны входить в производственную среду. Disabled by default. Пример: Credentials: {"User":"someuser", "Password":"P@ssword"}
Debug
Эти сообщения обладают краткосрочной пользой в процессе разработки. Они содержат информацию, которая может быть полезна при отладке, но не имеют долгосрочной ценности. Пример: Entering method Configure with flag set to true
Information
Эти сообщения используются для отслеживания общего потока приложения. Они обладают долгосрочными значениями, в отличии от Verbose. Пример: Request received for path /foo
Warning
Данные сообщения используются для отслеживания необычных или неожиданных событий в потоке приложения. Это включает в себя ошибки или другие условия, которые не останавливают приложение, но с которыми, возможно, нужно будет разобраться в будущем. При обработке исключений стоит пользоваться уровнем Warning. Примеры: Login failed for IP 127.0.0.1 или FileNotFoundException for file foo.txt
Error
Данные сообщения используются тогда, когда текущий поток приложения должен быть остановлен из-за ошибки, например, исключения, которое не может быть обработано или из-за которого приложение не может быть восстановлено. Такие сообщения должны указывать на сбой в текущей операции (например, текущем HTTP запросе), а не сбое на уровне приложения. Пример: Cannot insert record due to duplicate key violation
Critical
Такие сообщения должны использоваться для отслеживания неисправимых сбоев приложения или системы, а также катастрофических сбоев, которые требуют немедленного вмешательства. Пример: потеря данных, нехватка памяти на диске

В пакете Logging есть вспомогательные методы расширения для каждого из этих стандартных значений LogLevel, которые позволяет вам вызывать LogInformation, вместо того чтобы вызывать более подробный метод Log(LogLevel.Information, ...). У каждого метода расширения для определенного LogLevel есть несколько перегруженных вариантов, куда вы можете передавать все или только некоторые следующие параметры:

string data
Сообщение лога.
EventId eventId
Числовой id предназначается для работы с логом, который используется для связывания набора событий логирования с другим таким набором. Событийные ID должны быть статическими и конкретными для определенного вида событий, для которых ведутся логи. Например, событие при использовании покупательской корзины может быть обозначено как событие с id 1000, а завершение покупки как событие с id 1001. Это позволяет фильтровать и обрабатывать логи.
string format
Формат строки для сообщения лога.
object[] args
Массив объектов для форматирования.
Exception error
Исключение для логирования.

Примечание

Тип EventId можно легко привести к int.

Примечание

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

В примере с TodoController id константы событий определяются для каждого события, а логи настраиваются на определенный уровень конкретизации в зависимости от того, как проходит операция. В данном случае успешные операции логируются как Information, а не найденные результаты логируются как Warning (обработка ошибок не показывается).

[HttpGet]
public IEnumerable<TodoItem> GetAll()
{
    _logger.LogInformation(LoggingEvents.LIST_ITEMS, "Listing all items");
    EnsureItems();
    return _todoRepository.GetAll();
}

[HttpGet("{id}", Name = "GetTodo")]
public IActionResult GetById(string id)
{
    _logger.LogInformation(LoggingEvents.GET_ITEM, "Getting item {0}", id);
    var item = _todoRepository.Find(id);
    if (item == null)
    {
        _logger.LogWarning(LoggingEvents.GET_ITEM_NOTFOUND, "GetById({0}) NOT FOUND", id);
        return NotFound();
    }
    return new ObjectResult(item);
}

Примечание

Мы рекомендуем, чтобы вы выполняли логирование на уровне приложения и API, а не на уровне фреймворка. Во фреймворке есть встроенное логирование, которое можно включить, настроив соответствующий уровень логирования.

Чтобы просмотреть более детальное логирование на уровне фреймворка, вы можете установить LogLevel на Debug или Trace. Например, если AddConsole вызывает метод Configure, который использует LogLevel.Trace и запускает приложение, то вы увидите такой результат:

../_images/console-logger-trace-output.png

Консольный логер прибавляет префикс “dbug: ” результату debug.

Уровень лога Префикс
Critical crit
Error fail
Warning warn
Information info
Debug dbug
Trace trce

Scope

Вы можете группировать логические операции внутри scope. scope - это тип IDisposable, возвращаемый при вызове метода BeginScopeImpl, который работает с момента создания до момента размещения. Встроенный логгер `TraceSource`_ возвращает экземпляр scope, который отвечает за запуск и остановку трассирующих операций. Любое состояние логов, например, id трансакции, прикрепляется к scope во время создания.

Scope-типы не стоит использовать часто. Лучше всего использовать их в операциях, которые начинаются и завершают работу в разных местах, например, трансакциях, где задействованы несколько ресурсов.

Настройка логирования

Чтобы настроить логирование, вам нужно использовать ILoggerFactory в методе Configure класса Startup. ASP.NET автоматически создаст экземпляр ILoggerFactory с помощью Внедрение зависимостей (Dependency Injection), когда вы добавите параметр в метод Configure. Как только вы добавите ILoggerFactory в качестве параметра, вы настроите логгеры внутри метода Configure, вызвав методы (или методы расширения) для фабрики логгеров. Мы уже видели такую настройку в начале статьи, когда добавляли консольное логирование с помощью loggerFactory.AddConsole.

Экземпляр LoggerFactory можно дополнительно настроить с помощью пользовательского FilterLoggerSettings. См. пример ниже.

loggerFactory
    .WithFilter(new FilterLoggerSettings
    {
        { "Microsoft", LogLevel.Warning },
        { "System", LogLevel.Warning },
        { "ToDoApi", LogLevel.Debug }
    })

Примечание

Вы также можете указать уровень конкретизации логов для каждого провайдера. Например, метод расширения AddConsole поддерживает дополнительный параметр для настройки минимального LogLevel.

Вы также можете указать, включить или нет информацию по scope в результат, установив includeScopes на true. Также вы можете указать функцию для фильтрации уровней логов, которые вы хотите включить (например, l => l >= LogLevel.Warning) или функцию для фильтрации, основываясь на уровнях логов и строках категорий (например, (category,loglevel) => category.Contains("MyController") && loglevel >= LogLevel.Trace).

Настройка логирования TraceSource

При запуске полной версии .NET Framework вы можете настроить логирование, чтобы можно было использовать существующие библиотеки и провайдеры System.Diagnostics.TraceSource. TraceSource позволяет вам передавать сообщения множеству слушателей и уже используется многими организациями.

Во-первых, добавьте в проект пакет Microsoft.Extensions.Logging.TraceSourceproject.json), наряду с любыми другими пакетами, которые вы будете использовать (в данном случае, TextWriterTraceListener):

В следующем примере вы увидите, как настроить экземпляр TraceSourceLogger, который создает логи приоритета Warning или выше. AddTraceSource принимает TraceListener. Вызов настраивает TextWriterTraceListener.

var testSwitch = new SourceSwitch("sourceSwitch", "Logging Sample");
testSwitch.Level = SourceLevels.Warning;
loggerFactory.AddTraceSource(testSwitch,
    new TextWriterTraceListener(writer: Console.Out));

sourceSwitch настроен на использование SourceLevels.Warning, так что TraceListener подхватывает только сообщения Warning (или выше).

Действие API логирует предупреждение, если указанный id не найден:

[HttpGet("{id}", Name = "GetTodo")]
public IActionResult GetById(string id)
{
    _logger.LogInformation(LoggingEvents.GET_ITEM, "Getting item {0}", id);
    var item = _todoRepository.Find(id);
    if (item == null)
    {
        _logger.LogWarning(LoggingEvents.GET_ITEM_NOTFOUND, "GetById({0}) NOT FOUND", id);
        return NotFound();
    }
    return new ObjectResult(item);
}

Чтобы протестировать код, запустите приложение с консоли и перейдите по http://localhost:5000/api/Todo/0. Вы увидите схожий результат:

../_images/trace-source-console-output.png

Желтая строка с префиксом “warn: ” - это результат ConsoleLogger. Следующая строка, начинающаяся с “TodoApi.Controllers.TodoController” - это результат TraceSource. Есть много других слушателей TraceSource, и можно настроить TextWriterTraceListener, чтобы он использовал любой экземпляр TextWriter.

Настройка других провайдеров

В дополнении ко встроенным логгерам вы можете настраивать логирование с помощью других провайдеров. Добавьте соответствующий пакет в файл project.json, а затем настраивайте его, как и любой другой провайдер. Обычно эти пакеты должны включать в себя методы расширения для ILoggerFactory, чтобы их было проще добавлять.

  • elmah.io - provider for the elmah.io service
  • Loggr - provider for the Loggr service
  • NLog - provider for the NLog library
  • Serilog - provider for the Serilog library

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

Рекомендации по логированию

Вот некоторые полезные рекомендации для реализации логирования в ASP.NET приложениях.

  1. Используйте корректный LogLevel. Это позволит вам видеть логи в соответствии с их важностью.
  2. Используйте информацию о логах, которую легко распознать. Избегайте устаревшей или не соответствующей информации.
  3. Старайтесь быть краткими, не утаивая важную информацию.
  4. Ограничивайте методы логирования, чтобы предотвратить их дополнительные вызовы и перегрузку, особенно при выполнении критических методов.
  5. Называйте логгеры с определенным префиксом, чтобы их легко можно было отключить или отфильтровать. Помните, что Create<T> создаст логгеры, именованные полным именем класса.
  6. Аккуратно используйте Scope, и только для тех действий, которые ограничены началом и концом. Например, ограничивайте провайдеры фреймворка действиями MVC. Избегайте многократного использования scope.
  7. Логирование должно касаться бизнес-решений. Логи, касающиеся фреймворка должны быть более конкретными, нежели логи, касающиеся реализации.

Резюме

В ASP.NET Core есть встроенная поддержка логирования, которое можно настроить в классе Startup и использовать во всем приложении. Конкретность логов можно настроить глобально или с помощью провайдера логов. Встроенные провайдеры логов включены во фреймворк; а кроме того вы можете создавать и свои провайдеры логов.

Поделись хорошей новостью с друзьями!
Следи за новостями!