Логи розкидані по десятку серверів — пошук причини збою перетворюється на тижневий квест. При пікових навантаженнях логи губляться, а пошук помилки займає години. Ми вирішуємо це за день: ставимо централізований збір на 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. Замовте налаштування та отримайте консультацію по вашому проєкту.







