Настройка Grafana Loki для сбора логов

Современные информационные системы генерируют большое количество отладочной информации, которую желательно хранить какое-то время. Я расскажу, как установить и настроить  Grafana Loki - комплексную систему для сбора и хранения логов. Это современное, эффективное и технически развитое решение, которые покрывает большинство потребностей из этой технической области.

Углубленный онлайн-курс по MikroTik

Научиться настраивать MikroTik с нуля или систематизировать уже имеющиеся знания можно на углубленном онлайн-курcе по администрированию MikroTik. Автор курcа – сертифицированный тренер MikroTik Дмитрий Скоромнов. Более 40 лабораторных работ по которым дается обратная связь. В три раза больше информации, чем в MTCNA.
Реклама ИП Скоромнов Д.А. ИНН 331403723315

Введение

Grafana Loki - это в первую очередь сервис по сбору, анализу и хранению логов. Непосредственно служба для обработки и хранения называется Loki, а Grafana в данном случае - название компании, которое совпадает с изначальным их продуктом - панелью для визуализации метрик. В итоге у нас получается сервис для приёма и хранения логов - Loki и панель визуализации - Grafana.

Я не буду тратить своё и ваше время на описание продукта, так как это легко сделает любой ИИ и скорее всего не ошибётся. Сфокусируюсь на конкретных примерах настроек, которые я проверю и изложу, чтобы вы могли взять, повторить и быть уверенным в том, что это решение работает. Тут на ИИ пока не всегда можно положиться, так как он предложенные варианты выдумывает, но не проверяет. Пока не проверяет. Думаю, со временем будет.

Все компоненты системы, кроме сборщиков логов на хостах, я буду запускать в контейнерах Docker. Это необязательное условие, но считаю, что так проще и удобнее в большинстве случаев. Если вам это не подходит, то нет особых проблем запускать всё вручную, скачивая бинарники или готовые пакеты. Основное время на запуск итоговой системы уходит на отладку конфигураций, а не саму установку служб.

Для установки Docker на сервер я предпочитаю использовать вот такую простую команду. Привожу просто для справки, возможно кто-то не знает. Способов установить Docker много.

# curl https://get.docker.com | bash -

Все дальнейшие настройки будут проходить на системе Debian. Принципиальной разницы между разными дистрибутивами не будет. Конфигурация везде плюс-минус одинаковая.

Установка Loki

Выполним установку Loki с помощью Docker Compose. Я возьму стандартную конфигурацию с хранением логов в локальной директории сервера. Это наиболее простой способ хранения логов, который доступен всем и везде. Достаточно выделить виртуальной машине дополнительное место для логов или отдельный диск, раздел.

Создаём директорию для конфигурации Loki и в ней файл compose.yaml:

# mkdir ~/loki && cd ~/loki && touch compose.yaml
services:
  loki:
    container_name: loki
    image: docker.io/grafana/loki:3.6
    command: "-config.file=/etc/loki/config.yaml"
    ports:
      - "3100:3100"
    volumes:
      - ./config/config.yaml:/etc/loki/config.yaml:ro
      - /mnt/loki:/loki:rw
    restart: unless-stopped

На что тут обратить внимание:

  • loki:3.6 - актуальная версия loki на момент написания статьи, уточнить версию можно в репозитории
  • config.yaml - файл конфигурации самого Loki, настроим далее
  • /mnt/loki - раздел, где будут храниться логи, не забудьте его создать с правильными правами для доступа из контейнера
# mkdir /mnt/loki
# chown -R 10001:10001 /mnt/loki
# chmod 755 /mnt/loki

Раздел для хранения логов у меня вынесен в /mnt/loki и подключается через механизм Bind Mount. Это удобнее для запуска в проде, так как можно легко вынести хранение на отдельный раздел и диск со своими настройками и файловой системой. Но это неудобно с точки зрения переносимости конфигурации на другой хост. Надо не забывать указывать актуальную директорию. Если вы используете Loki для запуска в тестовой среде или для очень маленького проекта, то удобнее использовать Volumes. Примерно так:

services:
  loki:
    container_name: loki
    image: docker.io/grafana/loki:3.6
    command: "-config.file=/etc/loki/config.yaml"
    ports:
      - "3100:3100"
    volumes:
      - ./config/config.yaml:/etc/loki/config.yaml:ro
      - data_loki:/loki:rw
    restart: unless-stopped

volumes:
  data_loki:
    driver: local

Как запускать вам, решайте у себя по месту. Создадим конфигурацию для Loki:

# mkdir ~/loki/config && cd ~/loki/config && touch config.yaml
---
auth_enabled: false

server:
  http_listen_port: 3100

common:
  instance_addr: 127.0.0.1
  path_prefix: /loki
  storage:
    filesystem:
      chunks_directory: /loki/chunks
      rules_directory: /loki/rules
  replication_factor: 1
  ring:
    kvstore:
      store: inmemory

schema_config:
  configs:
    - from: 2020-10-24
      store: tsdb
      object_store: filesystem
      schema: v13
      index:
        prefix: index_
        period: 24h

compactor:
  working_directory: /loki/compactor
  retention_enabled: true
  delete_request_store: filesystem
  compaction_interval: 10m
  retention_delete_delay: 2h
  retention_delete_worker_count: 150

limits_config:
  retention_period: 720h
  ingestion_rate_mb: 4
  ingestion_burst_size_mb: 6
  max_streams_per_user: 10000
  max_line_size: 256000

ruler:
  alertmanager_url: http://localhost:9093

Здесь обращаем внимание на следующие параметры:

  • auth_enabled: false - аутентификация на уровне Loki отключена, если она будет нужна, то можно настроить доступ через reverse-proxy и сделать аутентификацию на ней
  • replication_factor: 1 - каждый чанк логов хранится в единственном числе, что логично для установки в единственном экземпляре с локальных хранением
  • from: 2020-10-24 - формат схемы хранения, не знаю, что конкретно обозначает эта дата, но в документации по умолчанию используется эта схема
  • prefix: index_ и period: 24h - данные хранятся в индексах с указанным префиксом, а индексы создаются раз в сутки, при очистке старых данных удаление происходит суточными индексами
  • retention_period: 720h - хранения логов 720 часов = 30 дней
  • ingestion_rate_mb: 4 и ingestion_burst_size_mb: 6 - максимальная скорость приёма логов 4 МБ/с на пользователя с разрешённым всплеском до 6 МБ/с
  • max_line_size: 256000 - максимальный размер одной строки лога ~256 КБ

Отмечу, что в одно и то же хранилище могут писать несколько экземпляров Loki. Префиксы индексов нужны в том числе для того, чтобы различать логи от разных экземпляров.

Compose файл готов, конфигурация тоже. Можно запускать Loki. Для этого будучи в директории с файлом compose.yaml выполните:

# docker compose up -d

После запуска сразу же проверим, отвечает ли сервис на настроенном порту:

# curl http://192.168.137.29:3100
404 page not found

Установка Loki

