Logging Loki
Централизованное логирование — сбор логов приложений, контейнеров и инфраструктуры в единой системе. Оно позволяет искать события по всем экземплярам сервисов, сохранять логи после перезапуска, строить дашборды и связывать ошибки с метриками.
Типичный поток:
Приложение / контейнер → Promtail → Loki → GrafanaLoki — система агрегации логов от Grafana Labs. По модели она похожа на Prometheus: группирует данные по labels и поддерживает LogQL. Loki не строит полнотекстовый индекс для каждой строки: индексируются labels, а записи хранятся в сжатых блоках. Поэтому выбор labels особенно важен.
Содержание
- Централизованное логирование
- Loki и архитектура
- Потоки и labels
- Promtail
- LogQL
- Связь логов с метриками
- Grafana и алертинг
- Хранение и безопасность
- Практический пример
- Диагностика и best practices
Централизованное логирование
При локальном хранении логи находятся на конкретном сервере или внутри контейнера. При масштабировании приходится вручную искать нужный экземпляр сервиса. Если контейнер удалён, его логи могут быть потеряны.
Централизованная система:
- собирает логи из разных источников;
- предоставляет единый поиск;
- сохраняет данные после перезапуска сервисов;
- помогает сопоставлять события между компонентами;
- позволяет создавать панели и алерты;
- сокращает время диагностики.
Типичные компоненты:
- Источник — приложение, ОС, reverse proxy, база данных или контейнер.
- Агент — читает логи, добавляет labels и отправляет записи.
- Хранилище — принимает и хранит данные.
- Интерфейс — поиск, дашборды и алерты.
Для контейнеров предпочтительно писать логи в stdout и stderr, а доставку поручать платформе или агенту.
Структурированные логи
Для серверных приложений удобно использовать JSON:
{
"timestamp": "2026-09-21T14:32:10Z",
"level": "error",
"service": "orders",
"message": "database timeout",
"request_id": "req-8d91",
"trace_id": "abc123",
"duration_ms": 2500,
"status": 500
}Полезные поля:
| Поле | Назначение |
|---|---|
timestamp |
Время события, желательно UTC |
level |
debug, info, warn, error |
service |
Имя сервиса |
environment |
Окружение |
message |
Описание события |
request_id |
Идентификатор запроса |
trace_id |
Идентификатор трассировки |
duration_ms |
Продолжительность операции |
status |
HTTP-код или результат |
Не следует писать в логи пароли, токены, приватные ключи, Authorization, платёжные данные и другие чувствительные сведения. Маскирование на агенте полезно как дополнительный слой, но лучше не выводить секрет из приложения вообще.
Loki и архитектура
Loki принимает записи через HTTP API, группирует их по labels, сжимает и сохраняет в chunks. LogQL сначала выбирает потоки по labels, а затем фильтрует строки и извлекает поля.
Однопроцессный режим
Для разработки и небольших стендов:
Promtail → Loki → локальный дискПреимущества — простота и низкие требования. Недостатки — единственная точка отказа и риск потери данных при удалении volume.
Масштабируемый режим
Клиенты → Distributor → Ingester → объектное хранилище
▲
│
Grafana → Query Frontend → Querier| Компонент | Назначение |
|---|---|
| Distributor | Принимает и распределяет записи |
| Ingester | Формирует chunks и сохраняет данные |
| Querier | Выполняет запросы |
| Query Frontend | Планирует, разбивает и кеширует запросы |
| Compactor | Обслуживает индекс и retention |
| Ruler | Выполняет recording и alerting rules |
Процесс записи:
- Агент формирует batch.
- Loki принимает записи.
- Записи группируются по одинаковым labels.
- Ingester формирует chunks.
- Chunks и индекс сохраняются в хранилище.
Процесс чтения выполняется в обратном направлении: по labels выбираются потоки, читаются chunks, затем применяются фильтры, парсеры и агрегации.
Потоки и labels
Поток логов — набор записей с одинаковым набором labels:
{service="orders", environment="production", instance="orders-1"}Другой instance создаёт отдельный поток.
Хорошие labels
Обычно полезны labels с ограниченным количеством значений:
job
service
application
environment
cluster
namespace
pod
container
host
levelПример выборки:
{service="orders", environment="production"}Опасные labels
Не следует без необходимости добавлять значения с высокой кардинальностью:
request_id
trace_id
user_id
session_id
order_id
full_url
client_ip
timestampКаждое уникальное сочетание увеличивает число потоков и нагрузку на индекс. Такие значения лучше оставлять внутри JSON:
{service="orders"} | json | request_id="req-8d91"Promtail
Promtail — агент, который читает логи, обнаруживает источники, добавляет labels, выполняет pipeline stages и отправляет записи в Loki. Он умеет работать с файлами, Docker-контейнерами, Kubernetes pods, JSON, logfmt и регулярными выражениями. В новых системах также может применяться Grafana Alloy.
Минимальная конфигурация
server:
http_listen_port: 9080
grpc_listen_port: 0
positions:
filename: /var/lib/promtail/positions.yaml
clients:
- url: http://loki:3100/loki/api/v1/push
scrape_configs:
- job_name: application
static_configs:
- targets:
- localhost
labels:
job: application
service: orders
environment: production
__path__: /var/log/myapp/*.logpositions.filename хранит позицию чтения каждого файла. Его следует размещать в постоянном volume, иначе после перезапуска агент может повторно отправить старые записи.
Pipeline stages
Pipeline stages обрабатывают строку до отправки.
Разбор JSON:
pipeline_stages:
- json:
expressions:
timestamp: timestamp
level: level
service: service
message: message
- timestamp:
source: timestamp
format: RFC3339
- labels:
level:
service:Разбор logfmt:
pipeline_stages:
- logfmt:
mapping:
level:
service:
duration_ms:
message:
- labels:
level:
service:Маскирование токена:
pipeline_stages:
- replace:
expression: '(?i)(token=)[^&\\s]+'
replace: '${1}[REDACTED]'Сбор Docker-логов
scrape_configs:
- job_name: docker
docker_sd_configs:
- host: unix:///var/run/docker.sock
refresh_interval: 5s
relabel_configs:
- source_labels: [__meta_docker_container_name]
regex: '/(.*)'
target_label: container
- source_labels: [__meta_docker_container_label_com_docker_compose_service]
target_label: service
pipeline_stages:
- docker: {}Доступ к /var/run/docker.sock очень чувствителен: Docker API даёт широкие права управления Engine. Ограничивайте доступ, не публикуйте socket наружу и при необходимости используйте socket proxy или сбор файлов логов без Docker API.
LogQL
LogQL поддерживает:
- log queries — возвращают строки;
- metric queries — вычисляют временные ряды на основе логов.
Общий конвейер:
селектор потоков → фильтр строк → парсер → фильтр полей → агрегацияСелекторы labels
{service="orders"}{service="orders", environment="production"}Операторы:
| Оператор | Значение |
|---|---|
= |
Точное совпадение |
!= |
Не равно |
=~ |
Совпадение с регулярным выражением |
!~ |
Исключение по регулярному выражению |
Примеры:
{service=~"orders|payments"}{environment!="development"}Фильтрация срок
{service="orders"} |= "error"{service="api"} != "/health"{job="nginx"} |~ " 5[0-9]{2} "Если достаточно точного текста, лучше использовать |=, а не regexp.
Парсинг JSON и logfmt
{service="orders"}
| json
| level="error"{service="orders"}
| json
| duration_ms > 1000{service="orders"}
| json
| request_id="req-8d91"{service="orders"}
| logfmt
| level="error"Pattern parser для фиксированного текстового формата:
{job="nginx"}
| pattern `<ip> - - <_> "<method> <path> <_>" <status> <size>`
| status >= 500Проверка ошибок парсинга:
{service="orders"} | json | __error__ != ""Форматирование результата:
{service="orders"}
| json
| line_format `{{.level}} {{.request_id}} {{.message}}`Метрики из логов
Количество строк за пять минут:
count_over_time({service="orders"}[5m])Количество ошибок:
sum(count_over_time({service="orders"} |= "error" [5m]))Частота ошибок по сервисам:
sum by (service) (
rate({environment="production"} |= "error" [5m])
)Объём логов:
sum by (service) (
bytes_rate({environment="production"}[5m])
)Средняя продолжительность операции:
avg_over_time(
{service="orders"}
| json
| unwrap duration_ms
| __error__ = ""
[5m]
)95-й перцентиль:
quantile_over_time(
0.95,
{service="orders"}
| json
| unwrap duration_ms
| __error__ = ""
[5m]
)Для основных SLI лучше использовать нативные метрики приложения. Метрики из логов полезны как дополнение.
Связь логов с метриками
Метрики отвечают на вопрос «что происходит?», а логи — «почему это произошло?».
Пример расследования:
- На дашборде Prometheus выросла доля HTTP
5xx. - В Grafana выбирается тот же временной диапазон.
- Открываются логи соответствующего
serviceиenvironment. - Выполняется фильтрация по
level,statusили тексту ошибки. - По
request_idилиtrace_idнаходится конкретная операция.
Для корреляции используются общие labels:
service
environment
cluster
namespace
pod
instance
regionМетрика:
http_requests_total{service="orders", environment="production", status="500"}Поток Loki:
{service="orders", environment="production"}Grafana может передавать значения переменных из панели метрик в запрос Loki. Derived fields позволяют извлекать trace_id из логов и создавать ссылку в системе трассировки:
Метрика → Логи → TraceRecording rules
Частый тяжёлый запрос можно считать заранее:
groups:
- name: loki-rules
interval: 1m
rules:
- record: service:log_errors:rate5m
expr: |
sum by (service) (
rate({environment="production"} |= "error" [5m])
)Перед созданием правила проверьте, что фильтр действительно находит ошибки и не создаёт ложные совпадения.
Grafana и алертинг
Loki добавляется в Grafana как data source. В Docker Compose обычно используется адрес:
http://loki:3100В разделе Explore можно выбрать Loki, указать диапазон времени, выбрать labels, выполнить LogQL-запрос, просмотреть строки и переключиться к графику временного ряда.
Типы панелей:
- Logs — строки логов;
- Time series — частота ошибок и объём данных;
- Stat — текущее значение;
- Table — агрегации по сервисам;
- Bar gauge — сравнение компонентов.
Пример запроса с переменными дашборда:
{environment="$environment", service=~"$service"}
| json
| level=~"$level"Полезный дашборд содержит общий объём логов, частоту error и warn, HTTP 5xx, последние ошибки, p95 latency и ошибки разбора.
Алертинг по логам
Алерт должен использовать metric query:
sum(
rate(
{service="orders", environment="production"}
| json
| level="error"
[5m]
)
) > 0.1Правило можно выполнять через Loki Ruler и передавать в Alertmanager. Alertmanager группирует уведомления, маршрутизирует их и поддерживает silences.
Алерты по логам следует подтверждать метриками доступности: отсутствие логов может означать отказ самого приложения или агента, а не отсутствие ошибок.
Хранение и безопасность
Retention
Срок хранения выбирают по требованиям бизнеса, аудита, безопасности и стоимости. Пример для локального стенда:
limits_config:
retention_period: 168h168h — семь суток.
Локальный filesystem подходит для разработки. Для production обычно используют объектное хранилище, резервирование, compactor и lifecycle policy.
Упрощённая оценка объёма:
объём логов в сутки × срок хранения × коэффициент сжатия и служебных данныхРеальный объём удобно оценивать через bytes_rate.
Безопасность
Loki не следует публиковать без защиты. Используйте TLS, reverse proxy, аутентификацию, авторизацию, firewall или внутреннюю сеть, разграничение ролей Grafana и секрет-хранилище для credentials.
Пример защищённого клиента:
clients:
- url: https://logs.example.com/loki/api/v1/push
basic_auth:
username: promtail
password_file: /run/secrets/loki_password
tls_config:
ca_file: /etc/promtail/ca.crtАгенту нужны только права чтения журналов и отправки данных. Не следует использовать --privileged без обоснования.
Практический пример
Структура проекта:
logging-stack/
├── compose.yml
├── loki-config.yml
├── promtail-config.yml
└── logs/app.logcompose.yml
services:
loki:
image: grafana/loki:3
command: -config.file=/etc/loki/loki-config.yml
volumes:
- ./loki-config.yml:/etc/loki/loki-config.yml:ro
- loki-data:/loki
ports:
- "127.0.0.1:3100:3100"
promtail:
image: grafana/promtail:3
command: -config.file=/etc/promtail/promtail-config.yml
volumes:
- ./promtail-config.yml:/etc/promtail/promtail-config.yml:ro
- ./logs:/var/log/app:ro
- promtail-positions:/var/lib/promtail
depends_on:
- loki
grafana:
image: grafana/grafana:11
volumes:
- grafana-data:/var/lib/grafana
ports:
- "127.0.0.1:3000:3000"
depends_on:
- loki
volumes:
loki-data:
promtail-positions:
grafana-data:loki-config.yml
auth_enabled: false
server:
http_listen_port: 3100
common:
path_prefix: /loki
storage:
filesystem:
chunks_directory: /loki/chunks
rules_directory: /loki/rules
replication_factor: 1
ring:
instance_addr: 127.0.0.1
kvstore:
store: inmemory
schema_config:
configs:
- from: 2024-01-01
store: tsdb
object_store: filesystem
schema: v13
index:
prefix: index_
period: 24hКонфигурация предназначена для учебного стенда. В production нужны защищённый доступ, retention и подходящее долговременное хранилище.
promtail-config.yml
server:
http_listen_port: 9080
grpc_listen_port: 0
positions:
filename: /var/lib/promtail/positions.yaml
clients:
- url: http://loki:3100/loki/api/v1/push
scrape_configs:
- job_name: demo
static_configs:
- targets:
- localhost
labels:
job: demo
service: orders
environment: development
__path__: /var/log/app/*.log
pipeline_stages:
- json:
expressions:
timestamp: timestamp
level: level
- timestamp:
source: timestamp
format: RFC3339
- labels:
level:logs/app.log
{"timestamp":"2026-09-21T14:30:00Z","level":"info","request_id":"req-001","duration_ms":120,"status":200,"message":"order list returned"}
{"timestamp":"2026-09-21T14:31:00Z","level":"warn","request_id":"req-002","duration_ms":950,"status":200,"message":"slow database query"}
{"timestamp":"2026-09-21T14:32:00Z","level":"error","request_id":"req-003","duration_ms":2500,"status":500,"message":"database timeout"}Запуск:
docker compose up -d
docker compose ps
curl http://localhost:3100/readyGrafana будет доступна на http://localhost:3000. В качестве Loki data source укажите http://loki:3100.
Проверочные запросы:
{service="orders", environment="development"}{service="orders", environment="development"} | json | level="error"{service="orders", environment="development"} | json | duration_ms > 500Диагностика и best practices
Loki не готов
curl http://localhost:3100/ready
docker compose logs lokiПроверяйте YAML, права на volume, заполнение диска, схему хранения и доступность object storage.
Promtail не отправляет записи
docker compose logs promtailПроверьте путь __path__, права чтения, доступность Loki, positions file и pipeline stages. Убедитесь, что временные метки не слишком далеко от текущего времени.
В Grafana нет данных
Проверьте URL data source, Docker-сеть, диапазон времени и labels. Начните с запроса:
{job=~".+"}Затем постепенно сужайте его.
Повторяющиеся записи
Причинами могут быть потеря positions file, два агента для одного файла, одновременная отправка через агент и logging driver или неправильная обработка ротации файлов.
Медленные запросы
Уменьшите диапазон времени, добавьте точные labels, примените ранний |=, избегайте избыточных regexp, проверьте кардинальность и вынесите повторяющиеся агрегации в recording rules.
Best practices
- Используйте структурированный JSON-формат.
- Пишите контейнерные логи в
stdoutиstderr. - Применяйте UTC и единые имена полей.
- Добавляйте
request_idиtrace_id. - Не записывайте секреты и лишние персональные данные.
- Используйте labels с низкой кардинальностью.
- Не делайте
request_id,trace_idиuser_idlabels. - Храните positions file в постоянном volume.
- Тестируйте pipeline stages на реальных строках.
- Защищайте Loki TLS и аутентификацией.
- Для production используйте долговременное объектное хранилище.
- Настройте retention и лимиты.
- Мониторьте Loki, Promtail и задержку доставки.
- Ограничивайте период и размер запросов.
- Используйте
|=вместо regexp, если этого достаточно. - Для основных SLI применяйте нативные метрики, а логи используйте для контекста.
- Проверяйте алерты контролируемыми тестовыми событиями.
Краткая шпаргалка LogQL
{service="orders"}{service="orders"} |= "error"{service="orders"} | json | level="error"{service="orders"} | json | duration_ms > 1000count_over_time({service="orders"}[5m])sum by (service) (rate({environment="production"} |= "error" [5m]))sum by (service) (bytes_rate({environment="production"}[5m])){service="orders"} | json | __error__ != ""Итог
Loki строит централизованное логирование вокруг потоков и labels. Promtail обнаруживает источники, обрабатывает записи и отправляет их в Loki. LogQL используется для поиска, разбора JSON и вычисления метрик из логов. Grafana связывает логи с метриками и трассировками.
Главные правила: использовать структурированные записи, не помещать секреты в логи, контролировать кардинальность labels, хранить идентификаторы корреляции внутри событий, защищать хранилище и мониторить весь конвейер доставки.