Подключаем Kibana для хранения логов ASP.NET Core

Логи — полезные инструмент для отладки и мониторинга приложений. Только просмотр текстовых логов — дело скучное и неинтересное. Поэтому изучают логи в исключительных ситуациях. Здесь на помощь приходят структурированные логи — когда записи хранятся не просто в виде текста, а в виде структурированных данных с разделением информации на отдельные поля. По таким логам можно настроить фильтры, поиск, при соблюдении определенных условий — отправлять уведомления и т.д.

Инфраструктура логгирования ASP.NET Core поддерживает структурированные логи. Посмотрим как это можно использовать на примере ELK.

Elasticsearch и Kibana

Один из вариантов хранения структурированных логов — хранение их в Elasticsearch. Преимущество этого подхода заключается в том, что для этого всё уже готово — вам не нужно допиливать код для хранения данных.

ELK — это стек из трех продуктов: Elasticsearch, Logstash и Kibana:

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

Чтобы попробовать ELK можно использовать Docker-образ sebp/elk:

docker pull sebp/elk
docker run -p 5601:5601 -p 9200:9200 -p 5044:5044 -it --name elk sebp/elk

Подробнее о том, как запустить ELK внутри Docker можно почитать в документации.

После запуска Kibana будет доступна через порт 5601, а Elasticsearch API — через 9200.

Для реального проекта Elasticsearch весьма требователен к оборудванию (в частности - к RAM). Поэтому хорошим вариантом может быть использование ELK как сервиса. К счастью, таких сервисов очень много, например — раз, два, три.

Отправка в данных в Elasticsearch

Если для отправки данных используется Logstash, то всё сводится к записи логов в файл и настройки Logstash для чтения этих файлов и отправки в Elasticsearch. Альтернативным способом может быть отправка данных напрямую в Elasticsearch, минуя Logstash. Рассмотрим этот способ.

Стоит отметить, что в данном случае нам потребуется какая-нибудь библиотека для работы с Elasticsearch. Желательно также, чтобы эта библиотека была интегрирована с инфраструктурой логгирования ASP.NET Core. С учетом этого, Serilog может быть хорошим выбором: она интегрирована с ASP.NET Core и имеет провайдер для отправки логов в Elastisearch.

Установим в проект пакет Serilog, а заодно Serilog.Extensions.Logging и Serilog.Sinks.Elasticsearch.

Теперь необходимо добавить Serilog-логгер в DI-контейнер:

public class Startup
{
  public void ConfigureServices(IServiceCollection services)
  {
    services.AddSingleton<Serilog.Core.Logger>(container => new LoggerConfiguration()
      .ConfigureEnrichers(container)
      .ConfigureElk("http://elk:9200", Configuration.GetSection("Logging:Elk"), container)
      .ConfigureSelfLog(container)
      .CreateLogger());
  }

И в список логгинг-провайдеров:

  public void Configure(IApplicationBuilder app, IHostingEnvironment env,
    ILoggerFactory loggerFactory)
  {
    loggerFactory.AddSerilog(services.GetService<Serilog.Core.Logger>());
  }
}

Обратите внимание, что при конфигурировании ELK указана конфигурационная секция Logging:Elk. В ней мы указываем параметры фильтров, о которых я писал ранее.

Для данного примера конфигурационный файл может выглядеть так:

{
  "Logging": {
    "Console": {
      "IncludeScopes": false,
      "LogLevel": {
        "Default": "Debug",
        "System": "Information",
        "Microsoft": "Information"
      }
    },
    "Elk": {
      "IncludeScopes": false,
      "LogLevel": {
        "MyApp.Services": "Information",
        "MyApp.Infrastructure": "Information"
      }
    }
  }
}

Теперь можно воспользоваться стандартным API для логгирования и создавать записи в в ELK:

public class HomeController : Controller
{
  private readonly ILogger _logger;

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

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

Однако, гораздо интереснее в таком случае писать в лог структурированную информацию. Для этого немного изменим способ логгирования:

public class HomeController : Controller
{
  private readonly ILogger _logger;

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

  public IActionResult Index()
  {
    var request = ...;
    var config = ...;
    _logger.LogError("Some error [email protected][email protected]}",
      User.Identity.Name, request, config);
    return View();
  }
}

Теперь данные отправляются в ELK в структурированном виде.

Если для каждой записи в лог требуется сохранять какие-то стандартные данные (например, данные HTTP-запроса, окружения и т.д.) можно воспользоваться специальными объектами Serilog — Enricher'ами. О том, как их использовать можно почитать в документации.