В данном случае ответ 404 подходит, так как даёт его сервис Loki в контейнере, а значит он как минимум запущен и отвечает на запросы. Мы отправили неверный запрос, поэтому и ответ 404.

На этом установка и настройка базовой конфигурации Loki завершена. Двигаемся дальше.

Установка Grafana

Для взаимодействия с логами в Loki нам понадобится установить веб панель Grafana. Никаких особых требований к настройке нет, поэтому запустим с базовой минимальной конфигурацией. Её можно было бы запустить в одном compose файле с Loki, но я для удобство запускаю по отдельности, потому что есть высокая вероятность, что запущены они будут на разных серверах, так как Grafana чаще всего связана ещё и с мониторингом. Архитектурно нет никакого смысла запускать её вместе с Loki. Но на небольших или тестовых проектах их можно объединить.

Создаём compose.yaml для Grafana:

# mkdir ~/grafana && cd ~/grafana && touch compose.yaml
services:
  grafana:
    image: docker.io/grafana/grafana-oss:12.3.2
    container_name: grafana
    ports:
      - "3000:3000"
    volumes:
      - grafana-data:/var/lib/grafana
    restart: unless-stopped
volumes:
  grafana-data:
    driver: local

Запускаем:

# docker compose up -d

Установка Grafana

Идём в браузере в веб интерфейс на порт 3000. Учётная запись по умолчанию в Grafana - admin / admin.

Логин в Grafana

Первым делом нам нужно будет добавить источник данных в виде нашего Loki. Для этого идём в раздел ConnectionsData sourcesAdd data source. Выбираем Loki. Указываем url, остальное можно не трогать. В моём случае это http://192.168.137.29:3100/. Мотаем страничку в самый низ и нажимаем Save & Test. Ошибок быть не должно.

Настройка Loki Data sources в Grafana

Grafana тоже установили и настроили в связке с Loki. Можно передавать логи в хранилище.

Настройка Alloy для текстовых логов Linux и Journald

Актуальным и рекомендуемым Графаной на момент написания статьи способом передачи различной информации в Loki является их же агент Alloy, который нужно установить на конечный сервер, с которого надо забирать данные. Это могут быть не только логи, но и метрики мониторинга, трейсы и некоторые другие вещи. В рамках данной статьи я рассмотрю вариант только с логами.

Настроим Alloy для отправки системных логов Linux в Loki. Покажу сразу в одном примере и отправку традиционных текстовых логов от syslog, которые хранятся в /var/log, и журналов journald, компонента логирования systemd.

Логи могут собираться не только с хостов, где установлен Docker, поэтому в своём примере выполню установку из пакета, собранного под все популярные системы Linux. Если у вас нет блокировки доступа к официальному репозиторию Grafana, то можете подключить его к своей системе Debian/Ubuntu:

# mkdir -p /etc/apt/keyrings
# wget -O /etc/apt/keyrings/grafana.asc https://apt.grafana.com/gpg-full.key
# chmod 644 /etc/apt/keyrings/grafana.asc
# echo "deb [signed-by=/etc/apt/keyrings/grafana.asc] https://apt.grafana.com stable main" > /etc/apt/sources.list.d/grafana.list

Установку под другие системы или другим способом можно посмотреть в официальной документации. Там всё подробно расписано.

Если же вы из России, то доступ к репозиторию скорее всего будет заблокирован, но можно воспользоваться любым зеркалом. Например, от Яндекса:

# echo "deb [trusted=yes] https://mirror.yandex.ru/mirrors/packages.grafana.com/oss/deb/ stable main" > /etc/apt/sources.list.d/grafana.list

Устанавливаем Alloy:

# apt update
# apt install alloy

Для сбора системных логов из /var/log и journald настройте следующую конфигурацию в файле /etc/alloy/config.alloy:

// SECTION: TARGETS

loki.write "default" {
	endpoint {
		url = "http://192.168.137.30:3100/loki/api/v1/push"
	}
	external_labels = {}
}

// !SECTION

// SECTION: SYSTEM LOGS & JOURNAL

loki.source.journal "journal" {
  max_age       = "24h0m0s"
  relabel_rules = discovery.relabel.journal.rules
  forward_to    = [loki.write.default.receiver]
  labels        = {component = string.format("%s-journal", constants.hostname)}
  path          = "/var/log/journal" 
}

local.file_match "system" {
  path_targets = [{
    __address__ = "localhost",
    __path__    = "/var/log/{syslog,messages,*.log}",
    instance    = constants.hostname,
    job         = string.format("%s-logs", constants.hostname),
  }]
}

discovery.relabel "journal" {
  targets = []
  rule {
    source_labels = ["__journal__systemd_unit"]
    target_label  = "unit"
  }
  rule {
    source_labels = ["__journal__boot_id"]
    target_label  = "boot_id"
  }
  rule {
    source_labels = ["__journal__transport"]
    target_label  = "transport"
  }
  rule {
    source_labels = ["__journal_priority_keyword"]
    target_label  = "level"
  }
}

loki.source.file "system" {
  targets    = local.file_match.system.targets
  forward_to = [loki.write.default.receiver]
}

// !SECTION

Здесь вам возможно нужно будет изменить следующие параметры:

  • url = "http://192.168.137.30:3100/loki/api/v1/push" - адрес сервера Loki
  • __path__ = "/var/log/{syslog,messages,*.log}" - перечисление логов, которые будут отправляться, в данном случае файлы syslog и messages, плюс все файлы с окончанием .log
  • labels = {component = string.format("%s-journal", constants.hostname)} - назначение метки в виде hostname-journal, то есть имя хоста и через тире добавление journal
  • job = string.format("%s-logs", constants.hostname) - метка job с именем хоста и приставкой logs, чтобы удобно было отличать от логов journald

Остальные параметры можно не трогать. Перезапускаем Alloy с новой конфигурацией и добавляем в автозагрузку:

# systemctl restart alloy
# systemctl enable alloy

Переходим в веб интерфейс Grafana, в раздел DrilldownLogs. Так как это наш первый сборщик логов, то там мы увидим 2 наборов логов под названием debian13-logs и debian13-journal.

Настройка Alloy для текстовых логов Linux и Journald

В данном случае debian13 - имя сервера. На этом сервере одновременно записываются логи и в /var/logs, и в journald.

Выбрав в разделе Service в выпадающем списке один из источников логов, можно детально изучить их, используя выборки по различным меткам. Например, по имени файла с логом.

Просмотр системных логов в Loki

Настройка Promtail для сбора логов

Для полноты картины покажу, как сбор этих же логов из /var/log и journald можно настроить с помощью более старого и легковесного продукта Promtail, который в настоящее время объявлен устаревшим и не развивается, но до сих пор продолжает нормально работать в качестве сборщика логов.

Подключаем репозиторий Grafana, как показано в предыдущем разделе и устанавливаем Promtail:

# apt install promtail

Формируем для него конфигурационный файл /etc/promtail/config.yml:

server:
  http_listen_port: 9080
  grpc_listen_port: 0

positions:
  filename: /tmp/positions.yaml

