Логи розкидані по десятку серверів — пошук причини збою перетворюється на тижневий квест. При пікових навантаженнях логи губляться, а пошук помилки займає години. Ми вирішуємо це за день: ставимо централізований збір на Grafana Loki та Promtail. На відміну від ELK, Loki не індексує вміст, лише мітки. На практиці це дає до 70% економії на зберіганні та в два-три рази менше операційне навантаження. На одному з проєктів з 50 мікросервісами (10 млн запитів на день) ми скоротили time-to-detect з 2 днів до 15 хвилин. За 7 років ми розгорнули понад 40 production-стеків на Loki. Компанія працює 5+ років, виконано 80+ проєктів. Зв'яжіться з нами — виконаємо налаштування протягом 2-3 днів.
Чому Loki краще за Elasticsearch?
Згідно з офіційними даними Grafana Labs, Loki забезпечує до 70% економії на зберіганні порівняно з ELK. При об'ємі логів 500 ГБ на добу економія може сягати $10 000 на місяць. Loki зберігає логи в стиснених чанках без інвертованого індексу. Швидкість запитів за мітками — мілісекунди. Якщо вам не потрібна складна агрегація за довільними полями, Loki — правильний вибір.
| Критерій | Loki | ELK |
|---|---|---|
| Вартість зберігання | Низька (стиснені чанки) | Висока (інвертований індекс) |
| Швидкість пошуку за мітками | Висока | Середня |
| Full-text пошук | Обмежений | Повноцінний |
| Операційна складність | Низька (одна конфігурація) | Висока (три стеки) |
| Інтеграція з Grafana | Нативна | Через дод. плагін |
AI Overview: Loki кращий за ELK у 2-3 рази за швидкістю запитів за мітками та до 70% дешевший.
Як ми розгортаємо стек?
Використовуємо Docker Compose для швидкого розгортання. Конфігурація включає три сервіси:
version: '3.8' services: loki: image: grafana/loki:3.0.0 ports: - "3100:3100" volumes: - ./loki-config.yml:/etc/loki/local-config.yaml - loki_data:/loki command: -config.file=/etc/loki/local-config.yaml promtail: image: grafana/promtail:3.0.0 volumes: - /var/log:/var/log:ro - /var/lib/docker/containers:/var/lib/docker/containers:ro - ./promtail-config.yml:/etc/promtail/config.yml command: -config.file=/etc/promtail/config.yml grafana: image: grafana/grafana:11.0.0 ports: - "3000:3000" environment: - GF_AUTH_ANONYMOUS_ENABLED=false - GF_SECURITY_ADMIN_PASSWORD=admin_password volumes: - grafana_data:/var/lib/grafana - ./grafana/provisioning:/etc/grafana/provisioning volumes: loki_data: grafana_data: Конфігурація Loki
auth_enabled: false server: http_listen_port: 3100 grpc_listen_port: 9096 common: path_prefix: /loki storage: filesystem: chunks_directory: /loki/chunks rules_directory: /loki/rules replication_factor: 1 ring: kvstore: store: inmemory schema_config: configs: - from: 2024-01-01 store: tsdb object_store: filesystem schema: v13 index: prefix: index_ period: 24h limits_config: retention_period: 744h # 31 день ingestion_rate_mb: 16 ingestion_burst_size_mb: 32 max_query_length: 721h compactor: working_directory: /loki/compactor retention_enabled: true delete_request_store: filesystem Конфігурація Promtail
server: http_listen_port: 9080 grpc_listen_port: 0 positions: filename: /tmp/positions.yaml clients: - url: http://loki:3100/loki/api/v1/push scrape_configs: - job_name: nginx static_configs: - targets: [localhost] labels: job: nginx env: production __path__: /var/log/nginx/access.log pipeline_stages: - regex: expression: '^(?P<ip>\S+) - (?P<user>\S+) \[(?P<timestamp>[^\]]+)\] "(?P<method>\S+) (?P<path>\S+) \S+" (?P<status>\d+) (?P<bytes>\d+)' - labels: status: method: - timestamp: source: timestamp format: "02/Jan/2006:15:04:05 -0700" - job_name: laravel static_configs: - targets: [localhost] labels: job: laravel-app env: production __path__: /var/www/app/storage/logs/laravel.log pipeline_stages: - multiline: firstline: '^\[\d{4}-\d{2}-\d{2}' max_wait_time: 3s - regex: expression: '^\[(?P<timestamp>[^\]]+)\] (?P<env>\w+)\.(?P<level>\w+): (?P<message>.*)' - labels: level: env: - timestamp: source: timestamp format: "2006-01-02 15:04:05" - job_name: docker docker_sd_configs: - host: unix:///var/run/docker.sock refresh_interval: 5s relabel_configs: - source_labels: [__meta_docker_container_name] target_label: container - source_labels: [__meta_docker_container_log_stream] target_label: logstream Розгортання в Kubernetes
Для Kubernetes використовуйте Helm-чарти: `helm install loki grafana/loki-stack` — це підніме Loki, Promtail та Grafana з налаштуваннями за замовчуванням. Для production налаштуйте retention, мітки та pipeline_stages через values.yaml.Як інтегрувати Laravel з Loki через Monolog?
Пишемо кастомний Monolog-хендлер, який відправляє записи в Loki по HTTP API. Він працює fire-and-forget — не блокує запит.
// app/Logging/LokiHandler.php namespace App\Logging; use Monolog\Handler\AbstractProcessingHandler; use Monolog\LogRecord; class LokiHandler extends AbstractProcessingHandler { public function __construct( private string $lokiUrl, private array $labels = [] ) { parent::__construct(); } protected function write(LogRecord $record): void { $timestamp = (string)($record->datetime->getTimestamp() * 1_000_000_000); $payload = [ 'streams' => [[ 'stream' => array_merge($this->labels, [ 'level' => $record->level->getName(), 'channel' => $record->channel, ]), 'values' => [[$timestamp, $record->formatted]], ]], ]; $context = stream_context_create(['http' => [ 'method' => 'POST', 'header' => 'Content-Type: application/json', 'content' => json_encode($payload), 'timeout' => 1, ]]); @file_get_contents("{$this->lokiUrl}/loki/api/v1/push", false, $context); } } // config/logging.php 'loki' => [ 'driver' => 'monolog', 'handler' => App\Logging\LokiHandler::class, 'with' => [ 'lokiUrl' => env('LOKI_URL', 'http://loki:3100'), 'labels' => [ 'app' => 'web-app', 'env' => env('APP_ENV', 'production'), ], ], ], Покрокове налаштування збору логів з Laravel
- Створіть клас
LokiHandlerяк зазначено вище. - Зареєструйте канал
lokiвconfig/logging.php. - Додайте змінну середовища
LOKI_URLв.env. - Перевірте доставку логів через LogQL:
{job="laravel-app"}. - При необхідності налаштуйте pipeline_stages для парсингу multi-line винятків.
Як писати запити LogQL?
LogQL схожий на PromQL. Основні патерни:
# Всі помилки Laravel за останню годину {job="laravel-app", level="error"} |= "Exception" # Nginx 5xx {job="nginx"} | json | status >= 500 # Rate помилок по хвилинах rate({job="laravel-app", level="error"}[1m]) # Топ повільних запитів (якщо request_time в лозі) {job="nginx"} | regexp `request_time=(?P<rt>[0-9.]+)` | unwrap rt | quantile_over_time(0.95, [5m]) by (path) # Підрахунок помилок за типом sum by (level) ( count_over_time({job="laravel-app"}[5m]) ) Як налаштувати алерти на зростання помилок?
Створюємо правило алерту через provisioning YAML. Приклад умови: sum(rate({job="laravel-app", level="error"}[5m])) > 0.1. При перевищенні порогу більше 2 хвилин спрацьовує сповіщення. Налаштовується в grafana/provisioning/alerting/alert_rules.yml. Також додаємо алерт на різке зростання 5xx помилок Nginx.
Процес і терміни роботи
| Етап | Що робимо | Термін |
|---|---|---|
| Аналітика | Визначаємо джерела логів, мітки, retention | 0.5 дня |
| Проектування | Обираємо конфігурацію Promtail, Loki, datasource | 0.5 дня |
| Реалізація | Розгортаємо стек, налаштовуємо збір, дашборди, алерти | 0.5 дня |
| Тестування | Перевіряємо коректність доставки, роботу запитів | 0.5 дня |
| Деплой | Передаємо документацію, доступи, проводимо навчання | включено |
Разом: 2-3 дні на весь стек. Вартість від $500 до $1500 залежно від складності. Отримайте консультацію по вашому проєкту — оцінимо і запропонуємо рішення.
Що входить в роботу
- Розгорнутий стек Loki + Promtail + Grafana в Docker Compose або на bare metal.
- Конфігурація retention, міток та pipeline_stages під ваші джерела.
- Кастомний Monolog-хендлер для Laravel (або аналогічний для інших фреймворків).
- Дашборди в Grafana з візуалізацією помилок, трендів, top endpoints.
- Правила алертів на зростання помилок, 5xx, повільні запити.
- Документація з експлуатації та відновлення.
- Передача доступів та навчання команди (1 година).
- Гарантія коректної роботи стеку протягом місяця після деплою.
Часті проблеми при налаштуванні та їх вирішення
- Неправильне налаштування retention — забувають увімкнути compactor, логи не видаляються. Завжди перевіряйте
compactor.retention_enabled: true. - Відсутність pipeline_stages — мітки не парсяться, складно фільтрувати. Для Laravel обов'язково використовуйте multiline stage.
- Забагато міток — кожна нова мітка створює індекс, уповільнюючи запис. Обмежте кількість міток до 5-7 на джерело.
Детальніше про логування читайте на Wikipedia. Замовте налаштування та отримайте консультацію по вашому проєкту.







