Налаштування логування (Loki/Grafana) для вашого веб-застосунку
Loki — це не Elasticsearch. Ключова відмінність: Loki не індексує вміст логів, тільки етикетки (labels). Це робить його в рази дешевше в зберіганні та простішим у експлуатації. Ви платите обмеженою швидкістю full-text пошуку в тілі логу. Для більшості веб-застосунків цей компроміс виправданий.
Стек: Promtail (або Alloy) → Loki → Grafana
Розгортування через 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
# loki-config.yml
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
# promtail-config.yml
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
Отправка логів з застосунку
Для Laravel — прямо в Loki через HTTP API:
// 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]],
]],
];
// Fire and forget — не блокуємо запит
$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'),
],
],
],
LogQL — мова запитів Loki
LogQL схожа на PromQL. Основні паттерни:
# Усі помилки Laravel за останню годину
{job="laravel-app", level="error"} |= "Exception"
# Nginx 5xx
{job="nginx"} | json | status >= 500
# Частота помилок на хвилину
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])
)
Grafana: datasource та дашборд
Автопровізіювання datasource:
# grafana/provisioning/datasources/loki.yml
apiVersion: 1
datasources:
- name: Loki
type: loki
url: http://loki:3100
isDefault: true
jsonData:
maxLines: 1000
derivedFields:
- datasourceUid: prometheus
matcherRegex: "request_id=(\\w+)"
name: RequestID
url: '${__value.raw}'
Базовий дашборд включає:
-
Logs panel з фільтрацією за етикетками
levelтаjob -
Time series з
rate({job="laravel-app", level="error"}[1m]) - Stat panel — кількість помилок за останні 24 години
-
Table з топ-20 повідомлень про помилки через
count_over_time
Алертинг у Grafana
# Правило алерту на сплеск помилок
apiVersion: 1
groups:
- name: app-alerts
rules:
- uid: error-rate-spike
title: High error rate
condition: C
data:
- refId: A
queryType: ''
relativeTimeRange:
from: 300
to: 0
model:
expr: 'sum(rate({job="laravel-app", level="error"}[5m]))'
- refId: C
queryType: ''
model:
type: threshold
conditions:
- evaluator:
params: [0.1]
type: gt
noDataState: OK
execErrState: Error
for: 2m
annotations:
summary: "Error rate > 0.1/s for 2 minutes"
labels:
severity: warning
Порівняння з ELK
Loki виграє в стоімості зберігання (немає інвертованого індексу — зберігає тільки стиснуті чанки) та простоті експлуатації. ELK виграє при необхідності складного full-text пошуку та агрегації за полями логу без попереднього парсингу через labels.
Для більшості веб-застосунків з JSON-логами та Grafana як єдиним дашбордом — Loki краще.
Розклад
Розгортування Loki + Promtail + Grafana, налаштування збору Nginx та логів застосунку, базові дашборди та один алерт на критичні помилки: 1-2 робочі дні.