clients:
- url: http://192.168.137.29:3100/loki/api/v1/push

scrape_configs:
- job_name: logs
  static_configs:
  - targets:
      - localhost
    labels:
      host: ${HOSTNAME}
      job: ${HOSTNAME}-logs
      __path__: '/var/log/{syslog,*.log}'

- job_name: journal
  journal:
    max_age: 24h
    labels:
      host: ${HOSTNAME}
      job: ${HOSTNAME}-journal
  relabel_configs:
    - source_labels: ['__journal__systemd_unit']
      target_label: 'unit'
    - source_labels: ['__journal__hostname']
      target_label: 'journal_host'
    - source_labels: ["__journal__boot_id"]
      target_label: 'boot_id'
    - source_labels: ["__journal_priority_keyword"]
      target_label: 'level'
    - source_labels: ["__journal__transport"]
      target_label: 'transport'

Я в конфигурации использую переменную окружения ${HOSTNAME}, с помощью которой передаю имя сервера в метки. По умолчанию promtail не читает переменные окружения. Чтобы это исправить, надо явно в юнит systemd передать эту переменную и указать для promtail использование внешних переменных. Для этого есть отдельный параметр запуска -config.expand-env=true. Добавляем его. Для этого правим юнит systemd:

# systemctl edit promtail

Добавляем туда строки:

[Service]
Environment=HOSTNAME=%H
ExecStart=
ExecStart=/usr/bin/promtail -config.file /etc/promtail/config.yml -config.expand-env=true

 

Настройка systemd юнита для Promtail

Перечитываем конфигурацию systemd:

# systemctl daemon-reload

Добавляем пользователя promtail, от которого работает сборщик логов, в группу adm, чтобы он имел к ним доступ:

# usermod -aG adm promtail
# usermod -aG systemd-journal promtail

Перезапускаем promtail:

# systemctl restart promtail

Настройка Promtail

У вас не должно быть ошибок. Если ошибки есть и служба promtail не запускается, смотрите системный лог. Там будут видны ошибки. Я в процессе настройки и отладки этой конфигурации сначала получал ошибки на неправильное форматирование yaml файла:

promtail[17820]: Unable to parse config: /etc/promtail/config.yml: yaml: line 27: did not find expected key.

Исправил это. А потом получил ошибку на отсутствие доступа к лог-файлам:

promtail[17820]: Unable to parse config: /etc/promtail/config.yml: yaml: line 27: did not find expected key.

После того, как выдал права, системные логи Linux отправились от Promtail в Loki. Теперь у меня там логи от Debian 13, отправленные через Alloy, и от Ubuntu 24, отправленные через Promtail.

Логи Linux в Loki

Отлично. Базовую настройку Loki и отправку туда системных логов Linux мы настроили, используя при этом разные программы - Alloy и Promtail. Переходим к отправке в Loki других видов логов.

Сбор логов Docker

Отправить в Loki логи Docker можно разными способами. Например, это можно сделать в том числе с помощью Promtail или Alloy, про которые я написал выше. В этом разделе я покажу самый простой и быстрой способ - с помощью Docker Driver. Устанавливаем его:

# docker plugin install grafana/loki-docker-driver:3.6.0-amd64 --alias loki --grant-all-permissions

Проверяем, что он установился:

# docker plugin ls

Отправка логов Docker в Loki

Далее у нас есть 2 пути:

  1. Можно глобально на хосте для службы Docker указать отправку логов в Loki для всех контейнеров.
  2. Для каждого отдельного контейнера при создании указать, куда отправлять его логи.

Мне лично видится более удобным второй способ, так как он позволяет гибко управлять отправкой логов в каждом конкретном случае. Для этого достаточно в настройках compose.yaml добавить секцию с logging и указать там параметры для драйвера loki. Выглядит примерно так:

services:
 grafana:
  image: docker.io/grafana/grafana-oss:12.3.2
  container_name: grafana
  logging:
   driver: loki
   options:
    loki-url: "http://192.168.137.29:3100/loki/api/v1/push"
    loki-retries: 2
    loki-max-backoff: 800ms
    loki-timeout: 1s
    keep-file: "true"
    mode: "non-blocking"
  ports:
   - "3000:3000"
  volumes:
   - grafana-data:/var/lib/grafana
  restart: unless-stopped
volumes:
 grafana-data:
  driver: local

Здесь мы для Docker контейнера с Grafana указали, что логи необходимо отправлять на наш сервер Loki. Дополнительные параметры указаны для того, чтобы не было блокировки запуска контейнера, если сервер с логами будет недоступен. Подробное описание параметров можно посмотреть в документации, не буду на этом останавливаться.

Если контейнеры у вас уже созданы без настроек логирования, то для их добавления контейнеры придётся пересоздать.

Для отправки логов от всех контейнеров Docker конкретного хоста в Loki, добавьте в файл конфигурации /etc/docker/daemon.json параметры:

{
  "debug": true,
  "log-driver": "loki",
  "log-opts": {
    "loki-url": "http://192.168.137.29:3100//loki/api/v1/push",
    "loki-batch-size": "400"
  }
}

После этого службу Docker надо перезапустить. Если файла daemon.json по указанному пути нет, то просто создайте его.

Сбор логов Windows в Loki

Настроим сбор стандартных журналов Windows в Loki. Для этого я буду использовать всё тот же Alloy. Для Windows проще всего скачать установщик из официального репозитория. Нужен будет файл alloy-installer-windows-amd64.exe.

Установка Alloy в Windows

Alloy установится как служба с автоматическим запуском.

Служба Alloy в Windows

Файл конфигурации config.alloy располагается в директории C:\Program Files\GrafanaLabs\Alloy\. В нём нужно явно указать все журналы, которые вы хотите собирать. Ниже привожу пример со сбором стандартных журналов System, Application и Security.

logging {
  level = "info"
}

// SECTION: TARGETS

loki.write "default" {
    endpoint {
        url = "http://192.168.137.30:3100/loki/api/v1/push"
    }
    external_labels = {}
}

// !SECTION

// SECTION: WINLOGS

loki.source.windowsevent "system"  {
    eventlog_name = "System"
    use_incoming_timestamp = true
    bookmark_path = "C:/ProgramData/GrafanaLabs/alloy/system.xml"
    labels                 = {
      instance = constants.hostname,
      job      = string.format("%s-system", constants.hostname),
    }

    forward_to = [loki.write.default.receiver]
}

loki.source.windowsevent "application"  {
    eventlog_name = "Application"
    use_incoming_timestamp = true
    bookmark_path = "C:/ProgramData/GrafanaLabs/alloy/application.xml"
    labels                 = {
      instance = constants.hostname,
      job      = string.format("%s-application", constants.hostname),
    }

    forward_to = [loki.write.default.receiver]
}

loki.source.windowsevent "security"  {
    eventlog_name = "Security"
    use_incoming_timestamp = true
    bookmark_path = "C:/ProgramData/GrafanaLabs/alloy/security.xml"
    labels                 = {
      instance = constants.hostname,
      job      = string.format("%s-security", constants.hostname),
    }

    forward_to = [loki.write.default.receiver]
}

