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

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

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

Начать курс

Введение: Почему логирование — это важно?

Представьте, что ваше приложение на 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 дней

3.5. Асинхронное логирование

Блоги

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