← Production Mastery
🏠 Главная 📚 Все курсы 🎯 RVV proof 📊 Paper proof

Урок 10 — Loki централизованное логирование

Урок 10 из 10 · Production Mastery

🟢 Не теория — наш живой бот: RVV Hunter MTF работает прямо сейчас (WR 66%, +$141 за 15 дней). Всё что в этом уроке — настроено на нашей боевой системе.
Финальный урок: Loki

Финальный урок: Loki — централизованное логирование для твоего трейдинг-бота

Поздравляю! Ты дошел до финала курса "Production Mastery". Это последнее, недостающее звено, которое превратит твою систему из «работает» в «надежно работает и легко чинится».

У тебя уже есть бот, который торгует 24/7. У него есть мониторинг, бэкапы и автоматическое развертывание. Но есть одна боль, знакомая каждому, кто управляет автоматическими системами...

1. ⏱ 25 мин / 📖 Термины

Давай быстро пробежимся по понятиям, которые нам сегодня понадобятся. Без воды, только суть.

2. ❌ До / ✅ После

Чтобы понять всю мощь Loki, давай сравним два подхода к поиску проблемы.

❌ До Loki (твой текущий процесс) ✅ После этого урока (с Loki)
1. Получаешь уведомление, что бот сделал странную сделку. 1. Получаешь автоматическое уведомление в Telegram: "Error rate > 5 in 5m".
2. Подключаешься к серверу по SSH. 2. Открываешь Grafana в браузере.
3. Выполняешь docker-compose logs trading-bot --since 1h > bot.log. Ждешь. 3. В поле поиска вводишь: {app="trading-bot", level="error"}.
4. Открываешь файл bot.log размером 50 МБ и ищешь глазами или через grep слово "ERROR". 4. Мгновенно видишь все ошибки за последний час.
5. Находишь ошибку, но хочешь посмотреть, что было до нее. Снова скроллишь гигантский файл. 5. Кликаешь на строку с ошибкой и видишь логи "до" и "после" в удобном интерфейсе.
6. Потрачено: 1-2 часа. Нервы на пределе. 6. Потрачено: 5 минут. Проблема ясна.

3. Story: Боль, которую мы лечим

Представь: 3 часа ночи. Тебя будит уведомление от биржи: твоя позиция по ETH закрыта с убытком в $500. Сердце уходит в пятки. Ты же настраивал стоп-лосс гораздо ниже! Что произошло?

Ты вскакиваешь, открываешь ноутбук, подключаешься к серверу. Твой бот — это Docker-контейнер. Первая мысль — посмотреть логи. Ты пишешь:

docker-compose logs trading-bot --since 2h

На экран вываливается стена текста. Десятки тысяч строк. Информация о каждом тике цены, каждом пинге к API биржи, каждом расчете индикатора. Ты пытаешься найти что-то осмысленное с помощью grep, но это как искать иголку в стоге сена. Ты не знаешь, что именно искать: "error", "stop-loss", "close position"?

Через час мучительного скроллинга и фильтрации ты наконец находишь строчку, где бот получил от биржи странный ответ, не смог его обработать, запаниковал и закрыл позицию по рынку. Проблема найдена, но какой ценой? Ты потратил кучу времени и нервов. А если бы у тебя было 5 ботов? Ты бы искал в 5 раз дольше.

Эта боль — главная причина, по которой мы сегодня внедряем Loki.

4. Часть 1: Зачем Loki, если есть grep?

grep и docker logs — это как поиск по одному огромному текстовому файлу на твоем компьютере. Это работает для маленьких файлов, но не для потока данных в реальном времени.

Loki — это Google для твоих логов.

Ключевое отличие — в метках (labels). Loki не индексирует весь текст лога (поэтому он такой дешевый и быстрый). Он индексирует только метки, которые ты сам задаешь. Например:

Когда ты делаешь запрос {app="trading-bot-eth", level="error"}, Loki мгновенно находит все потоки логов с этими метками, а уже потом ищет по тексту внутри этих отфильтрованных потоков. Это на порядки эффективнее, чем читать всё подряд.

5. Часть 2: Добавляем Loki и Promtail в docker-compose.yml

Мы расширим наш стек из урока 6, где мы настраивали Grafana и Prometheus. Grafana у нас уже есть, и она умеет работать с Loki "из коробки".

Открой свой docker-compose.yml и добавь в него сервисы loki и promtail. Вот полный пример, как он должен выглядеть:

version: '3.8'

services:
  # Твой бот (или боты) уже здесь
  trading-bot:
    image: my-trading-bot:latest
    restart: unless-stopped
    # ... остальные настройки ...

  grafana:
    image: grafana/grafana:latest
    ports:
      - "3000:3000"
    volumes:
      - grafana-data:/var/lib/grafana
    restart: unless-stopped

  # Prometheus у тебя уже может быть, оставляем его
  prometheus:
    image: prom/prometheus:latest
    ports:
      - "9090:9090"
    volumes:
      - ./prometheus.yml:/etc/prometheus/prometheus.yml
    restart: unless-stopped

  # --- Новые сервисы ---

  loki:
    image: grafana/loki:2.9.2
    ports:
      - "3100:3100"
    volumes:
      - loki-data:/loki
      # Мы добавим конфиг для retention чуть позже
      # - ./loki-config.yml:/etc/loki/local-config.yaml
    command: -config.file=/etc/loki/local-config.yaml
    restart: unless-stopped

  promtail:
    image: grafana/promtail:2.9.2
    volumes:
      - /var/log:/var/log
      - ./promtail-config.yml:/etc/promtail/config.yml
      # Важнейшая строка! Даем Promtail доступ к сокету Docker,
      # чтобы он мог автоматически находить все контейнеры и их логи.
      - /var/run/docker.sock:/var/run/docker.sock
    command: -config.file=/etc/promtail/config.yml
    restart: unless-stopped
    depends_on:
      - loki

volumes:
  grafana-data:
  loki-data:

Теперь, после запуска docker-compose up -d, у тебя поднимутся два новых контейнера: база данных для логов и сборщик.

6. Часть 3: Настройка Promtail — какие логи собирать

Promtail нужно объяснить, откуда брать логи. Мы используем его волшебную способность — автообнаружение Docker-контейнеров. Для этого создадим файл promtail-config.yml рядом с docker-compose.yml.

server:
  http_listen_port: 9080
  grpc_listen_port: 0

positions:
  # Файл, где Promtail запоминает, на каком месте он закончил читать логи.
  filename: /tmp/positions.yaml

clients:
  - url: http://loki:3100/loki/api/v1/push

scrape_configs:
- job_name: containers
  # Главная магия здесь:
  docker_sd_configs:
    - host: unix:///var/run/docker.sock
      refresh_interval: 5s
  # Для каждого найденного контейнера Promtail будет создавать метки
  relabel_configs:
    # Имя контейнера
    - source_labels: ['__meta_docker_container_name']
      regex: '/(.*)'
      target_label: 'container'
    # Имя приложения из лейблов docker-compose
    - source_labels: ['__meta_docker_container_label_com_docker_compose_service']
      target_label: 'app'

Что здесь происходит? Promtail подключается к Docker и для каждого запущенного контейнера автоматически собирает логи. При этом он создает две полезные метки: container (например, my-project-trading-bot-1) и app (например, trading-bot). Этого уже достаточно для начала!

7. Часть 4: Первый LogQL запрос в Grafana

