VictoriaTraces – як і VictoriaLogs – підтримує Recording Rules (див. VictoriaMetrics: Recording Rules для логів AWS Load Balancer) для трейсів, бо по факту трейси – це ті ж самі логи, просто інакше структуровані.
А раз є Recording Rules – то ми можемо з логів створювати метрики для алертів та Grafana dashboards.
Хоча насправді це не найкращий варіант, тим більш для якогось high load проекту, бо vmalert з Recording Rule постійно робить запити до VictoriaTraces – і тут краще підійшов би якийсь otelcol.connector.spanmetrics, який можна було б додати в pipeline.traces – але якщо OTel стеку нема або, як в моєму випадку, проект невеликий – то цілком робочий варіант робити метрики з Recording Rules.
Це вже третя частина по OpenTelemetry та VictoriaTraces, попередні тут:
- OpenTelemetry: OTel Collectors в Kubernetes та інтеграція з VictoriaMetrics stack
- VictoriaTraces: Tracing, Observability та OpenTelemetry
Зміст
Метрики з трейсів: а для чого?
Пара прикладів з моїх власних “хотєлок” на проекті – чому я почав робити таке рішення.
AWS ALB response time
У нас є метрика AWS ALB response time: тригерить алерт, коли якийсь ендпоінт починає довго відповідати. Метрика генериться з логів ALB, рахуючи поля з log record:
- record: vmlogs:alb:logs:alb_response_time:p95
expr: |
{namespace="ops-monitoring-ns"} app:="alb-logs-exporter" -"DEBUG" -"SQS" -"VMLOGS" -"PARSER"
| filter not `{"date"`*
| extract "<_> <_> <elb_id> <_> <_> <request_processing_time> <target_processing_time> <response_processing_time> <elb_status_code> <target_status_code> <received_bytes> <sent_bytes> <request_line> <user_agent> <_> <_> <_> <trace_id> <domain_name> <_> <_> <_> <_> <_> <error_reason> <_> <_> <_> <_> <conn_trace_id> <_> <_> <_>"
| extract_regexp `.*:443(?P<uri_path>/[^/?]*).* HTTP` from request_line
| filter target_processing_time :! "-1" and request_processing_time :! "-1" and response_processing_time :! "-1"
| filter request_processing_time :! "" and target_processing_time :! "" and response_processing_time :! ""
| math request_processing_time + target_processing_time + response_processing_time as total_response_time
| rename domain_name as domain
| stats by (domain, uri_path) quantile(0.95, total_response_time) as alb_response_time
І з нею відразу кілька проблем.
Перша: знов-таки – навантаження на бекенд, VictoriaLogs: якщо логів багато – то vmalert робить запит кожну хвилину (interval: 1m), до того ж в запиті є regex – який сам по собі доволі важкий в плані CPU/RAM.
Друга: лейбла uri_path в значенні має тільки першу частину URI. Тобто, якщо запит прийшов на /user/<name>/orders – то в метрику vmlogs:alb:logs:alb_response_time:p95 буде збережено тільки uri_path="/user".
Так зроблено через cardinality issue – аби не створювати багато різних значень, бо це вплине на сторейдж і ресурси (див. VictoriaMetrics: Churn Rate, High cardinality, метрики та IndexDB).
Відповідно, коли приходить алерт, ми бачимо тільки дуже загальну інформацію – по самому ендпоінту, а не конкретного юзера.
Ну і головне, чому я поліз копати в цю тему – це те, що алерти ніяк не прив’язані до трейсів.
Зараз, якщо приходить алерт типу:
Все, що ми можемо зробити – це піти в Grafana dashboard для Kubernetes Pods і WorkerNodes, і там дивитись навантаження CPU/RAM. Якщо там все ок – то йти в дашборду по RDS, і розбиратись там.
А маючи метрики з трейсів – я можу прямо в алерті створити лінк на всі пов’язані трейси, і тоді відразу в Grafana та VictoriaTraces побачити де проблема.
AWS RDS query duration
Інший приклад – метрика з логів AWS RDS:
- record: vmlogs:aws:rds:cloudwatch_logs:explain:query_duration:sum:avg:5m
expr: |
logtype:="rds" "plan:"
| extract_regexp `.*:(?P<connection>.*_kraken_user@.*kraken_db:\[\d+\])`
| extract_regexp ".*duration: (?P<duration>.+) ms"
| duration:~".+"
| extract_regexp `.*Query Text: (?P<query>.+?)(?:\s+AS\s|\s+FROM\s)`
| query:~".+"
| stats by (environment, connection, query) avg(duration) avg_duration
Тут аналогічний підхід: AWS RDS пише в лог AUTO EXPLAIN (див. PostgreSQL: використання EXPLAIN та налаштування “auto_explain” в AWS RDS), а ми парсимо логи в VictoriaLogs та генеруємо метрику.
Загалом дані в лейблах більш цікаві, ніж в прикладі з AWS ALB, бо є і частина SQL-запиту, і “connection ID” у вигляді “<db_user>@<db_host>:<PID>” – але знову-таки дебажити такі алерти складніше, бо треба брати цей connection ID, шукати його в логах RDS, потім ці логи якось пов’язувати з логами самого Backend API.
Натомість – можна мати аналогічну метрику з трейсів і генерити пряму лінку на Grafana/VictoriaTraces.
Отже, що будемо сьогодні робити – подивимось на метадані, які використовуються в OTLP для spans, опишемо кілька метрик з трейсів та напишемо кілька алертів.
Метрики HTTP
Наш Backend API “обмазаний” OTel auto-instrumentation (сподіваюсь, таки допишу пост по OTel та Python).
Трейси генеряться з усіх FastAPI та AWS викликів – і, використовуючи їх, можемо собі написати метрик та алертів.
В трейсах бекенду від FastAPI маємо поля (атрибути) http.route, http.status_code, duration – тому ми можемо створити зручні метрики з яких потім можемо створити зручні алерти.
Корисні span та resource attributes
Спершу на прикладі HTTP span від FastAPI подивимось в VictoriaTraces – що цікавого у нас є в span та resource attributes.
[
{
...
"duration": "4464509750",
...
"kind": "2",
"name": "GET /morpheus/sleep-agent/status",
...
"resource_attr:k8s.namespace.name": "prod-backend-api-ns",
"resource_attr:k8s.node.name": "ip-10-0-42-22.ec2.internal",
"resource_attr:k8s.pod.name": "backend-api-deployment-6966566f55-khb9v",
"resource_attr:service.name": "kraken-prod",
...
"span_attr:http.method": "GET",
"span_attr:http.route": "/morpheus/sleep-agent/status",
"span_attr:http.scheme": "http",
"span_attr:http.status_code": "503",
"span_attr:http.target": "/morpheus/sleep-agent/status",
...
...
"status_code": "2",
"status_message": "http 503",
"trace_id": "6a0c64013a090e5462414f3e3fba1630"
},
Що з цього нам може бути цікаве і корисне:
duration: час виконання запиту (в наносекундах) – корисно для метрик HTTP latency та PostgreSQL query durationkind: визначає роль span – чи це наш сервіс оброблює запит від клієнта (тип 2 –SERVER), чи сервіс робить запит до зовнішнього ресурсу (тип 3 –CLIENT), див. Span Kind та самі значення в кодіtrace.proto- в прикладах нижче буде AWS буде
span.kind=3, тобто “CLIENT” – наш сервіс є клієнтом, бо виконує вихідний виклик до AWS API
- в прикладах нижче буде AWS буде
name: ім’я span, згенероване SDK – корисне, бо включає в себе відразу кілька інших атрибутів – простіше використовувати в фільтрах для Grafana (про це далі, коли будемо робити алерт)k8s.namespace.name,node.name,pod.name: resource-level атрибути, які задаються або OTel Collector, або, як в моєму випадку, з Downward API в Kubernetes Deployment для Backend API (костиль, поки нема OTel Collector)- дуже корисні атрибути, бо дозволять будувати зв’язки між Pod-level metrics, Node-level, etc – дають загальний контекст для observability
span_attr:http.methodтаhttp.route: корисно відобразити і в алерті, і мати фільтр в Grafana для VictoriaMetricsspan_attr:http.target: якщоhttp.routeвище – це саме роут у FastAPI (з плейсхолдером типу/chats/{chat_id}) – то вhttp.targetвже маємо конкретний URI, який був викликаний- в цьому прикладі не дуже очевидна різниця, бо роут статичний, але в інших спанах вони виглядають як
route="/chats/{chat_id}"– а вtarget="/chats/john-789?limit=10"
- в цьому прикладі не дуже очевидна різниця, бо роут статичний, але в інших спанах вони виглядають як
span_attr:http.status_code: не путати зstatus_codeнижче – тут маємо саме HTTP-код відповіді від сервера клієнтуstatus_code: дуже корисний атрибут – OTel Span Status code (не HTTP status вище), який вказує на результат виконання операції – Success (1) або Error (2), див. Set Status- в цьому прикладі як раз добре видно, що HTTP-запит завершився з 503 – Service Unavailable, і, відповідно, статус цього span == Error
status_message: description доstatus_code– FastAPI/OTel, коли задавав значенняstatus_code=2додав текстовий опис помилкиtrace_id: ну і ID самого трейсу, до якого відноситься конкретно цей span
Тепер, маючи список атрибутів – можемо подумати над метрикою.
Метрика “vmtraces:backend:http:request_5xx:rate”
Створюємо новий VMRule з type: vlogs:
apiVersion: operator.victoriametrics.com/v1beta1
kind: VMRule
metadata:
name: recording-rules-vmalert-traces
labels:
app: vmalert-traces
spec:
groups:
- name: Traces.VictoriaTraces.Logs.rules
type: vlogs
interval: 5m
rules:
- record: vmtraces:kraken:http:request_5xx:rate
expr: |
{resource_attr:service.name=~"kraken-.*"} "span_attr:http.route":!"" "span_attr:http.status_code":~"5.."
| stats by ("resource_attr:k8s.namespace.name", "resource_attr:service.name", "span_attr:http.route", "span_attr:http.status_code") rate() requests_per_sec
Тут вибираємо всі трейси від service.name=~"kraken-.*" (Kraken – ім’я нашого бекенду), вибираємо тільки ті, які відносяться до HTTP – "span_attr:http.route":!"", вибираємо тільки з помилками 5хх.
Далі рахуємо per second rate, агрегуючи по Kubernetes Namespace, OTel Service Name, HTTP route (URI) та коду помилки.
Трохи забігаючи наперед: прикольно було б в алерті відразу створювати лінк на конкретний trace по його ID – але писати тут в метрику лейблу trace_id не варто, бо це мільйон різних значень – заб’ємо базу VictoriaMetrics.
Деплоїмо, перевіряємо, що метрика в VictoriaMetrics є:
Алерт “Backend HTTP 5xx Errors”
Аби не писати окремі алерти на Dev/Staging/Prod – використаємо Helm range.
Додаємо в values.yaml чарту:
alerts:
traces:
backend:
- env: dev
namespace: dev-backend-api-ns
severities: [warning]
- env: staging
namespace: staging-backend-api-ns
severities: [warning]
- env: prod
namespace: prod-backend-api-ns
severities: [warning, critical]
Описуємо новий VMRule (я для зручності тримаю окремо Recording Rules та алерти) – вже з самим алертом:
apiVersion: operator.victoriametrics.com/v1beta1
kind: VMRule
metadata:
name: alerts-kraken-traces-http
spec:
groups:
##########################
### Kraken Traces HTTP ###
##########################
- name: Kraken.Traces.HTTP.rules
rules:
##############################
### Kraken HTTP 5xx Errors ###
##############################
{{- range .Values.alerts.traces.backend }}
{{- $ns := . }}
{{- range .severities }}
{{- if not (eq . "critical") }}
- alert: Kraken HTTP 5xx Errors
expr: vmtraces:kraken:http:request_5xx:rate{"resource_attr:k8s.namespace.name"="{{ $ns.namespace }}",stats_result="requests_per_sec"} > 0
for: 1s
labels:
severity: {{ . }}
component: backend
environment: {{ $ns.env }}
ilert_routingkey: backend-{{ $ns.env }}-{{ . }}
annotations:
summary: "Kraken service is returning HTTP 5xx errors"
description: |-
HTTP 5xx error rate has been above 0 for more than `{{ "{{" }} $for }}`
*Namespace*: `{{ "{{" }} index $labels "resource_attr:k8s.namespace.name" }}`
*HTTP route*: `{{ "{{" }} index $labels "span_attr:http.route" }}`
*HTTP status*: `{{ "{{" }} index $labels "span_attr:http.status_code" }}`
*5xx rate*: `{{ "{{" }} printf "%.3f" $value }}` req/s
<https://{{ $.Values.monitoring.root_url }}/explore?orgId=1&left=%7B%22datasource%22:%22{{ $.Values.monitoring.victoria_traces_uid }}%22,%22queries%22:%5B%7B%22refId%22:%22A%22,%22datasource%22:%7B%22type%22:%22jaeger%22,%22uid%22:%22{{ $.Values.monitoring.victoria_traces_uid }}%22%7D,%22queryType%22:%22search%22,%22service%22:%22{{ "{{" }} index $labels "resource_attr:service.name" }}%22,%22tags%22:%22http.route%3D{{ "{{" }} index $labels "span_attr:http.route" }}%22,%22limit%22:100%7D%5D,%22range%22:%7B%22from%22:%22now-1h%22,%22to%22:%22now%22%7D%7D|:grafana: VictoriaTraces>
{{- end }}
{{- end }}
{{- end }}
Тут з {{- range .Values.alerts.traces.backend }} проходимось в циклі по всім значенням з values і для кожного оточення створюємо окремий алерт.
З {{- if not (eq . "critical") }} не створюю алерти CRITICAL, бо він приходить з @channel в Slack – поки це в тесті, подивитись, як буде працювати.
OTel names формат та Helm templating
Тут є цікавий момент, пов’язаний з OTel форматом імен метрик і лейбл.
Якщо в Prometheus format вони задаються через “_” – тобто у вигляді “resource_attr_k8s_namespace_name“, то OTel використовує крапки і двокрапки – і це ломає шаблонізатор Helm/Go.
Є варіант використовувати Label sanitization – писати метрики до VictoriaMetrics відразу в Prometheus-форматі, але це (поки що) не можна робити для трейсів і логів, і тоді в різних бекендах будемо мати різні імена лейбл.
Тому я поки що вирішив не включати usePromCompatibleNaming і писати дані як є, а коли вже цю опцію додадуть до VictoriaLogs та VictoriaTraces – можна буде оновити алерти.
Btw, можете лайкнути issue на GitHub – OpenTelemetry: support field names transformations 😉
Тому тут робимо з index $labels, а імена лейбл вказуємо в лапках:
index $labels "resource_attr:k8s.namespace.name"
Значення в $labels – це тип map(map[string]string), тому index проходиться по вкладеним ключам і отримує потрібну лейблу.
Grafana link до VictoriaTraces
Останнім в алерті створюється прямий лінк на Grafana – щоб ми з алерту могли відразу відкрити всі деталі.
Як писав вище – простіше тут було робити пошук по trace_id – але його ми не можемо писати в лейбли, бо знов-таки – cardinality issue.
Тому я зробив через фільтр по тегам, які підставляються з отриманих атрибутів в $labels – в данному випадку по span_attr:http.route:
А сам алерт в Slack виглядає так:
Метрика “vmtraces:backend:http:request_duration:p95”
Тут, в принципі, все аналогічно – тільки рахується не rate() – а 95 перцентиль по полю duration.
Чому 95 перцентиль, а не якийсь avg(): бо average дасть загальну “розмазану картину” по всім запитам – але може пропустити проблеми у невеликої кількості юзерів.
Recording Rule вийшов таким:
- record: vmtraces:kraken:http:request_duration:p95
expr: |
{resource_attr:service.name=~"kraken-.*"} "span_attr:http.route":!"" kind:=2
| stats by ("resource_attr:k8s.namespace.name", "resource_attr:service.name", "span_attr:http.route") quantile(0.95, duration) value
Тут в фільтрах додаємо kind=2, аби рахувати дані тільки від спанів з типом SERVER – бо нам цікавий результат від самого FastAPI на Backend API.
Інакше в результати міг б попасти спан із дочірніх спанів з цього трейсу – запити від Backend API до, наприклад, DynamoDB або RDS (хоча фільтр з "span_attr:http.route":!"" має вибрати тільки HTTP).
Деплоїмо, перевіряємо метрику:
Алерт “Backend HTTP p95 latency is high”
Тут все аналогічно до алерту по помилкам 5хх.
Тільки в expression виконуємо конвертацію наносекунд в секунди – ділимо результат на 1e9 (мільярд) і тригеримо алерт, p95 latency вище 5 секунд:
{{- range .Values.alerts.traces.backend }}
{{- $ns := . }}
{{- range .severities }}
{{- if not (eq . "critical") }}
- alert: Kraken HTTP Latency p95 High
expr: vmtraces:kraken:http:request_duration:p95{"resource_attr:k8s.namespace.name"="{{ $ns.namespace }}",stats_result="value"} / 1e9 > 5
for: 5m
labels:
severity: {{ . }}
component: backend
environment: {{ $ns.env }}
ilert_routingkey: backend-{{ $ns.env }}-{{ . }}
annotations:
summary: "Kraken HTTP p95 latency is high"
description: |-
HTTP p95 latency has been above 5s for more than `{{ "{{" }} $for }}`
*Namespace*: `{{ "{{" }} index $labels "resource_attr:k8s.namespace.name" }}`
*Service name*: `{{ "{{" }} index $labels "resource_attr:service.name" }}`
*HTTP route*: `{{ "{{" }} index $labels "span_attr:http.route" }}`
*P95 latency*: `{{ "{{" }} printf "%.2f" $value }}` seconds
<https://{{ $.Values.monitoring.root_url }}/explore?orgId=1&left=%7B%22datasource%22:%22{{ $.Values.monitoring.victoria_traces_uid }}%22,%22queries%22:%5B%7B%22refId%22:%22A%22,%22datasource%22:%7B%22type%22:%22jaeger%22,%22uid%22:%22{{ $.Values.monitoring.victoria_traces_uid }}%22%7D,%22queryType%22:%22search%22,%22service%22:%22{{ "{{" }} index $labels "resource_attr:service.name" }}%22,%22tags%22:%22http.route%3D{{ "{{" }} index $labels "span_attr:http.route" }}%22,%22limit%22:100%7D%5D,%22range%22:%7B%22from%22:%22now-1h%22,%22to%22:%22now%22%7D%7D|:grafana: VictoriaTraces>
{{- end }}
{{- end }}
{{- end }}
З HTTP поки все – йдемо далі.
Метрики AWS API
Для вибору спанів, пов’язаних з AWS можемо використати фільтр по атрибуту "span_attr:rpc.system".
Перевіряємо що у нас є цікавого:
{resource_attr:service.name=~"kraken-.*"} "span_attr:rpc.system":"aws-api"
Тут вже як раз бачимо kind=3 – наш бекенд виступає в ролі CLIENT до AWS API.
Метрика “vmtraces:backend:aws:client_error:rate”
З цікавих метрик, які тут можемо зробити – алертити, коли виникають помилки при запитах до AWS.
Описуємо новий Recording Rule:
- record: vmtraces:kraken:aws:client_error:rate
expr: |
{resource_attr:service.name=~"kraken-.*"} "span_attr:rpc.system":"aws-api" status_code:=2
| stats by ("resource_attr:k8s.namespace.name", "resource_attr:service.name", "span_attr:rpc.service", name) rate() requests_per_sec
Тут фільтр status_code:=2 вибираємо тільки помилки:
В атрибутах спану маємо і сам stacktrace, і status_message – але їх в лейбли не пишемо, це вже можна буде глянути в Grafana.
Деплоїмо, перевіряємо метрику:
Алерт “Backend is getting errors from AWS services”
Описуємо алерт – тут все аналогічно, тільки в Grafana link робимо фільтр по name, і його ж виводимо в тексті алерта в полі Operation – бо там є і AWS service name, і тип операції:
{{- range .Values.alerts.traces.backend }}
{{- $ns := . }}
{{- range .severities }}
{{- if not (eq . "critical") }}
- alert: Kraken AWS Client Errors
expr: vmtraces:kraken:aws:client_error:rate{"resource_attr:k8s.namespace.name"="{{ $ns.namespace }}",stats_result="requests_per_sec"} > 0
for: 1s
labels:
severity: {{ . }}
component: backend
environment: {{ $ns.env }}
ilert_routingkey: backend-{{ $ns.env }}-{{ . }}
annotations:
summary: "Kraken is getting errors from AWS services"
description: |-
AWS client error rate has been above 0 for more than `{{ "{{" }} $for }}`
*Namespace*: `{{ "{{" }} index $labels "resource_attr:k8s.namespace.name" }}`
*Service name*: `{{ "{{" }} index $labels "resource_attr:service.name" }}`
*Operation*: `{{ "{{" }} index $labels "name" }}`
*Error rate*: `{{ "{{" }} printf "%.3f" $value }}` req/s
<https://{{ $.Values.monitoring.root_url }}/explore?orgId=1&left=%7B%22datasource%22:%22{{ $.Values.monitoring.victoria_traces_uid }}%22,%22queries%22:%5B%7B%22refId%22:%22A%22,%22datasource%22:%7B%22type%22:%22jaeger%22,%22uid%22:%22{{ $.Values.monitoring.victoria_traces_uid }}%22%7D,%22queryType%22:%22search%22,%22service%22:%22{{ "{{" }} index $labels "resource_attr:service.name" }}%22,%22operation%22:%22{{ "{{" }} index $labels "name" }}%22,%22tags%22:%22rpc.service%3D{{ "{{" }} index $labels "span_attr:rpc.service" }}%22,%22limit%22:100%7D%5D,%22range%22:%7B%22from%22:%22now-1h%22,%22to%22:%22now%22%7D%7D|:grafana: VictoriaTraces>
{{- end }}
{{- end }}
{{- end }}
Деплоїмо, чекаємо на алерт в Slack:
І маємо лінк на Grafana з фільтром по Operation name:
Метрики RDS
І останній приклад – із запитами до RDS.
Метрика “vmtraces:backend:db:query_duration:p95”
Описуємо Recording Rule, фільтруємо по "scope_name":="opentelemetry.instrumentation.sqlalchemy":
# DB query p95 latency
- record: vmtraces:kraken:db:query_duration:p95
expr: |
{resource_attr:service.name=~"kraken-.*"} "scope_name":="opentelemetry.instrumentation.sqlalchemy"
| stats by ("resource_attr:k8s.namespace.name", "resource_attr:service.name", "span_attr:db.name", name) quantile(0.95, duration) p95_duration
Як і для HTTP – рахуємо 95 персентиль.
Алерт “Kraken DB Query p95 Duration High”
Аналогічно до HTTP алерта – конвертуємо value в секунди:
{{- range .Values.alerts.traces.backend }}
{{- $ns := . }}
{{- range .severities }}
{{- if not (eq . "critical") }}
- alert: "Kraken DB Query p95 Duration High"
expr: vmtraces:kraken:db:query_duration:p95{"resource_attr:k8s.namespace.name"="{{ $ns.namespace }}",stats_result="p95_duration"} / 1e9 > 1
for: 1m
labels:
severity: {{ . }}
component: backend
environment: {{ $ns.env }}
ilert_routingkey: backend-{{ $ns.env }}-{{ . }}
annotations:
summary: "Kraken DB query p95 duration is high"
description: |-
DB query p95 duration has been above 1s for more than `{{ "{{" }} $for }}`
*Namespace*: `{{ "{{" }} index $labels "resource_attr:k8s.namespace.name" }}`
*Service name*: `{{ "{{" }} index $labels "resource_attr:service.name" }}`
*DB name*: `{{ "{{" }} index $labels "span_attr:db.name" }}`
*Query*: `{{ "{{" }} index $labels "name" }}`
*P95 duration*: `{{ "{{" }} printf "%.2f" $value }}` seconds
<https://{{ $.Values.monitoring.root_url }}/explore?schemaVersion=1&panes=%7B%22dlh%22:%7B%22datasource%22:%22dfl962zwff6yoa%22,%22queries%22:%5B%7B%22refId%22:%22A%22,%22datasource%22:%7B%22type%22:%22jaeger%22,%22uid%22:%22dfl962zwff6yoa%22%7D,%22queryType%22:%22search%22,%22service%22:%22{{ "{{" }} index $labels "resource_attr:service.name" }}%22,%22limit%22:100,%22tags%22:%22%22,%22operation%22:%22{{ "{{" }} index $labels "name" }}%22%7D%5D,%22range%22:%7B%22from%22:%22now-1h%22,%22to%22:%22now%22%7D,%22compact%22:false%7D%7D&orgId=1|:grafana: VictoriaTraces>
{{- end }}
{{- end }}
{{- end }}
В Query теж робимо аналогічно до алерта AWS – виводимо значення лейбли name, бо там є частина запиту.
В результаті в Slack отримуємо такий алерт:
І лінк в Grafana з фільтром по Operation name – “INSERT staging_kraken_db“:
Власне, на цьому все.
Вийшло набагато краще, ніж старі алерти з логів.
А коли ще додамо OTel Collectors – буде ще краще.
![]()











