4.14. Настройка логирования
Настройка логирования
Что такое логирование?
Логирование — это систематическая запись событий, происходящих в программе, с целью отслеживания её поведения, диагностики ошибок и анализа производительности. Эффективное логирование помогает разработчикам понимать, что происходит внутри приложения без необходимости запускать отладчик или воспроизводить проблему вручную.
Представим, что у вас есть простой скрипт с несколькими строками кода. Вы объявляете переменные, вызываете функции, но в какой-то момент хотите понимать, какие значения у переменных, каково состояние процесса. Без глубокого исследования и отладки, простейший способ - записать информацию.
А если речь идёт о большом приложении, то когда что-то пойдёт не так, и придётся разбираться, вам, как разработчику, понадобится набор каких-то данных, доказательств того, что сломалось. Да, система "не работает", но нужно ведь понять, в какой именно части кода, в каком именно модуле возникла ошибка - и именно для этого используется логирование. Чтобы записать информацию подробно и в дальнейшем получить возможность быстро найти пробелы.
Как реализуют логирование
Логи могут сохраняться различными способами в зависимости от требований проекта, окружения и уровня критичности данных:
- Вывод в консоль — самый простой способ, используемый в терминале (для серверных приложений) или в консоли браузера (для клиентских JavaScript-приложений). Подходит для разработки и временного отладочного вывода.
- Отображение на интерфейсе — используется в десктопных или мобильных приложениях, где логи показываются в специальной панели или окне. Применяется редко, только в отладочных сборках.
- Сохранение в файл — стандартный подход для продакшн-систем. Логи пишутся в текстовые файлы (
.txt,.log) или структурированные форматы (JSON,CSV), что позволяет легко их обрабатывать и анализировать. - Отправка на сервер — централизованное логирование, когда данные отправляются на удалённый сервер (например, через HTTP, syslog или специализированные протоколы). Используется в распределённых системах.
- Сохранение в базу данных — применяется, когда требуется быстрый поиск, фильтрация или долгосрочное хранение. Может быть как в реляционной (SQL), так и в документной (NoSQL) базе.
Алгоритмическим языком можно представить такой пример:
алг Пример_логирования
нач
вызов ИнициализироватьЛог("приложение.log")
вызов ЗаписатьЛог("INFO", "Приложение запущено")
если ОшибкаПроизошла то
вызов ЗаписатьЛог("ERROR", "Произошла ошибка при обработке данных")
все
вызов ЗаписатьЛог("INFO", "Приложение завершает работу")
кон
Здесь основной алгоритм - инициализируется лог, определяется место сохранения, и конечно вызывается функция записи. В такую функцию передают текст, который нужно записать. В реальных языках программирования (Python, C#, Java, JavaScript) логирование реализуется через библиотеки или встроенные средства.
В результате, в зависимости от способа, мы получим лог в соответствующем формате:
Вывод в консоли
2026-03-15T14:23:01.123Z [INFO] UserService: Пользователь вошёл в систему. userId=12345, ip=192.168.1.100
2026-03-15T14:23:05.456Z [WARN] PaymentService: Таймаут при подключении к шлюзу. gateway=stripe, attempt=1
2026-03-15T14:23:07.789Z [ERROR] OrderService: Не удалось создать заказ. orderId=null, reason=InvalidPaymentToken
Особенности:
- Каждая запись — одна строка.
- Уровень выделен (
[INFO],[WARN],[ERROR]). - Имя компонента (
UserService) помогает быстро определить источник. - Контекст передаётся как пары
ключ=значение— это позволяет легко парсить.
Файл .log или .txt
Лог-файл может быть как в plain text, так и в JSON. Вот оба варианта.
2026-03-15 14:23:01,123 | INFO | UserService | Пользователь вошёл в систему. userId=12345, ip=192.168.1.100
2026-03-15 14:23:05,456 | WARN | PaymentService | Таймаут при подключении к шлюзу. gateway=stripe, attempt=1
2026-03-15 14:23:07,789 | ERROR | OrderService | Не удалось создать заказ. orderId=null, reason=InvalidPaymentToken
2026-03-15 14:23:10,012 | DEBUG | CacheService | Кэш обновлён. key=user:12345, size=2.1KB
{"timestamp":"2026-03-15T14:23:01.123Z","level":"INFO","logger":"UserService","message":"Пользователь вошёл в систему","userId":12345,"ip":"192.168.1.100"}
{"timestamp":"2026-03-15T14:23:05.456Z","level":"WARN","logger":"PaymentService","message":"Таймаут при подключении к шлюзу","gateway":"stripe","attempt":1}
{"timestamp":"2026-03-15T14:23:07.789Z","level":"ERROR","logger":"OrderService","message":"Не удалось создать заказ","orderId":null,"reason":"InvalidPaymentToken"}
{"timestamp":"2026-03-15T14:23:10.012Z","level":"DEBUG","logger":"CacheService","message":"Кэш обновлён","key":"user:12345","sizeBytes":2150}
Таблица с записями логов
Если логи хранятся в реляционной базе данных (например, PostgreSQL, MySQL), они могут быть представлены в виде таблицы. Структура типична:
| id | timestamp | level | logger | message | user_id | ip | trace_id |
|---|---|---|---|---|---|---|---|
| 1 | 2026-03-15 14:23:01.123 | INFO | UserService | Пользователь вошёл в систему | 12345 | 192.168.1.100 | a1b2c3d4-e5f6-7890-g1h2... |
| 2 | 2026-03-15 14:23:05.456 | WARN | PaymentService | Таймаут при подключении к шлюзу | NULL | NULL | a1b2c3d4-e5f6-7890-g1h2... |
| 3 | 2026-03-15 14:23:07.789 | ERROR | OrderService | Не удалось создать заказ | 12345 | 192.168.1.100 | a1b2c3d4-e5f6-7890-g1h2... |
| 4 | 2026-03-15 14:23:10.012 | DEBUG | CacheService | Кэш обновлён | 12345 | NULL | b2c3d4e5-f6g7-8901-h2i3... |
Пояснение полей:
id— уникальный идентификатор записи.timestamp— точное время события.level— уровень важности.logger— имя модуля или класса.message— читаемое описание.user_id,ip— контекстные данные (могут быть NULL).trace_id— идентификатор цепочки запросов (для распределённого трейсинга).
Такой формат позволяет выполнять SQL-запросы:
SELECT * FROM logs
WHERE level = 'ERROR'
AND timestamp > '2026-03-15 14:00:00'
ORDER BY timestamp DESC;
Как подключать и настраивать эти виды логирования
Подключение логирования начинается с выбора подходящего механизма записи и его конфигурации. В большинстве языков используются встроенные или сторонние библиотеки, которые позволяют гибко настраивать:
- уровень логирования (debug, info, warning, error, critical);
- формат сообщения;
- место назначения (файл, консоль, сеть и т.д.);
- ротацию файлов (автоматическое создание новых файлов по размеру или времени).
Например, в Python через logging.basicConfig() можно задать всё сразу. В C# часто используется ILogger из .NET с провайдерами (console, file, etc.). В JavaScript — либо console.log(), либо библиотеки вроде winston или pino.
Распространённые практики
- Использование уровней логирования — не все события одинаково важны. Debug-сообщения нужны только при разработке, а error/critical — всегда.
- Не логировать чувствительные данные — пароли, токены, персональные данные не должны попадать в логи.
- Структурированный формат — JSON предпочтительнее обычного текста, так как его проще парсить и анализировать автоматически.
- Контекст в логах — каждое сообщение должно содержать достаточно информации: время, модуль, пользователь (если применимо), идентификатор запроса.
- Ротация и архивация — логи не должны занимать всё дисковое пространство. Используются политики ротации и сжатия старых файлов.
- Централизованное хранение — в микросервисных архитектурах логи всех сервисов собираются в одном месте (например, через ELK-стек или Loki+Grafana).
Алгоритм подключения логирования
- Выбор библиотеки или встроенного средства — определить, что использовать: встроенное API или стороннюю библиотеку.
- Определение уровней логирования — решить, какие уровни будут активны в разных окружениях (dev, test, prod).
- Настройка формата сообщений — установить шаблон: время, уровень, имя модуля, сообщение.
- Выбор места назначения — консоль, файл, база данных, внешний сервис.
- Добавление контекста — обеспечить передачу идентификаторов сессий, пользователей, запросов.
- Тестирование — убедиться, что логи пишутся корректно и содержат нужную информацию.
- Настройка ротации и мониторинга — автоматизировать управление объёмом логов и настроить алерты на критические ошибки.
Первое решение — использовать ли стандартные средства языка или стороннюю библиотеку. Встроенные средства обычно просты и не требуют зависимостей, но ограничены в функциональности. Сторонние библиотеки предоставляют гибкость, структурированный вывод, поддержку разных форматов и интеграцию с внешними системами.
- Python: встроенная библиотека
loggingдостаточно мощна для большинства задач. Для продвинутого использования —structlog,loguru. - JavaScript (Node.js):
consoleподходит для прототипов, но для продакшена лучшеwinston,pino,bunyan. - C# (.NET):
ILogger<T>из Microsoft.Extensions.Logging — стандарт де-факто. Часто используется вместе сSerilogилиNLogдля расширенных возможностей. - Java:
java.util.logging— встроенный, но редко используется. Предпочтительны SLF4J + Logback или Log4j2.
Уровни логирования позволяют фильтровать сообщения по важности. Стандартные уровни (в порядке возрастания серьёзности):
- DEBUG — детальная информация, интересная только при диагностике.
- INFO — общие события жизненного цикла (запуск, завершение, успешные операции).
- WARNING / WARN — потенциально проблемные ситуации, не приводящие к сбою.
- ERROR — ошибка, повлиявшая на выполнение операции, но не упавшая система.
- CRITICAL / FATAL — критическая ошибка, требующая немедленного вмешательства.
Настройка уровней зависит от окружения:
- Разработка (dev):
DEBUG— все сообщения. - Тестирование (test):
INFOилиWARN— чтобы видеть основные события без шума. - Продакшен (prod):
WARNилиERROR— только значимые события, чтобы не перегружать диск и не раскрывать внутренности системы.
В конфигурационных файлах (например, appsettings.json в .NET или logback.xml в Java) уровни задаются явно для каждого компонента или глобально.
Формат определяет, как выглядит каждая запись. Хороший формат включает:
- временная метка (ISO 8601:
2026-03-15T14:23:01.123Z); - уровень (
INFO,ERROR); - имя модуля/класса/файла;
- идентификатор запроса или корреляции (trace ID);
- само сообщение;
- дополнительные поля (пользователь, IP, версия ПО).
Пример структурированного JSON-лога:
{
"timestamp": "2026-03-15T14:23:01.123Z",
"level": "INFO",
"logger": "UserService",
"message": "Пользователь вошёл в систему",
"userId": 12345,
"traceId": "a1b2c3d4-e5f6-7890-g1h2-i3j4k5l6m7n8"
}
В Python через logging.Formatter можно задать:
formatter = logging.Formatter(
'%(asctime)s [%(levelname)s] %(name)s: %(message)s'
)
В Serilog (C#):
.WriteTo.Console(outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss} [{Level}] {SourceContext}: {Message}{NewLine}{Exception}")
Структурированный формат (особенно JSON) критически важен для автоматического парсинга в системах вроде ELK, Grafana Loki, Datadog.
Логи могут направляться в разные «приёмники» (sinks):
- Консоль — удобно при разработке и в контейнерах (Docker), где stdout/stderr захватываются оркестратором.
- Файл — классический способ. Используется ротация по размеру или времени (
app.log,app.2026-03-15.log). - Системный журнал (syslog, journald) — в Linux-средах.
- База данных — редко, так как создаёт нагрузку; применяется для аудита или когда нужен SQL-доступ.
- Сетевой эндпоинт — HTTP, TCP, UDP, syslog, или специализированные протоколы (например, Fluentd, OpenTelemetry).
- Облачные сервисы — AWS CloudWatch, Azure Monitor, Google Cloud Logging.
Часто используется мультиплексирование: одновременная запись в консоль и файл, или в файл и удалённый сервер.
Без контекста логи бесполезны. Контекст помогает связать события между собой:
- Trace ID — уникальный идентификатор запроса, проходящего через микросервисы.
- User ID / Session ID — кто инициировал действие.
- IP-адрес клиента — откуда пришёл запрос.
- Версия приложения — для диагностики регрессий.
- География, язык, устройство — если релевантно.
В веб-приложениях контекст часто берётся из HTTP-заголовков или middleware. Например, в ASP.NET Core используется ILogger.BeginScope(), в Express.js — middleware, который добавляет req.id в каждый лог.
Пример (Python + structlog):
log = structlog.get_logger()
with structlog.contextvars.bind_contextvars(user_id=123, trace_id="abc123"):
log.info("action performed")
После настройки важно убедиться, что:
- логи действительно пишутся;
- содержат правильный уровень и формат;
- контекст присутствует;
- чувствительные данные не попадают в логи;
- при ошибке записывается стек вызовов.
Можно написать unit-тесты, которые проверяют, что при вызове метода генерируется ожидаемое сообщение. Или просто запустить приложение вручную и вызвать типичные сценарии (успешный вход, ошибка валидации, исключение).
Для продакшена — проверить, что логи поступают в централизованную систему (если используется).
Без управления объёмом логи быстро заполнят диск. Ротация — это автоматическое создание новых файлов и удаление старых.
- По размеру: новый файл после 100 МБ.
- По времени: новый файл каждый день.
- По количеству: хранить не более 7 файлов.
В Python — RotatingFileHandler или TimedRotatingFileHandler.
В .NET — Serilog.Sinks.File с параметром rollOnFileSizeLimit.
В Java — <rollingPolicy> в logback.xml.
Мониторинг — это настройка алертов на критические события:
- частые
ERRORилиCRITICAL; - рост объёма логов (может указывать на зацикливание);
- отсутствие логов (сервис упал).
Интеграция с Prometheus + Alertmanager, Grafana, или облачными мониторинговыми сервисами позволяет получать уведомления в Slack, Telegram, email.
Этот алгоритм применим ко всем языкам и платформам.
Как проверять и читать логи
- Локальные файлы: читаются через текстовые редакторы,
tail -fв терминале, или специализированные просмотрщики (например, LogMX, Chainsaw). - Консоль браузера: вкладка Console в DevTools.
- Серверные логи: через SSH,
journalctl,docker logs, или централизованные системы (Kibana, Grafana Logs). - Базы данных: SQL-запросы или NoSQL-поиск по полям.
- Фильтрация: по времени, уровню, ключевым словам, идентификаторам.
Хорошая практика — использовать grep, awk, jq (для JSON) или SIEM-системы для анализа больших объёмов логов.
Логи, записанные в файлы на диске, являются наиболее распространённым способом хранения событий. Они могут быть как простыми текстовыми файлами (.log, .txt), так и структурированными (JSON, CSV).
Способы просмотра:
- Текстовые редакторы: Notepad++, VS Code, Sublime Text — удобны для небольших файлов.
- Командная строка:
cat app.log— вывод всего содержимого файла.less app.log— постраничный просмотр с возможностью прокрутки.tail -f app.log— отображение последних строк файла в реальном времени (полезно при активной записи).head -n 20 app.log— показ первых 20 строк.
Специализированные просмотрщики:
- LogMX — графический инструмент с подсветкой уровней, фильтрацией и поддержкой регулярных выражений.
- Chainsaw — утилита от Elastic, предназначенная для быстрого поиска по логам с использованием Sigma-правил.
- lnav — терминальный просмотрщик с автоматическим определением формата и подсветкой.
Для структурированных логов (например, JSON) особенно полезны инструменты, поддерживающие парсинг и фильтрацию по полям.
В веб-разработке логи часто выводятся в консоль браузера через console.log(), console.error() и другие методы. Это помогает отслеживать состояние приложения во время работы.
Как читать:
- Открыть DevTools (F12 или ПКМ → «Просмотреть код»).
- Перейти во вкладку Console.
- Использовать фильтры по уровням: Info, Warning, Error, Debug.
- Искать по ключевым словам с помощью панели поиска (Ctrl+F).
Хорошая практика — не оставлять console.log() в продакшен-коде, так как это может раскрыть внутреннюю логику и замедлить работу приложения.
На серверах логи могут храниться в разных местах в зависимости от ОС, сервиса и способа развёртывания.
Основные способы доступа:
- SSH: подключение к удалённому серверу и работа с файлами через терминал.
- systemd/journald:
journalctl -u myapp.service— просмотр логов конкретного systemd-сервиса.journalctl --since "1 hour ago"— фильтрация по времени.
- Docker:
docker logs <container_id>— вывод всех логов контейнера.docker logs -f <container_id>— потоковый просмотр (аналогtail -f).docker logs --since 1h <container_id>— логи за последний час.
Централизованные системы:
- Kibana — визуальный интерфейс для Elasticsearch, позволяет строить дашборды, фильтровать по полям, искать по шаблонам.
- Grafana Logs (Loki) — легковесная система логирования, интегрированная с Grafana, использует метки вместо полнотекстового индекса.
- Datadog, New Relic, Sentry — облачные платформы с продвинутыми возможностями поиска, алертинга и корреляции с метриками.
Некоторые системы пишут логи напрямую в базы данных — чаще всего для аудита, аналитики или долгосрочного хранения.
Чтение из SQL-баз:
SELECT * FROM application_logs
WHERE level = 'ERROR'
AND created_at > '2026-03-15 10:00:00'
ORDER BY created_at DESC;
Чтение из NoSQL (например, MongoDB):
db.logs.find({
level: "ERROR",
timestamp: { $gt: ISODate("2026-03-15T10:00:00Z") }
}).sort({ timestamp: -1 });
Преимущества такого подхода — мощные возможности фильтрации и агрегации. Недостаток — нагрузка на базу и сложность масштабирования.
Фильтрация — ключевой навык при работе с логами. Без неё легко утонуть в потоке данных.
Основные критерии фильтрации:
- Время: события за последние 5 минут, час, день.
- Уровень: только
ERRORиCRITICAL. - Ключевые слова:
timeout,auth failed,NullPointerException. - Идентификаторы:
trace_id,user_id,request_id— позволяют отследить цепочку событий.
Для обработки тысяч или миллионов строк применяются специализированные утилиты:
-
grep— поиск по шаблону:grep "ERROR" app.log
grep -i "timeout" app.log # без учёта регистра -
awk— обработка колонок и условий:awk '$3 == "ERROR" { print $0 }' app.log -
jq— парсинг и фильтрация JSON:cat app.jsonl | jq 'select(.level == "ERROR") | .message' -
SIEM-системы (Security Information and Event Management):
- Splunk, IBM QRadar, Elastic SIEM — позволяют собирать, коррелировать и визуализировать логи из множества источников.
- Поддерживают правила обнаружения аномалий, автоматические алерты и расследование инцидентов.
Эти инструменты особенно важны в распределённых системах, где один запрос проходит через десятки сервисов, и логи разбросаны по разным машинам.
Как правильно записывать логи
-
Используйте интерполяцию или форматирование, а не конкатенацию строк:
logger.info(f"Пользователь {user_id} выполнил действие '{action}'")вместо
logger.info("Пользователь " + str(user_id) + " выполнил действие '" + action + "'") -
Не логируйте объекты напрямую — преобразуйте их в читаемый или сериализуемый вид:
_logger.LogInformation("Запрос: {@Request}", request);(в Serilog
@означает структурированную сериализацию) -
Избегайте многострочных логов без причины — каждый лог-сообщение должно быть одной строкой, чтобы упростить парсинг.
-
Добавляйте метаданные: время, уровень, имя потока, идентификатор трассировки.
-
Используйте параметризованные сообщения, если библиотека это поддерживает:
logger.info("Обработка заказа {} для клиента {}", orderId, customerId);
Библиотеки и примеры реализации
Python (logging + structlog)
import logging
import structlog
# Базовая настройка
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s [%(levelname)s] %(name)s: %(message)s',
handlers=[
logging.FileHandler("app.log"),
logging.StreamHandler()
]
)
logger = logging.getLogger(__name__)
logger.info("Приложение запущено")
# Структурированное логирование
structlog.configure(
processors=[
structlog.processors.JSONRenderer()
],
logger_factory=structlog.stdlib.LoggerFactory(),
)
log = structlog.get_logger()
log.info("event", user_id=123, action="login")
JavaScript (Node.js, winston)
const winston = require('winston');
const logger = winston.createLogger({
level: 'info',
format: winston.format.json(),
transports: [
new winston.transports.File({ filename: 'error.log', level: 'error' }),
new winston.transports.File({ filename: 'combined.log' }),
new winston.transports.Console()
]
});
logger.info('Приложение запущено', { userId: 123 });
C# (.NET, ILogger + Serilog)
using Microsoft.Extensions.Logging;
using Serilog;
Log.Logger = new LoggerConfiguration()
.WriteTo.Console()
.WriteTo.File("logs/app.txt", rollingInterval: RollingInterval.Day)
.CreateLogger();
var builder = Host.CreateApplicationBuilder(args);
builder.Services.AddLogging(logging => {
logging.AddSerilog();
});
var app = builder.Build();
var logger = app.Services.GetRequiredService<ILogger<Program>>();
logger.LogInformation("Приложение запущено. Пользователь: {UserId}", userId);
Java (SLF4J + Logback)
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class App {
private static final Logger logger = LoggerFactory.getLogger(App.class);
public static void main(String[] args) {
logger.info("Приложение запущено. Пользователь: {}", userId);
}
}
Файл logback.xml:
<configuration>
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>logs/app.log</file>
<encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">
<layout class="ch.qos.logback.contrib.json.classic.JsonLayout"/>
</encoder>
</appender>
<root level="info">
<appender-ref ref="FILE"/>
</root>
</configuration>
Какие данные записывать в логи, а какие — нет
Записывать стоит:
- Время события;
- Уровень важности (info, error и т.д.);
- Идентификатор операции или запроса (trace ID);
- Имя модуля или класса;
- Краткое описание события;
- Контекст: ID пользователя, IP-адрес, тип устройства;
- Параметры вызова (без чувствительных данных);
- Стек вызовов при ошибках.
Не записывать:
- Пароли, токены, ключи API;
- Персональные данные (ФИО, email, телефон) без согласия и шифрования;
- Полные тела запросов/ответов, если они содержат приватную информацию;
- Дублирующие или избыточные сообщения (например, лог каждой итерации цикла без причины);
- Бинарные данные или огромные структуры без обрезки.
Правильное логирование — это баланс между информативностью и безопасностью. Логи должны помогать, а не мешать или создавать риски.