// !SECTION

Особое внимание, как обычно, стоит уделить меткам. Когда логов много, нужно иметь возможность быстро находить то, что вам нужно. Я здесь каждому логу добавил две метки:

  • instance = "имя сервера", например WIN-VN3PJCM08OH.
  • job = "имя сервера"-"имя журнала", то есть WIN-VN3PJCM08OH-system.

Отправка логов Windows в Loki

Достаточно изменить файл config.alloy и перезапустить службу. Windows логи отправятся в Loki. Отлаживать конфигурацию Alloy неудобно. Если что-то не так, то служба просто не будет запускаться, выдавая в системный лог неинформативную ошибку. ИИшки тут мне несильно помогли, так как они то и давали конфигурацию, которая не запускалась. Пришлось вручную разобраться и сделать нормальную работающую версию.

В общем плане настройка сбора логов из Windows в Loki проста. По сути их нужно только отправить, а дальше Loki сам всё распарсит и выделит стандартные поля event_id, channel, level, security_userId, timeCreated и т.д. По ним можно будет делать выборку.

Просмотр Windows логов в Loki

Нам по умолчанию становятся доступны все фильтры, что и в стандартной оснастке для просмотра логов в системе Windows.

Приём Syslog логов

У Loki нет прямого приёмника для syslog. Нужно будет воспользоваться промежуточным агентом для приёма и отправки логов. Тут есть несколько вариантов:

  • Grafana Alloy
  • Promtail
  • Vector, Fluent Bit или любой другой похожий сторонний софт
  • Сбор логов в текстовый файл с помощью rsyslog и отправка в Loki, как я показывал ранее.

В целом, все эти способы имеют право на жизнь. Выбирать стоит в зависимости от вашей инфраструктуры. Проще всего конфигурация будет у Promtail, но так как он уже объявлен deprecated, я покажу пример с Alloy и Vector. Собирать логи буду с Mikrotik, а сборщики установлю на один из хостов с Linux. Можно взять тот же сервер, где работает Loki. Разницы нет, выбирайте, как вам удобнее.

Про установку Alloy я уже рассказывал ранее, так что не буду на этом останавливаться. Рисуем ему следущую конфигурацию:

loki.write "default" {
    endpoint {
        url = "http://192.168.137.30:3100/loki/api/v1/push"
    }
    external_labels = {}
}

loki.source.syslog "syslog_in" {
  listener {
    address = "0.0.0.0:1514"
    protocol = "udp"
    labels = { job = "syslog" }
  }
  forward_to = [loki.process.syslog_process.receiver]
}

loki.process "syslog_process" {
  stage.labels {
    values = {
      remote_ip = "__syslog_connection_ip_address",
      hostname = "__syslog_message_hostname",
      app = "__syslog_message_app_name",
    }
  }

  forward_to = [loki.write.default.receiver]
}

По умолчанию Alloy умеет парсить логи формата RFC5424. А Mikrotik и некоторые другие сетевые устройства используют более старый формат RFC3164. Если у вас такое устройство, то нужно будет добавить в конфигурацию пару дополнительных параметров в раздел loki.source.syslog и заодно я сразу отдельную метку Mikrotik добавил, так как эта конфигурация для него:

loki.source.syslog "syslog_in" {
  listener {
    address = "0.0.0.0:1514"
    protocol = "udp"
    syslog_format = "rfc3164"
    rfc3164_default_to_current_year = true
    labels = { job = "syslog", device = "Mikrotik"}
  }
  forward_to = [loki.process.syslog_process.receiver]
}

Остальное можно не менять, но нужно понимать, что автоматическое назначение меток для формата RFC3164 работать не будет, так как Alloy его не распарсит.

Перезапускаем Alloy и идём настраивать Mikrotik на отправку своих логов.

# systemctl restart alloy

Переходим в раздел SystemLoggingActions и добавляем новое действие. Я назвал его Loki, указал адрес и порт сервера, где установлен Alloy, в качестве Remote Log Format выбрал BSD Syslog. После этого в разделе Rules добавил новое правило для логов System действие - Loki.

Отправка Syslog логов в Mikrotik

Всё, в Микротике больше ничего делать не надо. Можно идти в Loki и смотреть новые логи. Если их там нет, то посмотрите системный лог на сервере с Alloy. Но по умолчанию в syslog или journald пишет. Если есть ошибки, вы их там увидите.

В Loki логи от Микротика выглядят примерно так:

Приём Syslog логов в Loki

Содержимое логов присутствует, но работать с ними не удобно, так как нет метаданных об отправителе, имени устройства и т.д. Если в один приёмник будут отправлять логи разные устройства, всё это превратится в общую портянку, где трудно будет найти что-то конкретное. Эту проблему можно решить средствами Alloy, например, добавив в обработку парсинг с помощью regex, но это хлопотно и трудоёмко. Я покажу решение гораздо проще и универсальнее, где всё это будет выполняться автоматически.

Для приёма и отправки логов формата syslog воспользуемся программой Vector. Ставим её в систему из пакетов:

# bash -c "$(curl -L https://setup.vector.dev)"
# apt install vector

Первая команда использует скрипт для подключения репозиториев. Вы можете использовать любой способ установки. В документации разработчиков их представлено много. Рисуем конфигурацию для Vector в файле /etc/vector/vector.yaml:

sources:
  syslog_udp:
    type: syslog
    address: 0.0.0.0:2514
    mode: udp

sinks:
  syslog_to_loki:
    type: loki
    inputs:
      - syslog_udp
    compression: none
    encoding:
      codec: json
    endpoint: http://192.168.137.30:3100
        healthcheck: false
    buffer:
      type: disk
      max_size: 1073741824 # 1GB
    labels:
      job: syslog

Она универсальная и подойдёт для логов любого формата. Перезапускаем Vector и добавляем в автозапуск:

# systemctl restart vector
# systemctl enable vector

Не забудьте на Mikrotik поменять порт с 1514, который настроен в Alloy на 2514, как в конфигурации Vector. Смотрим на те же самые логи в Loki, но отправленные вектором.

Логи Mikrotik в Loki

У нас все основные поля распарсены. Мы можем по ним делать выборку, настраивать удобное представление логов. Например, вот так.

Парсинг микротик логов

Я отфильтровал записи по конкретному устройству и настроил отображение в логе имени устройства, его IP, название приложения и сам текст сообщения. Такое представление удобнее и нагляднее того, что предлагает Alloy. Это благодаря тому, что Vector автоматически парсит любой формат syslog и сразу трансформирует его в json. А с json в Loki автоматически работают любые группировки и поиск.

Сбор логов веб сервера Nginx и Angie в Loki

Рассмотри ещё один практический пример. Соберём логи веб сервера и сделаем для них дашборд. В качестве веб сервера возьмём наиболее популярные Nginx/Angie. В данном примере их настройка будет идентична. Для того, чтобы показать не готовое решение, а научить вас самих собирать дашборды, я вначале возьму генератор логов веб сервера и покажу на его примере. А потом адаптируем настройки логов реального веб сервера под получившийся пример.

