Создание собственного провайдера ведения журнала

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

Создание собственного провайдера ведения журнала

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

  1. Создать класс, реализующий интерфейс ILogger – этот класс используется непосредственно для записи очередного сообщения в источник (вывод в консоль, запись в журнал событий Windows и т.д.)
  2. Создать класс, реализующий интерфейс ILoggerProvider – непосредственно сам провайдер ведения журнала.
  3. Создать метод расширения для добавления нового провайдера в приложения (этот пункт не обязательный, но рекомендуемый)

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

public class FileLogger : ILogger, IDisposable
{
    private readonly object _lock = new object();
    private readonly string _category;
    private readonly string _filePath = "log.txt";

    public FileLogger(string category, string filePath)
    {
        _category = category;
        _filePath = filePath;
    }

    public IDisposable? BeginScope<TState>(TState state) where TState : notnull => default!;

    public void Dispose()
    {

    }

    public bool IsEnabled(LogLevel logLevel)
    {
        return true;
    }

    public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func<TState, Exception?, string> formatter)
    {
        if (!IsEnabled(logLevel))
        {
            return;
        }
        lock (_lock)
        {
            File.AppendAllText(_filePath, $"{_category}: {logLevel}: {eventId}: {formatter(state, exception)}{Environment.NewLine}");
        }
    }
}

Этот класс реализует интерфейс ILogger, а именно три его метода:

  1. public IDisposable? BeginScope<TState>(TState state) where TState : notnull
  2. public bool IsEnabled(LogLevel logLevel)
  3. public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func<TState, Exception?, string> formatter)

В процессе ведения журнала в вашем приложении вы можете сгруппировать набор логических операций в некоторой области. Область видимости — это объект, реализующий IDisposable и, возвращаемый вызовом метода ILogger.BeginScope<TState> метода. Для нас этот метод не важен. А вот следующие два метода – важны.

Метод IsEnabled() должен возвращать true, если сообщение с уровнем logLevel доступно для записи. В нашем случае логгер будет записывать сообщения всех уровней, хотя, вы можете определить внутри метода свою логику, например, запрещать вывод в лог сообщений с уровнем ниже определенного. Главное, чтобы этот метод выполнялся как можно быстрее так как его вызов будет использоваться в дальнейшем в методе Log(), который осуществляет непосредственную запись в журнал.

Далее, мы реализуем метод Log() интерфейса и просто выводим очередную строку в файл, используя механизм интерполяции строк в C#.

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

public FileLogger(string category, string filePath)
{
    _category = category;
    _filePath = filePath;
}

в который и передаются необходимые нам параметры. Теперь реализуем интерфейс ILoggerProvider. Для этого добавим в наш проект новый класс FileLoggerProvider

public class FileLoggerProvider : ILoggerProvider
{
    private readonly string _filePath;
    public FileLoggerProvider(string filePath)
    {
        _filePath = filePath;
    }

    public ILogger CreateLogger(string categoryName)
    {
        return new FileLogger(categoryName, _filePath);
    }

    public void Dispose()
    {
    }
}

Здесь ключевым для нас является метод CreateLogger(), который должен возвращать реализацию ILogger. В нашем случае – это объект класса FileLogger. Именно из параметров этого метода мы извлекаем категорию лога и передаем её в наш логгер. Также, для этого класса мы создали конструктор, в который передаем путь к файлу журнала.

И, наконец, последний штрих – напишем метод расширения для регистрации нашего провайдера в приложении

public static class FileLoggerExtensions
{
    public static ILoggingBuilder AddFile(this ILoggingBuilder builder, string filePath)
    {
        return builder.AddProvider(new FileLoggerProvider(filePath));
    }
}

Здесь мы вызываем метод ILoggingBuilder.AddProvider() для регистрации нового провайдера, передавая в его параметрах новый объект класса FileLoggerProvider. Сам метод расширения AddFile() в качестве параметра принимает путь к файлу журнала. Теперь нам остается только протестировать работу нашего провайдера. Для этого зарегистрируем его и запустим приложение.

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers();

builder.Logging.AddFile("Log.txt");

var app = builder.Build();

app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();

После того, как приложение будет запущено, рядом с exe-файлом появится новый файл Log.txt в котором мы найдем следующее содержимое:

Microsoft.Hosting.Lifetime: Information: ListeningOnAddress: Now listening on: https://localhost:7246
Microsoft.Hosting.Lifetime: Information: ListeningOnAddress: Now listening on: http://localhost:5231
Microsoft.Hosting.Lifetime: Information: 0: Application started. Press Ctrl+C to shut down.
Microsoft.Hosting.Lifetime: Information: 0: Hosting environment: Development
Microsoft.Hosting.Lifetime: Information: 0: Content root path: E:\YandexDisk\CSharp Sources\CSharp Book\Web API\ProjectManager\Project Manager\LogExample

Здесь стоит обратить внимание на то, как выводится идентификатор сообщения. В консоли идентификатор сообщения – это число в квадратных скобках после названия категории. В нашем же случае, так как мы использовали интерполяцию строк, то в файл выводится имя идентификатора вместо его значения, например, ListeningOnAddress. И так как с идентификаторами мы пока особо не работали, то стоит уделить немного внимания и этому моменту ведения журналов в ASP.NET Core.

Идентификаторы событий (EventId)

Идентификаторы событий позволяют нам идентифицировать то или иное событие в журнале. Так, например, идентификатор 14 в сообщении лога означает, что событие относится к прослушиванию сервером определенного адреса, а само сообщения журнала носит информационный характер (уровень Information).  Мы можем определить в приложении свои собственные идентификаторы, чтобы в дальнейшем можно было быстро отфильтровать сообщения по этому идентификатору или же использовать их в приложении, например, для формирования текста ошибок для пользователя.

Идентификатор события представлен структурой:

public readonly struct EventId : IEquatable<EventId>
{
       public int Id { get; }
       public string? Name { get; }
}

По умолчанию, имя идентификатора имеет значение null. Таким образом, мы можем создать в своем приложении собственные идентификаторы, например, следующим образом:

namespace LogExample
{
    public static class CustomEvents
    {
        public static EventId ClientError { get; } = new EventId(400, "ClientError");
        public static EventId ServerError { get; } = new EventId(400, "ServerError");
        public static EventId Info { get; } = new EventId(100, "Info");
    }
}

И, затем, использовать эти идентификаторы в приложении.

Итого

Для создания своего собственного провайдера нам потребуется выполнить три действия: создать класс, реализующий интерфейс ILogger – этот класс используется непосредственно для записи очередного сообщения в источник (вывод в консоль, запись в журнал событий Windows и т.д.), создать класс, реализующий интерфейс ILoggerProvider – непосредственно сам провайдер ведения журнала, создать метод расширения для добавления нового провайдера в приложения (этот пункт не обязательный, но рекомендуемый)

Подписаться
Уведомить о
guest
0 Комментарий
Старые
Новые Популярные
Межтекстовые Отзывы
Посмотреть все комментарии