Логирование на практике: как не превратить свой код в болото из логов
Логирование — это не просто отладочные print()ы, которые потом закомментируют. Это система, которая должна работать в продакшене годами, не мешая разработке, но при этом давая ответы на вопросы: "Почему клиент получил 500-й?", "Где застрял юзер в фаннеле?", "Кто и когда удалил важный ресурс?".
Если логи не помогают, они только шум. Если они помогают — это половина успеха в поддержке и отладке. Давайте разберёмся, как сделать так, чтобы логи работали на вас, а не наоборот.
Проблема: когда логи становятся врагом
Представьте ситуацию: у вас есть микросервис, который обрабатывает тысячи запросов в секунду. Логи пишутся с уровнем DEBUG, потому что "на локалке так удобнее". Через месяц у вас:
- 100+ гигабайт логов в день — их сложно хранить и обрабатывать.
- Нет фильтрации — чтобы найти ошибку, приходится перебирать тысячи строк.
- Логи дублируются — один и тот же запрос записывается в несколько сервисов, а потом их приходится стыковать вручную.
- Нет структуры — парсить логи вручную или через
grep— это не решение, а пытка.
В итоге:
- Медленная отладка — вместо того чтобы быстро найти корень проблемы, тратишь время на фильтрацию.
- Риск потери данных — если логи не ротируются или не архивируются, они могут просто испариться.
- Безопасность под угрозой — в логах лежат PII (персональные данные), токены, пароли, а вы их никуда не чистите.
Практика: как правильно логировать
1. Уровни логирования: не всё нужно записывать
Используйте уровни логирования (DEBUG, INFO, WARN, ERROR, FATAL) по назначению:
- DEBUG — только для отладки на локалке. В продакшене его лучше отключать или использовать редко.
- INFO — бизнес-логика, важные события (например, "Пользователь успешно авторизовался").
- WARN — потенциальные проблемы (например, "Очередь задержалась на 2 секунды").
- ERROR — ошибки, которые требуют внимания (например, "Не удалось подключиться к базе").
- FATAL — критическая ошибка, после которой сервис может упасть.
Пример конфигурации для Python (logging.config.dictConfig):
LOGGING_CONFIG = {
'version': 1,
'disable_existing_loggers': False,
'formatters': {
'default': {
'format': '%(asctime)s - %(name)s - %(levelname)s - %(message)s',
'datefmt': '%Y-%m-%d %H:%M:%S'
},
'structured': {
'()': 'pythonjsonlogger.jsonlogger.JsonFormatter',
'format': '%(asctime)s %(levelname)s %(name)s %(message)s'
}
},
'handlers': {
'console': {
'class': 'logging.StreamHandler',
'level': 'INFO',
'formatter': 'default',
'stream': 'ext://sys.stdout'
},
'file': {
'class': 'logging.handlers.RotatingFileHandler',
'level': 'DEBUG',
'formatter': 'structured',
'filename': 'app.log',
'maxBytes': 10485760, # 10 MB
'backupCount': 5,
'encoding': 'utf8'
}
},
'loggers': {
'': {
'handlers': ['console', 'file'],
'level': 'INFO',
'propagate': False
},
'debug_logger': {
'handlers': ['file'],
'level': 'DEBUG',
'propagate': False
}
}
}
2. Структурированные логи: как не тонуть в тексте
Текстовые логи ("User logged in") — это прошлый век. Сегодня используйте структурированные логи (JSON, Protobuf), чтобы:
- Легко фильтровать по полям (например,
level="ERROR" and service="auth"). - Отправлять в системы мониторинга (ELK, Datadog, Prometheus).
- Автоматизировать анализ (например, находить аномалии в метриках).
Пример структурированного лога на Go:
package main
import (
"encoding/json"
"log"
"time"
)
type structuredLogger struct {
name string
}
func (l *structuredLogger) Log(level, message string, fields map[string]interface{}) {
logEntry := map[string]interface{}{
"timestamp": time.Now().Format(time.RFC3339),
"level": level,
"name": l.name,
"message": message,
"fields": fields,
}
jsonData, _ := json.Marshal(logEntry)
log.Println(string(jsonData))
}
func main() {
logger := &structuredLogger{name: "auth-service"}
logger.Log("INFO", "User logged in", map[string]interface{}{
"user_id": 123,
"action": "login",
"ip": "192.168.1.1",
})
}
Вывод в логе:
{"timestamp":"2023-10-05T12:34:56Z","level":"INFO","name":"auth-service","message":"User logged in","fields":{"user_id":123,"action":"login","ip":"192.168.1.1"}}
3. Ротация и хранение: не дайте логам съесть диск
Логи должны автоматически ротироваться и архивироваться. Иначе:
- Диск заполнится, и сервис упадет.
- Старые логи станут недоступны для анализа.
Настройки ротации для Nginx:
http {
log_format json_escape escape=json {
time_local $time_local;
remote_addr $remote_addr;
request "$request";
status $status;
body_bytes_sent $body_bytes_sent;
request_time $request_time;
upstream_response_time $upstream_response_time;
}
access_log /var/log/nginx/access.log.json json_escape buffer=32k flush=5m;
error_log /var/log/nginx/error.log warn;
# Ротация логов через logrotate
log_rotate on;
log_rotate_max_size 100m;
log_rotate_max_files 14d;
}
Пример конфига для logrotate:
/var/log/nginx/*.log {
daily
missingok
rotate 30
compress
delaycompress
notifempty
create 0640 www-data adm
sharedscripts
postrotate
systemctl reload nginx
endscript
}
4. Централизованное логирование: не ищите иглу в стоге сена
Если у вас несколько сервисов, логи должны собираться в одном месте. Для этого используйте:
- ELK Stack (Elasticsearch, Logstash, Kibana) — классика.
- Loki + Grafana — лёгкий вариант для контейнеров.
- Fluentd/Fluent Bit — для сбора и форвардинга логов.
Пример конфига Fluent Bit для сбора логов с Docker:
[SERVICE]
Flush 1
Log_Level info
Daemon off
Parsers_File parsers.conf
[INPUT]
Name tail
Tag docker.*
Path /var/log/containers/*.log
Parser docker
DB /var/log/flb_docker.db
Mem_Buf_Limit 5MB
[OUTPUT]
Name es
Match *
Host elasticsearch
Port 9200
Logstash_Format On
Logstash_Prefix fluentbit
Replace_Dots On
Retry_Limit False
HTTP_User logstash_writer
HTTP_Passwd somepassword
5. Безопасность логов: не оставляйте секреты на виду
Логи часто содержат:
- Токены API (JWT, OAuth).
- Пароли и секреты.
- PII (имена, email, номера телефонов).
Как защититься:
- Маскируйте чувствительные данные (например,
user_email: "user@example.com"→user_email: "[REDACTED]"). - Используйте отдельные логи для PII (например,
/var/log/audit/pii.logс ограниченным доступом). - Не храните логи дольше, чем нужно (например,
ERROR— 30 дней,DEBUG— 7 дней).
Пример маскировки в Python:
import re
def redact_logs(log_message: str) -> str:
# Маскируем email
log_message = re.sub(r'([a-zA-Z0-9._-]+@[a-zA-Z0-9._-]+\.[a-zA-Z0-9_-]+)', '[REDACTED_EMAIL]', log_message)
# Маскируем токены
log_message = re.sub(r'[a-f0-9]{8}-?[a-f0-9]{4}-?[a-f0-9]{4}-?[a-f0-9]{4}-?[a-f0-9]{12}', '[REDACTED_TOKEN]', log_message)
return log_message
# Пример использования
logger.info(redact_logs("User with email user@example.com logged in with token abc123..."))
Типичные ошибки и как их избежать
| Ошибка | Почему это плохо | Как исправить |
|---|---|---|
| Логируете всё на DEBUG | Забиваете диск и системы мониторинга шумом. | Используйте уровни логирования по назначению. В продакшене DEBUG должен быть отключён или очень редким. |
| Нет ротации логов | Логи съедают диск, сервис падает. | Настройте ротацию (например, через logrotate или встроенные средства фреймворка). |
| Текстовые логи без структуры | Трудно фильтровать, парсить, анализировать. | Переходите на структурированные логи (JSON, Protobuf). |
| Логи дублируются между сервисами | Тратятся ресурсы на хранение и обработку. | Стандартизируйте формат логов и используйте централизованный сбор. |
| Нет фильтрации PII и секретов | Риск утечки данных. | Маскируйте или удаляйте чувствительные данные перед записью. |
| Логи не индексируются | Нельзя быстро искать ошибки. | Используйте ELK, Loki или другие системы для индексации. |
| Нет метрик по логам | Не видно, сколько ошибок происходит. | Добавьте метрики (например, количество ERROR в минуту) и алерты. |
Пример полноценного workflow: от локальной разработки до продакшена
Локальная разработка (Python + Docker):
- Используйте
DEBUGлоги для отладки. - Конфигурация логирования в
logging.config.dictConfig(как в примере выше). - Логи записываются в консоль и файл
app.log.
- Используйте
Стенд/Тест (Kubernetes):
- Логи собираются через Fluent Bit и отправляются в Loki.
- Уровень логирования —
INFO(отключенDEBUG). - Ротация логов через
logrotateили встроенные средства K8s.
Производство:
- Структурированные логи (JSON) отправляются в ELK Stack.
- Фильтрация PII перед записью.
- Алерты на основе логов (например,
ERROR> 5 в минуту). - Архивация старых логов в S3/Glacier.
Пример CI/CD-шага для проверки логов (GitHub Actions):
name: Check Logs
on: [push]
jobs:
validate-logs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Run tests with log validation
run: |
python -m pytest tests/test_logging.py --log-cli-level=INFO
# Проверяем, что в логах нет секретов
grep -E "password|token|secret" app.log && exit 1 || exit 0
Вывод: как сделать логирование рабочим инструментом
- Не логируйте всё подряд — используйте уровни (
INFO,ERROR) и структурируйте данные. - Автоматизируйте ротацию и хранение — не дайте логам забить диск.
- Централизуйте сбор логов — ищите иглу в одном месте, а не в каждом сервисе отдельно.
- Защищайте данные — маскируйте PII и секреты, чтобы не нарушить GDPR/комплайенс.
- Интегрируйте с мониторингом — логов без метрик и алертов недостаточно.
Практический чеклист перед релизом:
- Логи структурированы (JSON/Protobuf).
- Уровни логирования настроены правильно (
DEBUGотключён в продакшене). - Ротация и архивация логов настроена.
- Логи собираются централизованно (ELK/Loki/Fluentd).
- PII и секреты маскируются или удаляются.
- Есть алерты на критичные ошибки.
Если вы сделаете это правильно, логи перестанут быть болотом и станут вашим лучшим другом в поддержке и отладке. А если нет — вы всегда сможете вернуться к этому гайду и исправить ошибки.