Логирование на практике: как не превратить свой код в болото из логов

Логирование — это не просто отладочные 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, номера телефонов).

Как защититься:

  1. Маскируйте чувствительные данные (например, user_email: "user@example.com"user_email: "[REDACTED]").
  2. Используйте отдельные логи для PII (например, /var/log/audit/pii.log с ограниченным доступом).
  3. Не храните логи дольше, чем нужно (например, 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: от локальной разработки до продакшена

  1. Локальная разработка (Python + Docker):

    • Используйте DEBUG логи для отладки.
    • Конфигурация логирования в logging.config.dictConfig (как в примере выше).
    • Логи записываются в консоль и файл app.log.
  2. Стенд/Тест (Kubernetes):

    • Логи собираются через Fluent Bit и отправляются в Loki.
    • Уровень логирования — INFO (отключен DEBUG).
    • Ротация логов через logrotate или встроенные средства K8s.
  3. Производство:

    • Структурированные логи (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

Вывод: как сделать логирование рабочим инструментом

  1. Не логируйте всё подряд — используйте уровни (INFO, ERROR) и структурируйте данные.
  2. Автоматизируйте ротацию и хранение — не дайте логам забить диск.
  3. Централизуйте сбор логов — ищите иглу в одном месте, а не в каждом сервисе отдельно.
  4. Защищайте данные — маскируйте PII и секреты, чтобы не нарушить GDPR/комплайенс.
  5. Интегрируйте с мониторингом — логов без метрик и алертов недостаточно.

Практический чеклист перед релизом:

  • Логи структурированы (JSON/Protobuf).
  • Уровни логирования настроены правильно (DEBUG отключён в продакшене).
  • Ротация и архивация логов настроена.
  • Логи собираются централизованно (ELK/Loki/Fluentd).
  • PII и секреты маскируются или удаляются.
  • Есть алерты на критичные ошибки.

Если вы сделаете это правильно, логи перестанут быть болотом и станут вашим лучшим другом в поддержке и отладке. А если нет — вы всегда сможете вернуться к этому гайду и исправить ошибки.