Введение: Почему логирование — это важно?
Представьте, что ваше приложение на C# работает в продакшене. Внезапно пользователи сообщают об ошибке, но вы не можете понять, что пошло не так. Без логов вы — как пилот без приборов. Логирование — это процесс записи информации о работе приложения: ошибок, предупреждений, отладочных сообщений и ключевых событий.
В этой статье мы разберем лучшие практики логирования для начинающих разработчиков на C# (.NET). Вы узнаете, как настроить логирование, какие инструменты использовать и как избежать типичных ошибок.
1. Выбор инструмента для логирования в C#
В экосистеме .NET существует несколько популярных библиотек для логирования. Для начинающих мы рекомендуем три основных варианта:
1.1. Встроенный ILogger (Microsoft.Extensions.Logging)
Начиная с .NET Core, Microsoft предоставляет встроенный интерфейс ILogger. Он прост в использовании и не требует установки сторонних библиотек.
using Microsoft.Extensions.Logging;
public class OrderService
{
private readonly ILogger<OrderService> _logger;
public OrderService(ILogger<OrderService> logger)
{
_logger = logger;
}
public void ProcessOrder(int orderId)
{
_logger.LogInformation("Обработка заказа {OrderId}", orderId);
// Логика обработки
}
}
1.2. Serilog — гибкий и мощный
Serilog — одна из самых популярных библиотек. Она поддерживает структурированное логирование, что позволяет легко анализировать данные. Установка через NuGet: Install-Package Serilog.AspNetCore
using Serilog;
public class Program
{
public static void Main(string[] args)
{
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Debug()
.WriteTo.Console()
.WriteTo.File("logs/app.log", rollingInterval: RollingInterval.Day)
.CreateLogger();
try
{
Log.Information("Приложение запущено");
CreateHostBuilder(args).Build().Run();
}
catch (Exception ex)
{
Log.Fatal(ex, "Приложение завершилось с ошибкой");
}
finally
{
Log.CloseAndFlush();
}
}
}
1.3. NLog — классический выбор
NLog — еще одна зрелая библиотека с широкими возможностями настройки через конфигурационные файлы.
// Настройка через appsettings.json
{
"NLog": {
"targets": {
"console": {
"type": "Console",
"layout": "${longdate}|${level:uppercase=true}|${message}"
},
"file": {
"type": "File",
"fileName": "logs/app.log",
"layout": "${longdate}|${level:uppercase=true}|${message}"
}
},
"rules": [
{ "logger": "*", "minLevel": "Info", "writeTo": "console,file" }
]
}
}
2. Уровни логирования: когда что использовать
Правильное использование уровней логирования — основа хорошей практики. В C# (.NET) определены следующие уровни (от наиболее критичного к наименее):
- Trace (0) — максимально подробная отладочная информация. Используется только для глубокой диагностики.
- Debug (1) — отладочные сообщения для разработчиков. Включается в среде разработки, но отключается в продакшене.
- Information (2) — общие информационные сообщения о работе приложения (запуск, успешные операции).
- Warning (3) — предупреждения о потенциальных проблемах, которые не являются ошибками (например, превышение времени выполнения).
- Error (4) — ошибки, которые требуют внимания, но не фатальны для приложения.
- Critical (5) — критические ошибки, после которых приложение не может продолжать работу.
Пример правильного использования уровней:
public class PaymentService
{
private readonly ILogger<PaymentService> _logger;
public async Task<bool> ProcessPayment(PaymentRequest request)
{
_logger.LogInformation("Начало обработки платежа {PaymentId}", request.Id);
try
{
// Проверка баланса
if (request.Amount <= 0)
{
_logger.LogWarning("Попытка списания нулевой суммы для {PaymentId}", request.Id);
return false;
}
// Логика платежа
await _paymentGateway.ChargeAsync(request);
_logger.LogInformation("Платеж {PaymentId} успешно обработан", request.Id);
return true;
}
catch (PaymentGatewayException ex)
{
_logger.LogError(ex, "Ошибка платежного шлюза для {PaymentId}", request.Id);
return false;
}
catch (Exception ex)
{
_logger.LogCritical(ex, "Неожиданная ошибка при обработке {PaymentId}", request.Id);
throw;
}
}
}
3. Лучшие практики логирования в C#
3.1. Структурированное логирование
Вместо конкатенации строк используйте именованные плейсхолдеры. Это позволяет легко фильтровать и анализировать логи.
// Плохо
_logger.LogInformation("Пользователь " + userName + " вошел в систему");
// Хорошо
_logger.LogInformation("Пользователь {UserName} вошел в систему", userName);
3.2. Не логируйте конфиденциальные данные
Никогда не записывайте пароли, номера кредитных карт или персональные данные в логи. Используйте маскирование или исключайте их.
// Плохо
_logger.LogInformation("Пароль пользователя: {Password}", password);
// Хорошо
_logger.LogInformation("Пользователь {UserName} аутентифицирован", userName);
3.3. Используйте корреляционные ID
Для отслеживания запросов через несколько сервисов используйте уникальные идентификаторы.
public async Task HandleRequest(Guid correlationId)
{
using (_logger.BeginScope("CorrelationId: {CorrelationId}", correlationId))
{
_logger.LogInformation("Начало обработки запроса");
// Логика
_logger.LogInformation("Запрос обработан");
}
}
3.4. Настройте ротацию логов
Логи могут быстро заполнить диск. Настройте автоматическое удаление старых записей.
// Serilog с ротацией
.WriteTo.File("logs/app.log",
rollingInterval: RollingInterval.Day,
retainedFileCountLimit: 30) // Хранить 30 дней