6. Кэширование и логирование¶
Аналитические запросы часто выполняются над большим количеством записей. Если результат нужен многим пользователям, его можно временно хранить в памяти.
Кэширование и логирование решают разные задачи, но обе важны для производственного качества.
Кэширование ускоряет повторные чтения. Например, если десять аналитиков за минуту открыли одну и ту же сводку за неделю, нет смысла десять раз пересчитывать одинаковые агрегаты по базе.
Логирование помогает понять, что происходило в приложении. Когда пользователь говорит “отчёт не сформировался”, разработчик смотрит логи и видит ошибку, время, параметры и место сбоя.
MemoryCache¶
Пакет обычно уже доступен в ASP.NET Core. Регистрация:
Эта строка включает встроенный in-memory cache в DI-контейнере. После регистрации сервисы смогут получать IMemoryCache через конструктор и хранить временные результаты расчётов.
Пример сервиса аналитики показывает, где кэш полезен на практике. Сводка за один и тот же период может запрашиваться много раз, поэтому результат агрегирующего запроса сохраняется на короткое время.
public class AnalyticsService
{
private readonly AppDbContext _db;
private readonly IMemoryCache _cache;
public AnalyticsService(AppDbContext db, IMemoryCache cache)
{
_db = db;
_cache = cache;
}
public async Task<AnalyticsSummaryDto> GetSummaryAsync(DateTime from, DateTime to, CancellationToken ct)
{
var cacheKey = $"analytics-summary:{from:yyyyMMddHH}:{to:yyyyMMddHH}";
return await _cache.GetOrCreateAsync(cacheKey, async entry =>
{
entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(2);
var events = _db.ActivityEvents.Where(x => x.CreatedAt >= from && x.CreatedAt <= to);
return new AnalyticsSummaryDto(
TotalEvents: await events.CountAsync(ct),
UniqueUsers: await events.Select(x => x.UserId).Distinct().CountAsync(ct),
Errors: await events.CountAsync(x => x.Severity == "Error", ct));
}) ?? new AnalyticsSummaryDto(0, 0, 0);
}
}
Когда кэш сбрасывать¶
Если появилось новое событие, старый результат аналитики может устареть. В учебной версии достаточно короткого TTL 2 минуты. В продвинутой версии можно удалять ключи кэша после создания события.
TTL — это время жизни записи в кэше. Маленький TTL даёт более свежие данные, но чаще обращается к базе. Большой TTL сильнее ускоряет приложение, но повышает риск показать устаревшую аналитику.
Логирование ASP.NET Core¶
ASP.NET Core уже имеет встроенный ILogger<T>.
В сервисе событий лог пишется вокруг ключевой бизнес-операции. Это помогает отличать успешное создание события от ошибки, которая произошла во время обработки запроса.
public class ActivityEventService : IActivityEventService
{
private readonly ILogger<ActivityEventService> _logger;
public ActivityEventService(ILogger<ActivityEventService> logger)
{
_logger = logger;
}
public async Task<ActivityEventDto> CreateAsync(CreateActivityEventRequest request, int userId, CancellationToken ct)
{
_logger.LogInformation("Creating activity event {Type} for user {UserId}", request.Type, userId);
try
{
...
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to create activity event for user {UserId}", userId);
throw;
}
}
}
Настройка уровней логов¶
Конфигурация уровней нужна, чтобы в разработке видеть важные сообщения приложения и при этом не утонуть в техническом шуме фреймворка.
appsettings.Development.json:
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning",
"Microsoft.EntityFrameworkCore.Database.Command": "Information"
}
}
}
Для практики достаточно консольных логов. Для реального проекта можно добавить Serilog и запись в файл или внешнюю систему мониторинга.
Что логировать¶
Логировать нужно не каждую строку кода, а важные события:
- вход пользователя;
- создание события активности;
- ошибки сохранения в базу;
- запуск генератора нагрузки;
- формирование отчёта;
- отказ в доступе к важной операции.
Не записывайте в логи пароли, JWT-токены и персональные данные без необходимости.