Настройка логирования (Loki/Grafana) для веб-приложения

Наша компания занимается разработкой, поддержкой и обслуживанием сайтов любой сложности. От простых одностраничных сайтов до масштабных кластерных систем построенных на микро сервисах. Опыт разработчиков подтвержден сертификатами от вендоров.
Разработка и обслуживание любых видов сайтов:
Информационные сайты или веб-приложения
Сайты визитки, landing page, корпоративные сайты, онлайн каталоги, квиз, промо-сайты, блоги, новостные ресурсы, информационные порталы, форумы, агрегаторы
Сайты или веб-приложения электронной коммерции
Интернет-магазины, B2B-порталы, маркетплейсы, онлайн-обменники, кэшбэк-сайты, биржи, дропшиппинг-платформы, парсеры товаров
Веб-приложения для управления бизнес-процессами
CRM-системы, ERP-системы, корпоративные порталы, системы управления производством, парсеры информации
Сайты или веб-приложения электронных услуг
Доски объявлений, онлайн-школы, онлайн-кинотеатры, конструкторы сайтов, порталы предоставления электронных услуг, видеохостинги, тематические порталы

Это лишь некоторые из технических типов сайтов, с которыми мы работаем, и каждый из них может иметь свои специфические особенности и функциональность, а также быть адаптированным под конкретные потребности и цели клиента

Предлагаемые услуги
Показано 1 из 1 услугВсе 2065 услуг
Настройка логирования (Loki/Grafana) для веб-приложения
Средняя
~3-5 рабочих дней
Часто задаваемые вопросы
Наши компетенции:
Этапы разработки
Последние работы
  • image_website-b2b-advance_0.png
    Разработка сайта компании B2B ADVANCE
    1236
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1167
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    865
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1077
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    829
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Разработка веб-сайта для компании ФИКСПЕР
    844

Настройка логирования (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 ошибок по минутам
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 с фильтрацией по label level и job
  • Time series с rate({job="laravel-app", level="error"}[1m])
  • Stat panel — количество ошибок за последние 24 часа
  • Table с топ-20 сообщениями об ошибках через count_over_time

Alerting в 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 поиска, aggregation по полям лога без предварительного парсинга через labels.

Для большинства веб-приложений с JSON-логами и Grafana как единым дашбордом — Loki предпочтительнее.

Сроки

Развёртывание Loki + Promtail + Grafana, настройка сбора Nginx и application logs, базовые дашборды и один алерт на критические ошибки: 1-2 рабочих дня.