Теперь самое интересное. Перезапусти свой стек: docker-compose down && docker-compose up -d.

  1. Зайди в Grafana (http://localhost:3000).
  2. Перейди в Connections → Data sources. Нажми Add new data source, найди в списке Loki.
  3. В поле URL введи http://loki:3100. Нажми Save & Test. Ты должен увидеть "Data source is working".
  4. Теперь слева в меню открой вкладку Explore.
  5. Вверху слева выбери из выпадающего списка свой источник данных Loki.
  6. В поле Log browser ты уже можешь увидеть наши метки, например app. Выбери app="trading-bot". Ты увидишь все логи своего бота!

А теперь решим нашу ночную проблему. Чтобы найти все ошибки за последний час, введи в строку запроса:

{app="trading-bot"} |= "ERROR"

Нажми Enter. И... вот они! Все строки, содержащие "ERROR", отфильтрованы и показаны тебе. Никакого SSH и grep. Это уже победа.

8. Часть 5: Структурированные логи в Python

Поиск по тексту "ERROR" — это хорошо, но мы можем лучше. Что если мы хотим найти все логи, связанные с конкретной сделкой? Для этого логи должны быть не просто текстом, а структурой. Например, JSON.

Установим библиотеку для JSON-логирования: pip install python-json-logger.

Теперь в коде твоего бота, там где ты настраиваешь логирование, используй logging.config.dictConfig. Это стандартный способ в Python. Вот полный пример конфигурации:

import logging
import logging.config

LOGGING_CONFIG = {
    'version': 1,
    'disable_existing_loggers': False,
    'formatters': {
        'json': {
            '()': 'pythonjsonlogger.jsonlogger.JsonFormatter',
            'format': '%(asctime)s %(name)s %(levelname)s %(message)s %(trade_id)s',
        },
    },
    'handlers': {
        'stdout': {
            'class': 'logging.StreamHandler',
            'stream': 'ext://sys.stdout',
            'formatter': 'json',
        },
    },
    'loggers': {
        '': {
            'handlers': ['stdout'],
            'level': 'INFO',
        },
    }
}

# Применяем конфигурацию при старте приложения
logging.config.dictConfig(LOGGING_CONFIG)

# Пример использования
logger = logging.getLogger(__name__)

def process_trade(trade_id, amount):
    extra_info = {'trade_id': trade_id}
    logger.info(
        f"Processing trade with amount {amount}",
        extra=extra_info
    )
    try:
        # ... какая-то логика ...
        if amount < 0:
            raise ValueError("Amount cannot be negative")
        logger.info("Trade processed successfully", extra=extra_info)
    except Exception as e:
        logger.error(
            f"Failed to process trade: {e}",
            extra=extra_info,
            exc_info=True  # Добавляет traceback в лог
        )

# ---
logger.info("Bot started")
process_trade("tx-12345", 100)
process_trade("tx-67890", -50)

Что изменилось? Теперь каждая строка лога — это JSON! В Grafana это будет выглядеть как структурированные поля. И теперь ты можешь писать еще более мощные запросы:

Найти все события для сделки "tx-67890":

{app="trading-bot"} | json | trade_id="tx-67890"

Оператор | json говорит Loki распарсить строку как JSON, а | trade_id="tx-67890" — это фильтр по содержимому JSON-поля. Теперь ты можешь отследить жизненный цикл любой операции в твоей системе за секунды.

9. Часть 6: Проактивность — алертинг на ошибки

Хватит смотреть в логи, пусть они сами приходят к тебе, когда что-то не так. Настроим алерт в Grafana, который будет писать в Telegram, если количество ошибок превысит порог.

  1. В Grafana перейди в Alerting → Alert rules → New alert rule.
  2. В секции A (Query) выбери свой Loki datasource.
  3. Введи запрос, который считает количество ошибок за 5 минут:
    count_over_time({app="trading-bot"} |= "ERROR" [5m])
  4. В секции B (Expression) добавь условие. Например, $A > 5. Это значит: "если запрос А вернул значение больше 5".
  5. Настрой папку для сохранения и группу.
  6. В секции "Notifications" выбери свой канал для Telegram (ты настраивал его на уроке про мониторинг) и напиши сообщение, например: "🚨 Слишком много ошибок в трейдинг-боте! ({{ $values.A }} за 5 мин)".
  7. Сохрани правило.

Готово! Теперь, если твой бот начнет сыпать ошибками, ты узнаешь об этом первым, а не от брокера.

10. Часть 7: Управление хранением (Retention)

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

Создай файл loki-config.yml:

auth_enabled: false

server:
  http_listen_port: 3100

ingester:
  lifecycler:
    address: 127.0.0.1
    ring:
      kvstore:
        store: inmemory
      replication_factor: 1
    final_sleep: 0s
  chunk_idle_period: 1m
  chunk_target_size: 1048576
  chunk_retain_period: 30s
  max_transfer_retries: 0

schema_config:
  configs:
    - from: 2020-10-24
      store: boltdb-shipper
      object_store: filesystem
      schema: v11
      index:
        prefix: index_
        period: 24h

storage_config:
  boltdb_shipper:
    active_index_directory: /loki/boltdb-shipper-active
    cache_location: /loki/boltdb-shipper-cache
    cache_ttl: 24h
    shared_store: filesystem
  filesystem:
    directory: /loki/chunks

# --- Самая важная часть ---
limits_config:
  # Удалять логи старше 30 дней (720 часов)
  retention_period: 720h

Теперь раскомментируй строку с монтированием этого конфига в docker-compose.yml:

  loki:
    # ...
    volumes:
      - loki-data:/loki
      - ./loki-config.yml:/etc
[tokens: in=511 out=5403 thinking=2593 | cost=$0.0806]



Хочешь больше? Полный курс AI Agents включает Production-модуль + 27 других уроков

AI Agents $199 →