Добре – Loki запускати навчились – Grafana Loki: архітектура та запуск в Kubernetes з AWS S3 storage та boltdb-shipper, як налаштовувати алерти теж розібрались – Grafana Loki: алерти з Ruler та labels з логів.
Тепер час розібратися з тим, що взагалі ми можемо робити в Loki використовуючи її LogQL.
Зміст
Підготовка
Далі для прикладів будемо використовувати два поди – один з nginxdemo/hello для звичайних логів nginx, а інший thorstenhans/fake-logger, який буде писати логи в JSON.
Для Nginx додамо Service, что б мати можливість слати запити з curl
:
apiVersion: v1 kind: Pod metadata: name: nginxdemo labels: app: nginxdemo logging: test spec: containers: - name: nginxdemo image: nginxdemos/hello ports: - containerPort: 80 name: nginxdemo-svc --- apiVersion: v1 kind: Service metadata: name: nginxdemo-service labels: app: nginxdemo logging: test spec: selector: app: nginxdemo ports: - name: nginxdemo-svc-port protocol: TCP port: 80 targetPort: nginxdemo-svc --- apiVersion: v1 kind: Pod metadata: name: fake-logger labels: app: fake-logger logging: test spec: containers: - name: fake-logger image: thorstenhans/fake-logger:0.0.2
Деплоїмо та прокидуємо порт:
[simterm]
$ kk port-forward svc/nginxdemo-service 8080:80
[/simterm]
І запустимо curl
зі звичайним GET в циклі:
[simterm]
$ watch -n 1 curl localhost:8080 2&>1 /dev/null &
[/simterm]
Та ще один – з POST:
[simterm]
$ watch -n 1 curl -X POST localhost:8080 2&>1 /dev/null
[/simterm]
Поїхали.
Grafana Explore: Loki – інтерфейс
Декілька слів про сам інтерфейс Grafana Explore > Loki.
Ви можете використовувати декілька запитів одночасно:
Також, є можливість розділити інтефейс на дві частини, і в кожній виконувати окремі запити:
Як і в звичайних дашбордах Grafana, є можливість вибрати період, за який ви хочете отримати дані, та задати інтервал для автооновлювання:
Або можете включити Live-режим – тоді дані будуть з’являтися як тільки вони потраплять до Loki:
Для створення запитів є два режими – Builder та Code.
В режимі Builder Loki видає список доступних тегів та фільтрів:
В режимі Code вони будуть підставлятися автоматично по мірі набору:
Функція Explain буде роз’яснювати що саме ваш запит робить:
А Inspector відобразить деталі про ваш запит – скільки часу і ресурсів було використано для формування відповіді – корисно для оптимізації запитів:
Крім того, завжди можна відкрити Loki Cheat Sheet, натиснувши (?)
з правої сторони від поля для запиту:
LogQL: overview
В цілому, робота з Loki та її LogQL майже аналогічна роботі з Prometheus та його PromQL – майже всі тіж самі функції та загальний підхід, це навіть відображено в опису Loki: “Like Prometheus, but for logs”.
Отже, основна вибірка базується на проіндексованих лейблах (або тегах, кому як більше до вподоби), за допомогою яких ми робимо основний пошук в логах – вибираємо стрім.
Типи запитів в Loki залежать від фінального результату:
- Log queries: формують строки з лог-файлів
- Metric queries: включають в себе Log queries, але в результаті формують числові значення, які можна використовувати для формування графіків в Grafana або для алертів в Ruler
В цілому, будь-який запит складається із трьох основних частин:
{Log Stream Selectors} <Log Pipeline "Log filter">
Тобто в запиті:
{app="nginxdemo"} |= "172.17.0.1"
{app="nginxdemo"}
– це Log Stream Selector, в якому ми вибираємо конкретний стрім из Loki, |=
– початок Log Pipeline, який включає в себе Log Filter Expression – "172.17.0.1"
.
Окрім Log filter, пайплайн може включати в себе Log або Tag formatting expression, який міняє отримані в пайплайн дані.
Обов’язковим є Log Stream Selector, тоді як Log Pipeline з його expressions являється опціональним, і використовуюється для уточненя або форматування результатів.
Log queries
Log Stream Selectors
Для селекторів викристовуються лейбли, які задаються агентом, який збирає логи – promtail
, fluentd
або іншими.
Log Stream Selector визначає скільки індексів та блоків данних будуть завантажені для повернення результату, тобто напряму впливає на швидкість роботи і ресурси CPU/RAM, задіяні для формування відповіді.
В прикладі вище в селекторі {app="nginxdemo"}
ми використовуємо оператор “=
“, який може бути:
=
: дорівнює!=
: не дорівнює=~
: regex!~
: негативний regex
Отже, за запитом {app="nginxdemo"}
ми отримаємо логи всіх подів, у яких є тег app
зі значенням nginxdemo:
Можемо комбінувати декілька селекторів, наприклад отримати всі логи з logging=test
, але без app=nginxdemo
:
{logging="test", app!="nginxdemo"}
Або використати regex
:
{app=~"nginx.+"}
Або просто вибрати взагалі всі логи (стріми), в яких є тег app
:
Log Pipeline
Данні, отримані зі стриму можна передати в пайплайн для подальшого фільтрування або форматування. При цьому результат роботи одного пайплайну можна передати в наступний, і так далі.
Pipeline може включати в себе:
- Log line filtering expressions – для фільтрування попередніх результатів
- Parser expressions – для отримання тегів з логів, які можна передати в Tag filtering
- Tag filtering expressions – для фільтрування даних по тегам
- Log line formatting expressions – використовується для редагування отриманних результатів
- Tag formatting expressions – редагування тегів/лейбл
Log Line filtering
Фільтри використовуються для… фільтрування)
Тобто, коли ми отримали дані із стриму, і хочемо з нього вибрати окремі строки – то використовуємо log filter.
Фільтр складається з оператора та строкового запиту, за яким робиться вибірка данних.
Операторами можуть буди:
|=
: строка містить строковий запит! =
: строка НЕ містить строковий запит|~
: строка дорівнює регулярному виразу! ~
: строка НЕ дорівнює регулярному виразу
При використанні regex майте на увазі, что використовується синтаксис Golang RE2, і за замочуванням він є case-sensitive. Щоб переключити його на незалежний від регистру режим – додаємо (i?)
.
Окрім того, Log Line filtering краще використовувати на початку запиту, бо вони працють швидко, і позбавлять наступні пайплайни він зайвої роботи.
Прикладом log filter може бути вибірка за строкою:
{job=~".+"} |= "promtail"
Або декілька виразів, використовуючи регулярку:
Parser expressions
Парсери… парсять) (гвинтокрили гвинтять) вхідні дані, та отримують з них лейбли, які потім можна використати в подальших фільтрах або для формування Metric queries.
Наразі, LogQL підтримує json
, logfmt
, pattern
, regexp
та unpack
для роботи з тегами.
json
Наприклад, json
формує всі json-ключі в лейбли, тобто запит {app="fake-logger"} | json
замість:
Сформує новий набір тегів:
Отримані через json
теги можна далі використати для додаткових фільтрів, наприклад – вибрати тільки строки з level=debug
:
logfmt
Для формування тегів з логів не в форматі JSON можно використати logfmt
, який всі знайдені поля перетворить на лейбли.
Наприклад, job="monitoring/loki-read"
має поля ключ=значення:
level=info ts=2022-12-28T14:31:11.645759285Z caller=metrics.go:170 component=frontend org_id=fake latency=fast
Які за допомогою logfmt
перетворяться на лейбли:
regexp
Парсер regex
приймає аргумент, в якому вказується regex-група, яка сформує тег із запиту.
Наприклад, зі строки:
10.0.44.12 – – [28/Dec/2022:14:42:58 +0000] 204 “POST /loki/api/v1/push HTTP/1.1” 0 “-” “promtail/” “-“
Ми можемо динамічно сформувати теги ip
та status_code
:
{container="nginx"} | regexp "^(?P<ip>[0-9]{1,3}.{3}[0-9]{1,3}).*(?P<status_code>[0-9]{3})"
pattern
pattern
дозволяє сформувати лейбли за шаблонами лог-запису, тобто строка:
10.0.7.188 – – [28/Dec/2022:15:27:04 +0000] 204 “POST /loki/api/v1/push HTTP/1.1” 0 “-” “promtail/2.7.0” “-“
Може бути описана у вигляді:
{container="nginx"} | pattern `<ip> - - [<date>] <status_code> "<request> <uri> <_>" <size> "<_>" "<agent>" <_>`
де <_>
ігнорить, тобто не створює тегу.
І в результаті отримаємо набір лейбл за цим шаблоном:
Див. більше тут – Introducing the pattern parser.
Tag filtering expressions
Як видно з назви, дозволяє створювати нові фільтри з тегів які вже є в запису, або які були створені за допомогою попереднього парсеру, наприклад logfmt
.
Візьмемо строку:
level=info ts=2022-12-28T15:56:31.449162304Z caller=table_manager.go:252 msg=”query readiness setup completed” duration=1.965µs distinct_users_len=0
Якщо пропустимо її через парсер logrmt
, то отримаємо теги caller
, msg
, durarion
та distinct_users_len
:
Далі, можемо створити фільтр за цими тегами:
Доступні оператори тут ==
, =
, !=
, >
, >=
, <
, <=
.
Також, можемо використати оператори and
або or
:
{job="monitoring/loki-read"} | logfmt | caller="table_manager.go:252" or org_id="fake" and caller!~"metrics.go.+"
Log line formatting expressions
Далі, можемо формувати те, які саме дані нам будуть відображені в записі.
Наприклад, візьмемо той же loki-read, в якому маємо теги:
Серед них нам цікаво відобразити тільки component
та duration
– використовуємо форматування:
{job="monitoring/loki-read"} | logfmt | line_format "{{.component}} {{.duration}}"
Label format expressions
За допомогою label_format
можемо перейменувати, змінити чи додати нові лейбли.
Для цього, аргументом передаємо ім’я лейбли з оператором =
, за яким йде потрібне значення.
Наприклад, маємо лейблу app
:
Яку хочемо перейменувати в application
– використовуємо label_format application=app
:
Або можемо використати значення існуючого тегу для створення нового, для цього використовуємо шаблонізатор у вигляді {{.field_name}}
, де можемо комбінувати декілька полів.
Тобто, якщо хочемо створити тег error_message
в якому будуть значення полів level
та msg
– формуємо такий запит:
{job="default/fake-logger"} | json | label_format error_message="{{.level}} {{.msg}}"
Log Metrics
І розглянемо як із логів можна створювати метрики, які можна використовувати для формування графіків або алертів (див. Grafana Loki: алерти з Ruler та labels з логів).
Interval vectors
Для роботи з векторами за часом наразі є чотири доступні функції, які в принципі вже знайомі по Prometheus:
rate
: кількість логів в секундуcount_over_time
: підрахувати кількість записів стріму за заданий проміжок часуbytes_rate
: кількість байт в секундуbytes_over_time
: підрахувати кількість байт стріму за заданий проміжок часу
Наприклад, отримати queries per second для джоби fake-logger:
rate({job="default/fake-logger"}[5m])
Може бути корисно, щоб створити алерт на випадок як якийсь сервіс почав писати багато логів, що може бути ознакою того, що “щось пішло не так”.
Отримати кількість записів з рівнем warning
за останні 5 хвилин можно за допомогою такого запиту:
count_over_time({job="default/fake-logger"} | json | level="warning" [5m])
Aggregation Functions
Також, можемо використовувати функції агрегації для об’єднання вихідних даних, всі також знайомі по PromQL:
sum
: сумма за лейблоюmin
,max
таavg
: мінімальне, максимальне да середнє значенняstdev
,stdvar
: стандартне відхилення та розбіжністьcount
: кількість елементів у векторіbottomk
таtopk
: мінімальний та максимальний елементи
Синтаксис функцій агрегації:
<aggr-op>([parameter,] <vector expression>) [without|by (<label list>)]
Наприклад, отримати кількість записів в секунду від джоби fake-logger, та поділити їх по тегу label
:
sum(rate({job="default/fake-logger"} | json [5m])) by (level)
Або з прикладів вище:
- отримати записи з подів loki-read
- із результату створити дві нові лейбли –
component
таduration
- отримати кількість записів в секунду
- прибрати записи без компоненту
- та відобразити суму по кожному компоненту
sum(rate({job="monitoring/loki-read"} | logfmt | line_format "{{.component}} {{.duration}}" | component != "" [10s])) by (component)
Інші оператори
І зовсім вже коротко про інші можливості.
Математичні оператори:
+
– додавання-
– віднімання*
– множення/
– ділення%
– коефіцієнт^
– зведення у ступінь
Логічні оператори:
and
: таor
: абоunless
: за виключенням
Оператори порівняння:
==
: дорівнює!=
: не дорівнює>
: більше ніж>=
: більше ніж або дорівнює<
: менше ніж<=
: менше ніж або дорівнює
Знов-таки з прикладів, які використовували раньше.
Створюємо лейблу request
:
{container="nginx"} | pattern `<_> - - [<_>] <_> "<request> <_> <_>" <_> "<_>" "<_>" <_>`
Отримаємо рейт запросів POST в секунду за останні 5 хвилин:
sum(rate({container="nginx"} | pattern `<_> - - [<_>] <_> "<request> <_> <_>" <_> "<_>" "<_>" <_>` | request="POST" [5m]))
Спочатку перевіримо на графіку кількість запитів GET та POST:
sum(rate({container="nginx"} | pattern `<_> - - [<_>] <_> "<request> <_> <_>" <_> "<_>" "<_>" <_>` [5m])) by (request)
А теперь отримаємо процент з типом POST від загальної кількості запитів:
- всі запити POST ділимо на загальну кількість запитів
- результат множимо на 100
sum(rate({container="nginx"} | pattern `<_> - - [<_>] <_> "<request> <_> <_>" <_> "<_>" "<_>" <_>` | request="POST" [5m])) / sum(rate({container="nginx"} | pattern `<_> - - [<_>] <_> "<request> <_> <_>" <_> "<_>" "<_>" <_>` [5m])) * 100
На цьому все.
Посилання по темі
- Log queries
- Metric queries
- LogQL: Log query language
- Usage of Grafana Loki Query Language LogQL
- LogQL
- New in Loki 2.3: LogQL pattern parser makes it easier to extract data from unstructured logs
- Loki 2.0 released: Transform logs as you’re querying them, and set up alerts within Loki
- Logging in Kubernetes with Loki and the PLG stack
- How to create fast queries with Loki’s LogQL to filter terabytes of logs in seconds
- Labels from Logs
- The concise guide to labels in Loki
- How labels in Loki can make log queries faster and easier
- Grafana Loki and what can go wrong with label cardinality