Что такое логирование в разработке ПО?

Ответ

Логирование — это систематическая запись событий, сообщений и данных о работе приложения во время его выполнения. Это критически важный инструмент для наблюдения за поведением системы в production, отладки проблем и аудита действий.

Зачем нужно логирование?

  1. Отладка в production: Воспроизвести баг на продакшене часто невозможно. Логи — это "черный ящик" приложения.
  2. Мониторинг: Понимание состояния системы, выявление аномалий и медленных операций.
  3. Аудит: Отслеживание действий пользователей (кто, что и когда сделал).
  4. Анализ производительности: Поиск узких мест с помощью замеров времени выполнения операций.
  5. Сбор статистики.

Уровни логирования (от наиболее детального к наиболее критичному): Обычно соответствуют стандарту Microsoft.Extensions.Logging.LogLevel.

Уровень Когда использовать Пример
Trace Детальная отладочная информация, шаги алгоритма. Entering method Calculate with param: {id}
Debug Информация для отладки во время разработки. Cache miss for key: {key}
Information Отслеживание общего потока работы приложения. Request {requestId} started for user {userId}
Warning Неожиданное, но некритичное событие. Failed to connect to backup service, using primary.
Error Ошибка, которая не позволила выполнить конкретную операцию. Failed to process order {orderId}.
Critical Критический сбой, требующий немедленного вмешательства. Database is unavailable.

Практический пример с Serilog и Structured Logging:

// Настройка (часто в Program.cs)
Log.Logger = new LoggerConfiguration()
    .MinimumLevel.Information()
    .Enrich.FromLogContext() // Для добавления контекста
    .WriteTo.Console(outputTemplate: "[{Timestamp:HH:mm:ss} {Level:u3}] {Message:lj}{NewLine}{Exception}")
    .WriteTo.File("logs/app-.txt", rollingInterval: RollingInterval.Day) // Ротация по дням
    .WriteTo.Seq("http://localhost:5341") // Отправка в централизованное хранилище Seq
    .CreateLogger();

try
{
    Log.Information("Starting web host...");
    CreateHostBuilder(args).Build().Run();
}
catch (Exception ex)
{
    Log.Fatal(ex, "Host terminated unexpectedly");
}
finally
{
    Log.CloseAndFlush(); // Важно для буферизованных приемников
}

// Использование в сервисе с structured logging
public class OrderService
{
    private readonly ILogger<OrderService> _logger;

    public void ProcessOrder(Order order)
    {
        // Используйте именованные placeholders для структурированного логирования
        _logger.LogInformation("Processing order {OrderId} for user {UserId}",
            order.Id, order.UserId);

        try
        {
            // Бизнес-логика
        }
        catch (PaymentException ex)
        {
            // Всегда передавайте исключение как отдельный параметр
            _logger.LogError(ex, "Payment failed for order {OrderId}", order.Id);
            throw;
        }
    }
}

Best Practices:

  • Избегайте логирования в цикле на высоких уровнях (Info, Warn, Error), чтобы не "завалить" логи.
  • Не логируйте конфиденциальные данные (пароли, токены, ПД).
  • Используйте разные приемники (sinks): Console для разработки, файл/база данных/специализированные системы (ELK Stack, Seq, Application Insights) для production.
  • Контекст — это ключ: Всегда добавляйте идентификаторы запросов (requestId), ID пользователей, сущностей. Это позволяет связать все логи, относящиеся к одной операции.
  • Настройте уровни логирования через конфигурацию, чтобы включать детальное логирование (Debug) только при необходимости, не перезаписывая код.

Ответ 18+ 🔞

Давай разберёмся с этим вашим логированием, а то народ как обезьяна с гранатой — пишет в консоль Console.WriteLine и думает, что он архитектор.

Представь, твоё приложение — это как мужик в бане после трёх бутылок пива. Оно может внезапно грохнуться, начать нести хуйню или просто молча обоссаться в углу. Логи — это как видеокамеры в этой бане. Без них ты никогда не поймёшь, кто, когда и на каком уроне наступил на мыло.

Зачем это, блядь, нужно?

  1. Отладка на проде. Ты ж не будешь к клиенту на сервер с отладчиком подключаться? Вот и я о том. Логи — это твои глаза и уши там, куда тебе доступ закрыт.
  2. Мониторинг. Чтобы понять, что система не просто "тормозит", а конкретно, сука, запрос к этой ебучей платежке в 300 миллисекунд упирается.
  3. Аудит. Чтобы когда придет менеджер с вопросом "а кто это заказ №666 удалил?", ты мог не пиздеть, а показать строчку в логах.
  4. Производительность. Найти узкое место — это как понять, кто в бане последний пиво допил. Без замеров — нихуя не ясно.

