Логирование в C# приложениях: лучшие практики на практике
Логирование — это не просто запись сообщений в файл. Это важнейший инструмент мониторинга, отладки и аудита любого production-приложения на C#. Правильно настроенное логирование позволяет быстро находить причины сбоев, анализировать поведение системы и предотвращать инциденты. В этой статье мы разберём лучшие практики логирования в .NET, которые вы сможете сразу применить в своих проектах.
1. Выбор библиотеки: Serilog vs NLog vs встроенный ILogger
В современном .NET есть три основных подхода к логированию. Рассмотрим их сильные стороны.
- Встроенный ILogger (Microsoft.Extensions.Logging) — базовый абстрактный интерфейс. Он встроен в ASP.NET Core и минимально конфигурируем. Подходит для простых проектов, но требует сторонних провайдеров для продвинутых сценариев.
- Serilog — де-факто стандарт в индустрии. Поддерживает структурированное логирование "из коробки", богатую экосистему sinks (назначений) и простую настройку через конфигурацию.
- NLog — зрелый фреймворк с гибкой маршрутизацией и поддержкой legacy-систем. Отличается высокой производительностью и детальной настройкой правил.
Рекомендация: для новых проектов используйте Serilog — он лучше всего интегрируется с современными практиками (ELK, Application Insights). Для высоконагруженных систем, где критична производительность, рассмотрите NLog.
// Пример минимальной настройки Serilog в Program.cs
using Serilog;
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Information()
.WriteTo.Console()
.WriteTo.File("logs/app-.log", rollingInterval: RollingInterval.Day)
.CreateLogger();
try
{
Log.Information("Приложение запущено");
// ваш код
}
catch (Exception ex)
{
Log.Fatal(ex, "Критическая ошибка при запуске");
}
finally
{
Log.CloseAndFlush();
}
2. Уровни логирования: когда и что писать
Неправильный выбор уровня логирования — одна из самых частых ошибок. Разберём каждый уровень и его назначение.
- Trace / Verbose — только для отладки разработчиком. Никогда не включайте в production без крайней необходимости.
- Debug — диагностическая информация для разработки. Может быть включен временно для поиска проблемы.
- Information — нормальная работа приложения: запросы, успешные операции, бизнес-события. Основной уровень в production.
- Warning — потенциальная проблема: низкий запас места, повторные попытки, устаревший API.
- Error — исключения и сбои, которые приложение обработало, но пользователь заметил проблему.
- Fatal / Critical — невосстановимые ошибки, требующие немедленного вмешательства администратора.
// Пример корректного использования уровней
public class OrderService
{
private readonly ILogger<OrderService> _logger;
public OrderService(ILogger<OrderService> logger)
{
_logger = logger;
}
public async Task<Order> CreateOrderAsync(CreateOrderRequest request)
{
_logger.LogInformation("Создание заказа для пользователя {UserId}", request.UserId);
try
{
var order = await _repository.SaveAsync(request);
_logger.LogInformation("Заказ {OrderId} успешно создан", order.Id);
return order;
}
catch (Exception ex)
{
_logger.LogError(ex, "Ошибка создания заказа для пользователя {UserId}", request.UserId);
throw;
}
}
}
3. Структурированное логирование: почему это важно
Традиционное текстовое логирование (через string.Format) убивает возможность машинного анализа. Вместо этого используйте структурированные сообщения с именованными параметрами.
Неправильно:
_logger.LogInformation($"Пользователь {userId} совершил покупку на сумму {amount}");
Правильно (Serilog/ILogger):
_logger.LogInformation("Пользователь {UserId} совершил покупку на сумму {Amount}", userId, amount);
Преимущества структурированного подхода:
- Значения
{UserId}и{Amount}сохраняются как отдельные поля в JSON. - Вы сможете фильтровать логи по конкретному
UserIdв Elasticsearch или Kibana. - Автоматическая сериализация сложных объектов — передавайте целые DTO как параметры.
// Передача объекта целиком
_logger.LogInformation("Создан заказ {@Order}", order);
// В логе появится JSON-представление объекта Order
4. Ротация логов и управление хранилищем
Логи имеют свойство заполнять весь диск, если не настроить ротацию. Вот стандартная практика для файловых логов.
// Serilog с ротацией по дням и ограничением размера
Log.Logger = new LoggerConfiguration()
.WriteTo.File(
path: "logs/app-.log",
rollingInterval: RollingInterval.Day, // новый файл каждый день
retainedFileCountLimit: 30, // хранить 30 последних файлов
fileSizeLimitBytes: 50 * 1024 * 1024, // 50 MB на файл
rollOnFileSizeLimit: true // создавать новый файл при превышении размера
)
.CreateLogger();
Дополнительные советы по управлению:
- Для облачных сред используйте Application Insights или AWS CloudWatch — не храните логи локально.
- Настройте централизованное логирование через ELK (Elasticsearch, Logstash, Kibana) или Grafana Loki.
- Никогда не логируйте пароли, токены и PII-данные (персональные данные). Используйте фильтры Serilog для маскировки.
5. Асинхронное логирование и производительность
Синхронная запись в файл или базу данных может замедлить ваше приложение. Все современные библиотеки поддерживают асинхронное логирование.
// Serilog: асинхронный sink для высокой производительности
Log.Logger = new LoggerConfiguration()
.WriteTo.Async(a