Логирование в C# приложениях: лучшие практики на практике

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Логирование в 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                

Блоги

Рекомендаций по книгам