Уровни, или "Насколько всё плохо?" Смотри, как в больнице: есть "сопливит нос", а есть "клиническая смерть". Так и тут.

Уровень Для чего Как в жизни
Trace Совсем мелкая отладочная хуйня. Шаги алгоритма. "Сделал шаг левой ногой, потом правой". В проде это обычно выключено, ибо овердохуища текста.
Debug Инфа для разработчика. "Кэш промахнулся по ключу user_228". На проде включаем, только когда всё ебнулось и надо копать.
Information Нормальная работа приложения. "Пользователь Vasya зашёл в систему". Это чтобы видеть, что оно живое.
Warning Странно, но не смертельно. "Не удалось отправить статистику, но работаем дальше". Как предупреждение "осторожно, двери закрываются".
Error Конкретная операция не удалась. "Не смог сохранить заказ в БД". Вот тут уже надо внимание обращать.
Critical Всё, пиздец, система или часть умерла. "База данных не отвечает". Поднимай всех, даже тех, кто в отпуске.

Как это выглядит в коде, на примере Serilog Не будем как лохи писать строки-склеенки. Структурированное логирование — это когда ты не просто текст пишешь, а данные отдельно. Потом их можно искать, как в базе.

// Настраиваем это безобразие где-то в начале программы (Program.cs)
Log.Logger = new LoggerConfiguration()
    .MinimumLevel.Information() // Всё ниже Info (Debug, Trace) игнорируем
    .Enrich.FromLogContext() // Магия, чтобы добавлять контекст (типа requestId)
    .WriteTo.Console(outputTemplate: "[{Timestamp:HH:mm:ss} {Level:u3}] {Message:lj}{NewLine}{Exception}") // В консольку для красоты
    .WriteTo.File("logs/app-.txt", rollingInterval: RollingInterval.Day) // Пишем в файлы, новый каждый день, чтобы не выебать диск одним файлом
    .WriteTo.Seq("http://localhost:5341") // Кидаем в Seq (отдельная тема, офигенная штука для просмотра логов)
    .CreateLogger();

try
{
    Log.Information("Запускаем хост... Поехали!");
    CreateHostBuilder(args).Build().Run();
}
catch (Exception ex)
{
    // Если всё упало на старте — пишем самое страшное
    Log.Fatal(ex, "Хост неожиданно сдох. Совсем.");
}
finally
{
    // Всегда смываем за собой, иначе последние логи могут потеряться в буфере
    Log.CloseAndFlush();
}

// А вот как этим пользоваться в сервисе
public class OrderService
{
    private readonly ILogger<OrderService> _logger; // Инжектим

    public void ProcessOrder(Order order)
    {
        // ВАЖНО: используем именованные параметры, а не интерполяцию строк!
        // Это позволит искать потом все логи по OrderId, например.
        _logger.LogInformation("Обрабатываем заказ {OrderId} для пользователя {UserId}",
            order.Id, order.UserId); // Смотри, данные отдельно

        try
        {
            // ... тут твоя бизнес-логика, которая может ебнуться ...
        }
        catch (PaymentException ex)
        {
            // Всегда передавай само исключение первым параметром в LogError/Critical!
            // Иначе стектрейс потеряется, и ты будешь как слепой котёнок.
            _logger.LogError(ex, "Платеж провалился для заказа {OrderId}", order.Id);
            throw; // Пробрасываем дальше, логирование — не обработка ошибки!
        }
    }
}

Главные правила, чтобы не быть мудаком:

  • Не логируй в циклах на высоких уровнях. Представь, что ты логируешь Information на каждую итерацию в цикле на 100000 элементов. Твои логи съедят всё место на диске быстрее, чем толпа хачей на фуршете.
  • Никаких паролей, токенов и персональных данных в логах. Это не просто плохо, это пиздец как опасно. Тебя потом по GDPR ебут так, что мало не покажется.
  • Контекст — твой лучший друг. requestId, userId, orderId — вот это всё. Без этого логи это как рассказ алкоголика: вроде и события были, но связать их нихуя не возможно.
  • Уровни настраивай через конфиг. Чтобы не пересобирать проект, когда на проде всё полетело к хуям и нужно включить Debug. Вынес в appsettings.json и поменял на лету.
  • Логирование — не замена исключениям. Записали ошибку в лог — и что? Система должна знать, как её обработать. Лог — для тебя, исключение — для программы.