Логгирование в ASP.NET Core

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

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

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

Прежде чем начать писать что-то в логи, нужно настроить провайдеров логов. Для этого в метод Configure() класса Startup инжектируется сервис ILoggerFactory. Используя этот сервис можно настроить провайдеров:

public class Startup
{
  public void ConfigureServices(IServiceCollection services)
  {
  }

  public void Configure(IApplicationBuilder app, IHostingEnvironment env,
    ILoggerFactory loggerFactory)
  {
    loggerFactory.AddDebug();

    // ...
  }
}

В данном примере добавялется провайдер, который будет писать логи в консоль отладчика. Чтобы добавить этого провайдера, нужно установить пакет Microsoft.Extensions.Logging.Debug. Впрочем, он уже итак установлен в проекте, который создается в базовом шаблоне проекта ASP.NET MVC.

Аналогичным образом добавляются и другие провайдеры. Microsoft предоставляет такие провайдеры как Microsoft.Extensions.Logging.Console, Microsoft.Extensions.Logging.Debug, Microsoft.Extensions.Logging.TraceSource, Microsoft.Extensions.Logging.AzureAppServices, Microsoft.Extensions.Logging.EventSource, Microsoft.Extensions.Logging.EventLog. Как нетрудно догадаться из названия, каждый из них имеет собственный источник для хранения логов.

Можно использовать несколько провайдеров одновременно:

public class Startup
{
  public void Configure(IApplicationBuilder app, IHostingEnvironment env,
    ILoggerFactory loggerFactory)
  {
    loggerFactory.AddDebug();
    loggerFactory.AddConsole();

    // ...
  }
}

Запись в лог

Для того, чтобы начать писать что-то в лог, следует инжектировать интерфейс ILogger в нужный компонент приложения. Причем в качестве generic-параметра мы можем указать категорию логгера. Например, получим доступ к логгеру в компоненте HomeController:

public class HomeController : Controller
{
  private readonly ILogger _logger;

  public HomeController(ILogger<HomeController> logger)
  {
    _logger = logger;
  }

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

