Часто в проекте возникает непонятная ошибка для которой необходимо максимальное логирование всех запросов к Базе данных. Статья поможет тем, кто пишет (развертывает на сервере) один из своих первых проектов на Asp.Net Boilerplate.
Статья написана для новичков в технологии Asp.Net Boilerplate, у которых возникла любая странная ошибка, связанная с Базой данных. При использовании PostgreSQL это может быть, например, первый проект. Мотивацией к написанию статьи стало то, что решение данного вопроса не так-то просто найти в Интернете даже на английском, не говоря о том, что найденные решения не полностью отвечают на все вопросы по данной проблеме.
Версия продукта: Asp.Net Boilerplate 4.3, .NET Core 2.1
В случае выполнения данных шагов: В вашем основном лог-файле вы увидите залогированными абсолютно все запросы к Базе данных.
Необходимо создать логгер. На платформе Boilerplate уже есть настроенный внутренний логгер. Это может быть Log4Net в стандартной комплектации. С ним не нужно производить никаких манипуляций. Вместо этого достаточно создать класс логгера, который зарегистрировать, как обработчик всех лог-сообщений от Базы данных.
Проект *.EntityFrameworkCore. Здесь нам нужно создать 2 класса. С одной стороны логгер, который будет делать только одно — выводить в системный лог все сообщения от БД. Назовем его MyLogger. И провайдер этого логгера, который будет создавать MyLogger. Провайдер назовем MyLoggerProvider.
Создаем один файл со следующим кодом (один файл для упрощения, хотя, конечно, в каждом файле должно быть по одному классу):
Если присмотреться, то видно, как в параметры MyLoggerProvider, а потом в MyLogger пробрасывается некоторый другой логгер. Получается уже третий! Суть в том, что именно этот третий это класс уровня инфраструктуры логирования, который должен быть получен из недр Boilerplate с помощью которого и будет осуществляться сохранение сообщений в журнале. См. далее.
В рамках того же проекта *.EntityFrameworkCore идем в файл *DbContextConfigurer.cs и в обоих методах Configure() делаем следующие изменения:
2.1) Добавляем параметр loggerfactory типа LoggerFactory
2.2) В тело метода добавляем две строки:
Смысл UseLoggerFactory — включить использование loggerFactory, которая передается в параметрах для логирования Базы данных. Очень важно помнить, что здесь мы включаем логирование Базы данных.
Смысл EnableSensitiveDataLogging — включить логирование не только запросов к БД, но в данных запросах еще и фиксировать все данные. Без данной настройки вы не сможете увидеть в запросах данные — они будут заменены на вопросительные знаки.
В рамках того же проекта *.EntityFrameworkCore идем в файл *DbContextFactory.cs.
3.1) Добавляем новый метод:
3.2) В методе CreateDbContext():
Т.к. мы ранее добавили в обе реализации Configure() новый параметр, то здесь должна выводиться ошибка. Настало время указать этот новый параметр — прописываем через запятую GetDbLoggerFactory(). Т.е. значение нового параметра loggerFactory должно возвращаться новым методом из п. 3.1.
В рамках того же проекта *.EntityFrameworkCore идем в файл *EntityFrameworkModule.cs.
4.1) Добавляем новый метод:
4.2) В методе PreInitialize():
Т.к. мы ранее добавили в обе реализации Configure() новый параметр, то здесь также должна выводиться ошибка. Указываем новый параметр аналогично п. 3.2 — прописываем через запятую GetDbLoggerFactory(). Т.е. значение нового параметра loggerFactory должно возвращаться новым методом из п. 4.1.
В основном лог файле (по умолчанию Logs.txt) вы увидите все запросы, предваряемые последовательностью символов «DB-REQUEST» (именно по ней и можно искать данные записи в журнале).
Итак, теперь объясню, что мы сделали. Объяснение вынесено в конец статьи, т.к. часто читателям интересно начать уже делать что-то конкретное.
В классе *DbContextFactory, а также *EntityFrameworkModule мы создаем нашу LoggerFactory, в параметрах которой указываем созданный MyLoggerProvider. Но в качестве инфраструктурного класса, который будет непосредственно осуществлять логирование в первом случае (*DbContextFactory) мы передаем заглушку NullLogger.Instance, чтобы записей не было. Во втором случае (*EntityFrameworkModule) мы передаем логгер, который уже есть в Abp-модуле. Это поле Logger. Оно уже проинициализировано и с его помощью можно логировать. Соответственно, наш MyLogger сможет делать записи в файл Logs.txt с помощью данного класса.
Вся логика заключается в том, что данная фабрика loggerFactory устанавливается в качестве лог-фабрики для процессов работы с Базой данных. Как только нужен логгер — он создается фабрикой. И это наш MyLogger, который, в свою очередь, логирует все что приходит в Logs.txt (или в тот источник, в который настроен вывод ваших основных логов).
Как видите — не все так просто и уровни абстракций иногда вымораживают, особенно новичков! Задавайте ваши вопросы в комментариях.
Примечание:
— Решение создано для того чтобы включить логгер, понять в чем ошибка и выключить. Оно не рассчитано на долговременное использование.
Статья написана для новичков в технологии Asp.Net Boilerplate, у которых возникла любая странная ошибка, связанная с Базой данных. При использовании PostgreSQL это может быть, например, первый проект. Мотивацией к написанию статьи стало то, что решение данного вопроса не так-то просто найти в Интернете даже на английском, не говоря о том, что найденные решения не полностью отвечают на все вопросы по данной проблеме.
Версия продукта: Asp.Net Boilerplate 4.3, .NET Core 2.1
В случае выполнения данных шагов: В вашем основном лог-файле вы увидите залогированными абсолютно все запросы к Базе данных.
Шаг 1
Необходимо создать логгер. На платформе Boilerplate уже есть настроенный внутренний логгер. Это может быть Log4Net в стандартной комплектации. С ним не нужно производить никаких манипуляций. Вместо этого достаточно создать класс логгера, который зарегистрировать, как обработчик всех лог-сообщений от Базы данных.
Шаг 1.1
Проект *.EntityFrameworkCore. Здесь нам нужно создать 2 класса. С одной стороны логгер, который будет делать только одно — выводить в системный лог все сообщения от БД. Назовем его MyLogger. И провайдер этого логгера, который будет создавать MyLogger. Провайдер назовем MyLoggerProvider.
Создаем один файл со следующим кодом (один файл для упрощения, хотя, конечно, в каждом файле должно быть по одному классу):
public class MyLoggerProvider : ILoggerProvider
{
private Castle.Core.Logging.ILogger _logger;
public MyLoggerProvider(Castle.Core.Logging.ILogger logger)
{
_logger = logger;
}
public ILogger CreateLogger(string categoryName)
{
return new MyLogger(_logger);
}
public void Dispose()
{
}
}
public class MyLogger : ILogger
{
private Castle.Core.Logging.ILogger _logger;
public MyLogger(Castle.Core.Logging.ILogger logger)
{
_logger = logger;
}
public IDisposable BeginScope<TState>(TState state)
{
return null;
}
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))
{
var msg = formatter(state, exception);
_logger.Info("DB-REQUEST: " + msg);
}
}
}
Если присмотреться, то видно, как в параметры MyLoggerProvider, а потом в MyLogger пробрасывается некоторый другой логгер. Получается уже третий! Суть в том, что именно этот третий это класс уровня инфраструктуры логирования, который должен быть получен из недр Boilerplate с помощью которого и будет осуществляться сохранение сообщений в журнале. См. далее.
Шаг 2
В рамках того же проекта *.EntityFrameworkCore идем в файл *DbContextConfigurer.cs и в обоих методах Configure() делаем следующие изменения:
2.1) Добавляем параметр loggerfactory типа LoggerFactory
2.2) В тело метода добавляем две строки:
builder.UseLoggerFactory(loggerFactory);
builder.EnableSensitiveDataLogging(true);
Смысл UseLoggerFactory — включить использование loggerFactory, которая передается в параметрах для логирования Базы данных. Очень важно помнить, что здесь мы включаем логирование Базы данных.
Смысл EnableSensitiveDataLogging — включить логирование не только запросов к БД, но в данных запросах еще и фиксировать все данные. Без данной настройки вы не сможете увидеть в запросах данные — они будут заменены на вопросительные знаки.
Шаг 3
В рамках того же проекта *.EntityFrameworkCore идем в файл *DbContextFactory.cs.
3.1) Добавляем новый метод:
private LoggerFactory GetDbLoggerFactory()
{
return new LoggerFactory(new[] { new MyLoggerProvider(NullLogger.Instance) });
}
3.2) В методе CreateDbContext():
Т.к. мы ранее добавили в обе реализации Configure() новый параметр, то здесь должна выводиться ошибка. Настало время указать этот новый параметр — прописываем через запятую GetDbLoggerFactory(). Т.е. значение нового параметра loggerFactory должно возвращаться новым методом из п. 3.1.
Шаг 4
В рамках того же проекта *.EntityFrameworkCore идем в файл *EntityFrameworkModule.cs.
4.1) Добавляем новый метод:
private LoggerFactory GetDbLoggerFactory()
{
return new LoggerFactory(new[] { new MyLoggerProvider(Logger) });
}
4.2) В методе PreInitialize():
Т.к. мы ранее добавили в обе реализации Configure() новый параметр, то здесь также должна выводиться ошибка. Указываем новый параметр аналогично п. 3.2 — прописываем через запятую GetDbLoggerFactory(). Т.е. значение нового параметра loggerFactory должно возвращаться новым методом из п. 4.1.
Результат
В основном лог файле (по умолчанию Logs.txt) вы увидите все запросы, предваряемые последовательностью символов «DB-REQUEST» (именно по ней и можно искать данные записи в журнале).
Общее понимание решения
Итак, теперь объясню, что мы сделали. Объяснение вынесено в конец статьи, т.к. часто читателям интересно начать уже делать что-то конкретное.
В классе *DbContextFactory, а также *EntityFrameworkModule мы создаем нашу LoggerFactory, в параметрах которой указываем созданный MyLoggerProvider. Но в качестве инфраструктурного класса, который будет непосредственно осуществлять логирование в первом случае (*DbContextFactory) мы передаем заглушку NullLogger.Instance, чтобы записей не было. Во втором случае (*EntityFrameworkModule) мы передаем логгер, который уже есть в Abp-модуле. Это поле Logger. Оно уже проинициализировано и с его помощью можно логировать. Соответственно, наш MyLogger сможет делать записи в файл Logs.txt с помощью данного класса.
Вся логика заключается в том, что данная фабрика loggerFactory устанавливается в качестве лог-фабрики для процессов работы с Базой данных. Как только нужен логгер — он создается фабрикой. И это наш MyLogger, который, в свою очередь, логирует все что приходит в Logs.txt (или в тот источник, в который настроен вывод ваших основных логов).
Как видите — не все так просто и уровни абстракций иногда вымораживают, особенно новичков! Задавайте ваши вопросы в комментариях.
Примечание:
— Решение создано для того чтобы включить логгер, понять в чем ошибка и выключить. Оно не рассчитано на долговременное использование.