В качестве генератора логов я возьму простую утилиту flog. Она может создавать логи веб сервера в наиболее популярных форматах, с заданной интенсивностью и максимальным размером. Для отладки хранилища логов отличное и простое решение.

# wget https://github.com/mingrammer/flog/releases/download/v0.4.4/flog_0.4.4_linux_amd64.tar.gz
# tar -xzf flog_0.4.4_linux_amd64.tar.gz flog
# mv flog /usr/local/bin/

Теперь сгенерируем логи в формате json. Для Loki удобнее всего генерировать логи именно в этом формате, так как он существенно упрощает дальнейший парсинг. Фактически он будет происходить автоматически. Можно разово сделать лог файл размером 10 мегабайт:

# flog -t log -f json -o /var/log/nginx/access.log -b 10485760

Это не очень удобно для нашей задачи, так как время всех событий в логе будет идентичное. Удобнее запустить непрерывную генерацию с заданным интервалом. В данном случае - 300 мс.

# flog -t log -f json -o /var/log/nginx/access.log -b 10485760 -d 300ms -w

Можно в одном терминале запустить генерации и оставить. А в другом работать с настройкой. Формат логов будет вот такой:

{"host":"208.71.182.144", "user-identifier":"gutmann7885", "datetime":"13/Apr/2026:10:57:27 +0300", "method": "HEAD", "request": "/recontextualize", "protocol":"HTTP/1.0", "status":302, "bytes":5054, "referer": "https://www.humanefficient.io/e-markets/brand/killer/e-commerce"}

По получаемым данным этот формат соответствует типу логов apache_combined. Но из-за того, что у нас выбран формат json, названия полей могут различаться в зависимости от настроек, которые можно указывать вручную. Я имею ввиду названия полей host, datetime, request и т.д.

Для данного формата лога имеет смысл сразу же назначить метки некоторым полям, по которым потом можно будет делать группировки. Это ускорит данную процедуру и уменьшит потребление ресурсов на неё. Рисуем конфигурацию для alloy, которая будет передавать логи в Loki.

loki.write "default" {
    endpoint {
        url = "http://192.168.137.30:3100/loki/api/v1/push"
    }
    external_labels = {}
}

local.file_match "web_logs" {
  path_targets = [
    {
        __path__    = "/var/log/nginx/access.log",
        instance    = constants.hostname,
        job         = "nginx",
        service     = "web",
  },

  {
        __path__    = "/var/log/angie/access.log",
        instance    = constants.hostname,
        job         = "angie",
        service     = "web",
  },
 ]
}

loki.source.file "web_logs" {
  targets    = local.file_match.web_logs.targets
  forward_to = [loki.process.nginx_pipeline.receiver]
}

loki.process "nginx_pipeline" {

forward_to = [loki.write.default.receiver]

    stage.json {
        expressions = {
            request = "request",
            status = "status",
            method = "method",
            host = "host",
        }
    }

    stage.labels {
        values = {
            request = "",
            status = "",
            method = "",
            host = "",
        }
    }
}

Мне приходится работать с обоими веб серверами, поэтому сразу оба добавил в конфигурацию. Если вам какой-то не нужен, просто удалите строки с ним. Я добавил метки на следующие поля:

  • request - url запроса, например /deploy/innovative
  • status - код ответа, например, 200, 404, 502 и т.д.
  • method - метод запроса, например, GET, PUT и т.д.
  • host - IP адрес клиента, отправившего запрос

Смотрим на эти логи в Grafana:

Сбор логов веб сервера в Loki

Видим настроенные индексированные поля. Они нужны будут для дашборда. Без них некоторые выборки будут очень ресурсоёмки. Переходим к созданию своего дашборда.

Создание дашбордов для логов

Изначально я ожидал увидеть готовые дашборды для логов веб сервера Nginx. Но я их не нашёл. Пришлось разбираться самому с нуля и делать свой. Для этого сразу же привлёк на помощь ИИ. Они сильно упрощают решение однотипных задач. А рисование дашборда таковой и является. Другое дело, что сразу получить готовое работающее решение у меня не получилось. Пришлось потом дорабатывать и изменять то, что нагенерилили ИИ.

У меня получилась в итоге вот такая панель:

Dashboard для Nginx в Loki

Добавил в неё тот набор визуализаций, что мне показались полезными для быстрого анализа состояния веб сервера в плане запросов к нему. Тут отображены следующие значения:

  • Количество запросов в секунду (RPC)
  • Исходящий трафик веб сервера (bytes/sec)
  • Количество 400-х и 500-х ошибок
  • Коды ответа веб сервера
  • Типы запроса к веб серверу
  • ТОП 20 IP клиентов с запросами к веб севреру
  • ТОП 20 урлов, к которым выполнялись запросы

Последние 2 метрики в зависимости от объёма логов, временного интервала и настройки max_query_series в Loki могут то работать, то нет. Они очень затратны по ресурсам, так как на лету выполняют вычисления по всему объёму логов. Если вам они сильно не нужны, то лучше отключить. Но иногда они бывают полезны, поэтому я оставил. На небольших временных интервалах должны работать.

В целом, ничего сложного в построении дашбордов нет. Нужен опыт, так как очень много всевозможных настроек и преобразований в визуализации. Когда первый раз разбираешься, приходится долго пробовать те или иные настройки, сортировки, замены полей для удобства и т.д.

Сами запросы на выборку данных выглядят интуитивно. Например, смотрим график с RPS. Запрос там такой:

sum(rate({job="$job"} [1m]))

Настройка дашборда в Loki

Меняя запрос и параметры отображения, можно тут же в режиме реального времени следить за изменениями. Для отображения статусов запросов, используется такое выражение:

sum by (status) (rate({job="$job"} [1m]))

Ну и так далее. Отдельные запросы вам без проблем напишет ИИ и всё разъяснит. Можно очень быстро во всём разобраться. Нюансы будут возникать с нагрузкой и хранением. Там уже нужно будет разбираться с labels, structured_metadata и т.д. В общем случае для простых визуализаций это не потребуется.

Вот исходный код настроенного мной дашборда для этой статьи:

{
  "__inputs": [
    {
      "name": "DS_LOKI",
      "label": "loki",
      "description": "",
      "type": "datasource",
      "pluginId": "loki",
      "pluginName": "Loki"
    }
  ],
  "__elements": {},
  "__requires": [
    {
      "type": "panel",
      "id": "bargauge",
      "name": "Bar gauge",
      "version": ""
    },
    {
      "type": "grafana",
      "id": "grafana",
      "name": "Grafana",
      "version": "12.3.1"
    },
    {
      "type": "datasource",
      "id": "loki",
      "name": "Loki",
      "version": "12.3.1"
    },
    {
      "type": "panel",
      "id": "piechart",
      "name": "Pie chart",
      "version": ""
    },
    {
      "type": "panel",
      "id": "table",
      "name": "Table",
      "version": ""
    },
    {
      "type": "panel",
      "id": "timeseries",
      "name": "Time series",
      "version": ""
    }
  ],
  "annotations": {
    "list": [
      {
        "builtIn": 1,
        "datasource": {
          "type": "grafana",
          "uid": "-- Grafana --"
        },
        "enable": true,
        "hide": true,
        "iconColor": "rgba(0, 211, 255, 1)",
        "name": "Annotations & Alerts",
        "type": "dashboard"
      }
    ]
  },
  "editable": true,
  "fiscalYearStartMonth": 0,
  "graphTooltip": 0,
  "links": [],
  "panels": [
    {
      "datasource": {
        "type": "loki",
        "uid": "${DS_LOKI}"
      },
      "fieldConfig": {
        "defaults": {
          "color": {
            "mode": "palette-classic"
          },
          "custom": {
            "axisBorderShow": false,
            "axisCenteredZero": false,
            "axisColorMode": "text",
            "axisLabel": "",
            "axisPlacement": "auto",
            "barAlignment": 0,
            "barWidthFactor": 0.6,
            "drawStyle": "line",
            "fillOpacity": 0,
            "gradientMode": "none",
            "hideFrom": {
              "legend": false,
              "tooltip": false,
              "viz": false
            },
            "insertNulls": false,
            "lineInterpolation": "linear",
            "lineWidth": 1,
            "pointSize": 5,
            "scaleDistribution": {
              "type": "linear"
            },
            "showPoints": "auto",
            "showValues": false,
            "spanNulls": false,
            "stacking": {
              "group": "A",
              "mode": "none"
            },
            "thresholdsStyle": {
              "mode": "off"
            }
          },
          "displayName": "Число запросов в сек (среднее)",
          "mappings": [],
          "min": 0,
          "thresholds": {
            "mode": "absolute",
            "steps": [
              {
                "color": "green",
                "value": 0
              },
              {
                "color": "red",
                "value": 80
              }
            ]
          }
        },
        "overrides": []
      },
      "gridPos": {
        "h": 8,
        "w": 11,
        "x": 0,
        "y": 0
      },
      "id": 1,
      "options": {
        "legend": {
          "calcs": [
            "mean"
          ],
          "displayMode": "list",
          "placement": "bottom",
          "showLegend": true
        },
        "tooltip": {
          "hideZeros": false,
          "mode": "single",
          "sort": "none"
        }
      },
      "pluginVersion": "12.3.1",
      "targets": [
        {
          "direction": "backward",
          "editorMode": "code",
          "expr": "sum(rate({job=\"$job\"} [1m]))",
          "queryType": "range",
          "refId": "A",
          "datasource": {
            "type": "loki",
            "uid": "${DS_LOKI}"
          }
        }
      ],
      "title": "Requests per second (RPS)",
      "type": "timeseries"
    },
    {
      "datasource": {
        "type": "loki",
        "uid": "${DS_LOKI}"
      },
      "fieldConfig": {
        "defaults": {
          "color": {
            "mode": "palette-classic"
          },
          "custom": {
            "axisBorderShow": false,
            "axisCenteredZero": false,
            "axisColorMode": "text",
            "axisLabel": "",
            "axisPlacement": "auto",
            "barAlignment": 0,
            "barWidthFactor": 0.6,
            "drawStyle": "line",
            "fillOpacity": 0,
            "gradientMode": "none",
            "hideFrom": {
              "legend": false,
              "tooltip": false,
              "viz": false
            },
            "insertNulls": false,
            "lineInterpolation": "linear",
            "lineWidth": 1,
            "pointSize": 5,
            "scaleDistribution": {
              "type": "linear"
            },
            "showPoints": "auto",
            "showValues": false,
            "spanNulls": false,
            "stacking": {
              "group": "A",
              "mode": "none"
            },
            "thresholdsStyle": {
              "mode": "off"
            }
          },
          "mappings": [],
          "min": 0,
          "thresholds": {
            "mode": "absolute",
            "steps": [
              {
                "color": "green",
                "value": 0
              },
              {
                "color": "red",
                "value": 80
              }
            ]
          }
        },
        "overrides": []
      },
      "gridPos": {
        "h": 8,
        "w": 13,
        "x": 11,
        "y": 0
      },
      "id": 7,
      "options": {
        "legend": {
          "calcs": [],
          "displayMode": "list",
          "placement": "bottom",
          "showLegend": false
        },
        "tooltip": {
          "hideZeros": false,
          "mode": "single",
          "sort": "none"
        }
      },
      "pluginVersion": "12.3.1",
      "targets": [
        {
          "direction": "backward",
          "editorMode": "code",
          "expr": "sum(rate({job=\"$job\"} | json | unwrap bytes [1m]))",
          "queryType": "range",
          "refId": "A",
          "datasource": {
            "type": "loki",
            "uid": "${DS_LOKI}"
          }
        }
      ],
      "title": "Traffic (bytes/sec)",
      "type": "timeseries"
    },
    {
      "datasource": {
        "type": "loki",
        "uid": "${DS_LOKI}"
      },
      "fieldConfig": {
        "defaults": {
          "color": {
            "mode": "palette-classic"
          },
          "custom": {
            "axisBorderShow": false,
            "axisCenteredZero": false,
            "axisColorMode": "text",
            "axisLabel": "RPS",
            "axisPlacement": "auto",
            "barAlignment": 0,
            "barWidthFactor": 0.6,
            "drawStyle": "line",
            "fillOpacity": 0,
            "gradientMode": "none",
            "hideFrom": {
              "legend": false,
              "tooltip": false,
              "viz": false
            },
            "insertNulls": false,
            "lineInterpolation": "linear",
            "lineWidth": 1,
            "pointSize": 5,
            "scaleDistribution": {
              "type": "linear"
            },
            "showPoints": "auto",
            "showValues": false,
            "spanNulls": false,
            "stacking": {
              "group": "A",
              "mode": "none"
            },
            "thresholdsStyle": {
              "mode": "off"
            }
          },
          "mappings": [],
          "min": 0,
          "thresholds": {
            "mode": "absolute",
            "steps": [
              {
                "color": "green",
                "value": 0
              },
              {
                "color": "red",
                "value": 80
              }
            ]
          }
        },
        "overrides": []
      },
      "gridPos": {
        "h": 7,
        "w": 8,
        "x": 0,
        "y": 8
      },
      "id": 5,
      "options": {
        "legend": {
          "calcs": [],
          "displayMode": "list",
          "placement": "bottom",
          "showLegend": false
        },
        "tooltip": {
          "hideZeros": false,
          "mode": "single",
          "sort": "none"
        }
      },
      "pluginVersion": "12.3.1",
      "targets": [
        {
          "direction": "backward",
          "editorMode": "code",
          "expr": "sum(rate({job=\"$job\"} | status >= 400 and status < 500 [1m]))", "queryType": "range", "refId": "A", "datasource": { "type": "loki", "uid": "${DS_LOKI}" } } ], "title": "4xx Errors", "type": "timeseries" }, { "datasource": { "type": "loki", "uid": "${DS_LOKI}" }, "fieldConfig": { "defaults": { "color": { "mode": "palette-classic" }, "custom": { "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "RPS", "axisPlacement": "auto", "barAlignment": 0, "barWidthFactor": 0.6, "drawStyle": "line", "fillOpacity": 0, "gradientMode": "none", "hideFrom": { "legend": false, "tooltip": false, "viz": false }, "insertNulls": false, "lineInterpolation": "linear", "lineWidth": 1, "pointSize": 5, "scaleDistribution": { "type": "linear" }, "showPoints": "auto", "showValues": false, "spanNulls": false, "stacking": { "group": "A", "mode": "none" }, "thresholdsStyle": { "mode": "off" } }, "mappings": [], "min": 0, "thresholds": { "mode": "absolute", "steps": [ { "color": "green", "value": 0 }, { "color": "red", "value": 80 } ] } }, "overrides": [] }, "gridPos": { "h": 7, "w": 8, "x": 8, "y": 8 }, "id": 6, "options": { "legend": { "calcs": [], "displayMode": "list", "placement": "bottom", "showLegend": false }, "tooltip": { "hideZeros": false, "hoverProximity": -1, "mode": "single", "sort": "none" } }, "pluginVersion": "12.3.1", "targets": [ { "direction": "backward", "editorMode": "code", "expr": "sum(rate({job=\"$job\"} | status >= 500 [1m]))",
          "queryType": "range",
          "refId": "A",
          "datasource": {
            "type": "loki",
            "uid": "${DS_LOKI}"
          }
        }
      ],
      "title": "5xx Errors",
      "type": "timeseries"
    },
    {
      "datasource": {
        "type": "loki",
        "uid": "${DS_LOKI}"
      },
      "fieldConfig": {
        "defaults": {
          "color": {
            "mode": "palette-classic"
          },
          "custom": {
            "axisBorderShow": false,
            "axisCenteredZero": false,
            "axisColorMode": "text",
            "axisLabel": "RPS",
            "axisPlacement": "auto",
            "barAlignment": 0,
            "barWidthFactor": 0.6,
            "drawStyle": "line",
            "fillOpacity": 0,
            "gradientMode": "none",
            "hideFrom": {
              "legend": false,
              "tooltip": false,
              "viz": false
            },
            "insertNulls": false,
            "lineInterpolation": "linear",
            "lineWidth": 1,
            "pointSize": 5,
            "scaleDistribution": {
              "type": "linear"
            },
            "showPoints": "auto",
            "showValues": false,
            "spanNulls": false,
            "stacking": {
              "group": "A",
              "mode": "none"
            },
            "thresholdsStyle": {
              "mode": "off"
            }
          },
          "mappings": [],
          "min": 0,
          "thresholds": {
            "mode": "absolute",
            "steps": [
              {
                "color": "green",
                "value": 0
              },
              {
                "color": "red",
                "value": 80
              }
            ]
          }
        },
        "overrides": []
      },
      "gridPos": {
        "h": 7,
        "w": 8,
        "x": 16,
        "y": 8
      },
      "id": 2,
      "options": {
        "legend": {
          "calcs": [],
          "displayMode": "list",
          "placement": "bottom",
          "showLegend": true
        },
        "tooltip": {
          "hideZeros": false,
          "mode": "single",
          "sort": "none"
        }
      },
      "pluginVersion": "12.3.1",
      "targets": [
        {
          "direction": "backward",
          "editorMode": "code",
          "expr": "sum by (status) (rate({job=\"$job\"} [1m]))",
          "queryType": "range",
          "refId": "A",
          "datasource": {
            "type": "loki",
            "uid": "${DS_LOKI}"
          }
        }
      ],
      "title": "HTTP Status Codes",
      "type": "timeseries"
    },
    {
      "datasource": {
        "type": "loki",
        "uid": "${DS_LOKI}"
      },
      "fieldConfig": {
        "defaults": {
          "color": {
            "mode": "palette-classic"
          },
          "custom": {
            "hideFrom": {
              "legend": false,
              "tooltip": false,
              "viz": false
            }
          },
          "mappings": []
        },
        "overrides": []
      },
      "gridPos": {
        "h": 9,
        "w": 8,
        "x": 0,
        "y": 15
      },
      "id": 8,
      "options": {
        "legend": {
          "displayMode": "list",
          "placement": "bottom",
          "showLegend": true
        },
        "pieType": "pie",
        "reduceOptions": {
          "calcs": [
            "lastNotNull"
          ],
          "fields": "",
          "values": false
        },
        "sort": "desc",
        "tooltip": {
          "hideZeros": false,
          "mode": "single",
          "sort": "none"
        }
      },
      "pluginVersion": "12.3.1",
      "targets": [
        {
          "direction": "backward",
          "editorMode": "code",
          "expr": "sum by (method) (count_over_time({job=\"$job\"} [$__range]))",
          "queryType": "range",
          "refId": "A",
          "datasource": {
            "type": "loki",
            "uid": "${DS_LOKI}"
          }
        }
      ],
      "title": "HTTP Methods",
      "type": "piechart"
    },
    {
      "datasource": {
        "type": "loki",
        "uid": "${DS_LOKI}"
      },
      "fieldConfig": {
        "defaults": {
          "color": {
            "mode": "thresholds"
          },
          "mappings": [],
          "thresholds": {
            "mode": "absolute",
            "steps": [
              {
                "color": "green",
                "value": 0
              },
              {
                "color": "red",
                "value": 80
              }
            ]
          }
        },
        "overrides": [
          {
            "matcher": {
              "id": "byName",
              "options": "host"
            },
            "properties": []
          },
          {
            "matcher": {
              "id": "byName",
              "options": "Число запросов"
            },
            "properties": []
          }
        ]
      },
      "gridPos": {
        "h": 9,
        "w": 8,
        "x": 8,
        "y": 15
      },
      "id": 9,
      "options": {
        "displayMode": "gradient",
        "legend": {
          "calcs": [],
          "displayMode": "list",
          "placement": "bottom",
          "showLegend": false
        },
        "maxVizHeight": 300,
        "minVizHeight": 16,
        "minVizWidth": 8,
        "namePlacement": "auto",
        "orientation": "horizontal",
        "reduceOptions": {
          "calcs": [
            "lastNotNull"
          ],
          "fields": "",
          "values": true
        },
        "showUnfilled": true,
        "sizing": "auto",
        "valueMode": "color"
      },
      "pluginVersion": "12.3.1",
      "targets": [
        {
          "direction": "backward",
          "editorMode": "code",
          "expr": "topk(20,\r\n  sum by (host) (\r\n    count_over_time({job=\"$job\"}[$__range])\r\n  )\r\n)",
          "queryType": "instant",
          "refId": "A",
          "datasource": {
            "type": "loki",
            "uid": "${DS_LOKI}"
          }
        }
      ],
      "title": "Top Client IPs",
      "transformations": [
        {
          "id": "sortBy",
          "options": {
            "fields": {},
            "sort": [
              {
                "desc": true,
                "field": "Value #A"
              }
            ]
          }
        }
      ],
      "type": "bargauge"
    },
    {
      "datasource": {
        "type": "loki",
        "uid": "${DS_LOKI}"
      },
      "fieldConfig": {
        "defaults": {
          "color": {
            "mode": "thresholds"
          },
          "custom": {
            "align": "auto",
            "cellOptions": {
              "type": "auto"
            },
            "footer": {
              "reducers": [
                "sum"
              ]
            },
            "inspect": false
          },
          "mappings": [],
          "thresholds": {
            "mode": "absolute",
            "steps": [
              {
                "color": "green",
                "value": 0
              },
              {
                "color": "red",
                "value": 80
              }
            ]
          }
        },
        "overrides": [
          {
            "matcher": {
              "id": "byName",
              "options": "URL"
            },
            "properties": []
          },
          {
            "matcher": {
              "id": "byName",
              "options": "Число запросов"
            },
            "properties": []
          }
        ]
      },
      "gridPos": {
        "h": 9,
        "w": 8,
        "x": 16,
        "y": 15
      },
      "id": 3,
      "options": {
        "cellHeight": "sm",
        "enablePagination": true,
        "showHeader": true,
        "sortBy": [
          {
            "desc": true,
            "displayName": "Число запросов"
          }
        ]
      },
      "pluginVersion": "12.3.1",
      "targets": [
        {
          "direction": "backward",
          "editorMode": "code",
          "expr": "topk(20,\r\n  sum by (request) (\r\n    count_over_time({job=\"$job\"}[$__range])\r\n  )\r\n)",
          "queryType": "instant",
          "refId": "A",
          "datasource": {
            "type": "loki",
            "uid": "${DS_LOKI}"
          }
        }
      ],
      "title": "Top URLs",
      "transformations": [
        {
          "id": "organize",
          "options": {
            "excludeByName": {
              "Time": true
            },
            "includeByName": {},
            "indexByName": {},
            "renameByName": {
              "Time": "",
              "Value #A": "Число запросов",
              "request": "URL"
            }
          }
        },
        {
          "id": "sortBy",
          "options": {
            "fields": {},
            "sort": [
              {
                "desc": true,
                "field": "Число запросов"
              }
            ]
          }
        }
      ],
      "type": "table"
    }
  ],
  "preload": false,
  "refresh": "",
  "schemaVersion": 42,
  "tags": [],
  "templating": {
    "list": [
      {
        "current": {},
        "definition": "",
        "name": "job",
        "options": [],
        "query": {
          "label": "job",
          "refId": "LokiVariableQueryEditor-VariableQuery",
          "stream": "",
          "type": 1
        },
        "refresh": 1,
        "regex": "",
        "type": "query"
      }
    ]
  },
  "time": {
    "from": "now-5m",
    "to": "now"
  },
  "timepicker": {},
  "timezone": "browser",
  "title": "Nginx Logs (Loki)",
  "uid": "1d2b765b-d840-4b33-98c1-667fddd56b37",
  "version": 31,
  "weekStart": "",
  "id": null
}

Для добавления его к себе в Grafana при создании дашборда выберите пункт Import и вставьте json дашборда в соответствующее поле.

Импорт дашборда для логов веб сервера в Loki

Свяжите его со своим Datasource с Loki и пользуйтесь.

Для того, чтобы этот дашборд заработал с реальными логами веб сервера, их необходимо привести к такому же виду. Вот как это выглядит в конфигурации Nginx/Angie:

http {
...................
    log_format json escape=json
    '{'
        '"host":"$remote_addr",'
        '"user-identifier":"$remote_user",'
        '"datetime":"$time_local",'
        '"method":"$request_method",'
        '"request":"$request_uri",'
        '"protocol":"$server_protocol",'
        '"status":"$status",'
        '"bytes":"$request_length",'
        '"referrer":"$http_referer",'
        '"user_agent":"$http_user_agent"'
    '}';
...............
}

Данный код нужно добавить в блок http в основной конфигурации веб сервера. И потом выбрать этот формат в настройках общего логирования или конкретного виртуального хоста.

access_log /var/log/nginx/access.log json;

Данный формат можно вести параллельно с обычным, если вы хотите себе оставить возможность привычным способом просматривать логи. То есть можно сделать примерно вот так:

access_log /var/log/nginx/access.log main;
access_log /var/log/nginx/access.json.log json;

Веб сервер будет одновременно писать два лог файла в разных форматах.

Очистка данных в Loki

Удалять логи вручную из Loki - муторное занятие и в общем случае его не рекомендуется делать. Оно затратно по ресурсам, происходит не сразу, а помещается в очередь на удаление. На нагруженном сервере эта процедура может растянуться во времени.

Для ручного удаления необходимо будет воспользоваться специальным методом API. Вот пример, как это может выглядеть:

 # curl -X POST -G "http://localhost:3100/loki/api/v1/delete" --data-urlencode "start=2026-04-12T00:00:00Z" --data-urlencode "end=2026-04-13T20:00:00Z" --data-urlencode "query={job=\"nginx\"}"

Этот запрос удаляет все логи с меткой job=nginx в заданном интервале времени. Его указание обязательно. Не перепутайте часовые пояса при вставлении интервалов. Чтобы сделать более узкую выборку, запрос можно сформировать в разделе Explore и проверить то, что вы хотите удалить.

Другого способа частичного ручного удаления логов в Loki нет.

Заключение

Я разобрал все основные моменты в настройке Loki, чтобы начать собирать в него разнородные логи со всей инфраструктуры:

  • Системные логи Linux и Windows
  • Логи Docker контейнеров
  • Логи веб сервера Nginx или Angie
  • Логи сетевых устройств

На удивление, когда изучал его, потратил больше времени по сравнению с ELK. Во многом это из-за того, что в ELK больше возможностей, и я ожидал их же увидеть в Loki, но их нет. Объясняется это тем, что Loki - более легковесное хранилище, структура хранения которого отличается от ELK. Из-за этого работать с данными надо аккуратнее, особенно при построении запросов в дашбордах.

Онлайн-курс по устройству компьютерных сетей.

На углубленном курсе "Архитектура современных компьютерных сетей" вы с нуля научитесь работать с Wireshark и «под микроскопом» изучите работу сетевых протоколов. На протяжении курса надо будет выполнить более пятидесяти лабораторных работ в Wireshark.
Реклама ИП Скоромнов Д.А. ИНН 331403723315

Автор Zerox

Владимир, системный администратор, автор сайта. Люблю настраивать сервера, изучать что-то новое, делиться знаниями, писать интересные и полезные статьи. Открыт к диалогу и сотрудничеству. Если вам интересно узнать обо мне побольше, то можете послушать интервью. Запись на моем канале - https://t.me/srv_admin/425 или на сайте в контактах.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Нажимая кнопку "Отправить комментарий" Я даю согласие на обработку персональных данных.
Много интересного в канале автора в Telegram →
This is default text for notification bar