  • LogDebug()
  • LogTrace()
  • LogInformation()
  • LogWarning()
  • LogError()
  • LogCritical()

Попробуем записать что-нибудь в лог:

public IActionResult Index()
{
  _logger.LogError("Some error occured");
  return View();
}

Теперь при срабатывании этого действия в консоли отладчика обязательно появится запись:

WebApplication8.Controllers.HomeController:Error: Some error occured

Если при настройке провайдеров мы настроили также вывод в консоль (AddConsole()), то эта же запись появится и в консоли (если мы запускаем приложение через Kestrel и консоль нам видна).

Записывать в лог можно не только обычные строки, но и структурированные данные. Для этого существуют специальные перегрузки методов LogXXX(). Запишем, например, в лог данные с идентификатором объекта:

public IActionResult Index()
{
  _logger.LogError("Unable to delete object with {ID} key", 25);
  return View();
}

В логе появится строка следующего содержания:

WebApplication8.Controllers.HomeController:Error: Unable to delete object with 25 key

Если провайдер поддерживает структурированные данные, то ID будет доступно как отдельное поле. Более того, поле может быть не только простым типом, но и составным объектом. Но это — отдельная тема и здесь касаться этого не будем.

Scope

Иногда бывает полезно объединить набор сообщений лога в группы. Это можно сделать используя метод BeginScope(), реализующий IDisposable. Поэтому можно использовать конструкцию using для группировки сообщений в логе:

public IActionResult Index()
{
  using (_logger.BeginScope("Updating object {ID}", 25))
  {
    _logger.LogInformation("Checking object state");

    // ...

    _logger.LogInformation("Updating object reference");

    // ...

    _logger.LogError("Something went wrong");
  }

  return View();
}

Не все провайдеры поддерживают такую группировку. Однако, те, кто поддерживают будут иметь пометку Updating object {ID} для каждой записи внутри scope.

Например, группировку поддерживает Microsoft.Extensions.Logging.Console. Для её использования это нужно указать явно при настройке провайдера:

public class Startup
{
  public void Configure(IApplicationBuilder app, IHostingEnvironment env,
    ILoggerFactory loggerFactory)
  {
    loggerFactory.AddConsole(includeScopes: true);

    // ...
  }
}

В таком случае для каждого сообщения внутри scope будет добавлена строка Updating object {ID}:

Фильтрация логов

В логи обычно записывается большой объем отладочной информации. Например, инфраструктура ASP.NET записывает туда информацию о каждом обработанном HTTP-запросе. Это может быть полезно во время отладки, но когда приложение работает на боевом сервере, эта информация не нужна. Более того, в большом потоке отладочной информации можно не увидеть действительно ценного — сообщения об ошибках или каких-либо нестандартных ситуациях. Поэтому для разных окружений настройки логгирования можно настроить по-разному.

Все записи в логах разделяются по степени важности. Например, методы LogDebug() и LogInformation() записывают событие с какими-то отладочными данными. Их смысл в том, чтобы дать представление о процессах, которые происходят в приложении, но не более. С другой стороны LogWarning(), говорит о том, что в приложении произошло что-то, на что было бы неплохо обратить внимание. Наконец, LogError() или LogCritical() записывает информацию об ошибках — т.е. приложение повело себя не так, как планировалось, и на такие записи обязательно нужно обращать внимание.

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

Это поведение задается при помощи специального параметра:

public class Startup
{
  public void Configure(IApplicationBuilder app, IHostingEnvironment env,
    ILoggerFactory loggerFactory)
  {
    loggerFactory.AddDebug(minLevel: LogLevel.Error);
    loggerFactory.AddConsole(minLevel: LogLevel.Warning);

    // ...
  }
}

В этом случае всё, что менее критично, чем Error или Warning будет игнорироваться для этих провайдеров.

Но не все категории записей в логе одинаково полезны. Например, в каких-то ситуациях нам могут быть неинтересны ошибки, которые пишутся в лог инфраструктурой ASP.NET, даже если для них задан уровень Error, но при этом очень важны записи, сделанные нашими собственными сервисами, причем для менее приоритетных уровней.

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

Вот как например можно настроить провайдер для записи в консоль:

public class Startup
{
  public void Configure(IApplicationBuilder app, IHostingEnvironment env,
    ILoggerFactory loggerFactory)
  {
    loggerFactory.AddConsole((category, level) =>
    {
      if (category.Contains("MyApp") && level >= LogLevel.Information)
        return true;
      else if (category.Contains("Microsoft") && level >= LogLevel.Error)
        return true;

      return false;
    });

    // ...
  }
}

Другой способ — использовать конфигурационный файл для настройки уровней логгирования:

public class Startup
{
  public void Configure(IApplicationBuilder app, IHostingEnvironment env,
    ILoggerFactory loggerFactory)
  {
    loggerFactory.AddConsole(Configuration.GetSection("Logging"));

    // ...
  }
}

Конфигурационный файл в этом случае может выглядеть так:

{
  "Logging": {
    "IncludeScopes": false,
    "LogLevel": {
      "Default": "Warning",
      "Microsoft": "Error",
      "MyApp": "Information"
    }
  }
}

Ещё один удобный способ настроить фильтры — это использовать пакет Microsoft.Extensions.Logging.Filter. После установки этого пакета для ILoggerFactory, появится метод-расширение WithFilter(), позволяющий настроить фильтры:

public class Startup
{
  public void Configure(IApplicationBuilder app, IHostingEnvironment env,
    ILoggerFactory loggerFactory)
  {
    loggerFactory
      .WithFilter(new FilterLoggerSettings
      {
        { "Microsoft", LogLevel.Error },
        { "MyApp", LogLevel.Information }
      })
      .AddConsole()
      .AddDebug();

    // ...
  }
}

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

Существующие фреймворки

Добавленный в ASP.NET Core встроенный мехнизм логгрования отлично интегрируется с большиством известных фреймворков. Если вы уже используете в приложение какой-то существующий фреймворк — не нужно удалять его из проекта. Скорее всего для него уже создан провайдер для интеграции с ASP.NET Core. Вот провайдеры для некоторых популярных фреймворков: