Перейти к основному содержимому

events, logs, metrics, trace

graphenectl events <kind> <id> [-f]
graphenectl logs <kind> <id> [-f]
graphenectl metrics <kind> <id> [-f]
graphenectl trace <kind> <id> [-f]

graphenectl <kind>/<id> logs -f # форма от ресурса

У каждой записи Graphene пять измерений; get читает первое — состояние, а эти четыре глагола читают остальные. Они работают с любой записью: docker/nginx, agent/vm-1 и run по голому id. Измерение принадлежит записи, поэтому запись может стоять первой: graphenectl pipeline/x logs -f эквивалентна graphenectl logs pipeline/x -f.

ИзмерениеГлаголИсточник
2 — событияeventsсобственная workflow history записи: плоскость истины
3 — логиlogsистория из log backend, затем live push
4 — метрикиmetricsPromQL range snapshot, затем live push
5 — tracetraceJaeger JSON snapshot, затем live push

Follow работает push-потоком, не polling. Дверь сервера уже принимает каждый сигнал workers и агентов, поэтому запись попадает в -f сразу при поступлении. Для логов подписка открывается до чтения истории, а шов дедуплицируется. Медленный клиент сбрасывает самые старые live entries и получает явный счётчик потерь.

Флаги​

ФлагКомандыЧто делает
-f, --followвсе четырепродолжать live stream до остановки
--query <expr>logs, metrics, traceсвой запрос на языке backend, вычисляемый внутри записи (ниже)
--start, --endlogs, metricsокно: RFC3339 или «столько назад» (-2h, -10m)
--step <dur>metricsшаг range-запроса (30s, 1m); по умолчанию диапазон/200, не меньше 15 с; не больше 11 000 точек на серию
--limit <n>logsзаписей на страницу (по умолчанию 1000, не больше 10 000)
--desclogsновые сначала
--page <token>logsпродолжить с токена, который напечатала прошлая страница
--severity, --stream, --agent, --entity, --textlogsфильтры, через AND: уровни (повторяемый, регистр не важен — error, ERROR и Error от backend это один уровень; строка, названная только числом OTel severity, считается по своей полосе — 17–20 это ERROR; в ответах уровни в верхнем регистре), поток job, агент-источник, запись, о которой строка, текст в теле
--facets <fields>logsпосчитать значения этих полей в выборке вместо списка строк
--traces <n>traceтрейсов в snapshot (по умолчанию 20)

Две формы запроса​

Каждое измерение принимает запрос на языке своего backend — LogsQL, PromQL, параметры поиска Jaeger — в двух формах, различающихся тем, чей это вопрос:

  • Raw — запрос сам по себе, без записи: graphenectl metrics 'rate(...)'. Всё хранилище, поверхность администратора.

  • Scoped — запрос вместе с записью: graphenectl metrics run x --query 'rate(stroppy_ops_total[1m])'. Тот же язык, но дверь накладывает на него скоуп записи — её namespace, метки корреляции, момент рождения — так, что выражению из него не вырваться: фильтр LogsQL заключён в скобки внутри скоупа, к выражению PromQL backend применяет скоуп на каждом селекторе и подзапросе, у параметров Jaeger теги скоупа сильнее пользовательских. Авторизуется как любое чтение записи — администратор не нужен.

    Фильтр LogsQL должен закрываться: скобки сбалансированы вне строковых литералов, литералы завершены, без пайпа. *) OR (… закрыл бы скобки скоупа изнутри, поэтому отвергается как неверный аргумент до того, как его увидит backend; за скобками backend дополнительно получает namespace своим собственным extra-фильтром. --query не сочетается с -f: live-записи нельзя отфильтровать на языке backend, поэтому follow принимает флаги выборки (--severity, --stream, --agent, --entity, --text) и применяет их и к живому хвосту.

Также действуют флаги подключения и формы вывода; --jq выполняется для каждого сообщения потока.

events​

Собственная история записи классифицируется, но ничего не отбрасывает. Внутренняя бухгалтерия Temporal (internal-*: workflow tasks, таймеры) — большая часть истории и ничего из её сюжета: вид по умолчанию её опускает и сообщает, сколько строк скрыто; -o wide её печатает, -o json несёт её всегда.

$ graphenectl events run logs-test-2
20:55:55.091 run-started
20:55:57.549 activity-scheduled server.agent.declare
20:56:03.128 activity-completed server.agent.declare
20:57:12.331 activity-failed k8s.apply @edge-1 secret "kubeconfig" not found
… 41 internal events hidden; -o wide shows them

В терминале kind окрашен по исходу: scheduled и started — жёлтым, completed — зелёным, failed и timed out — красным, canceled — фиолетовым.

Веха, которую поставил пайплайн (obs.Event), — событие kind note: subject — имя вехи, input — payload. --kind оставляет только названные kinds (повторяемый) и фильтрует на сервере — десяток вех длинного прогона читается без всей его history:

$ graphenectl events run nightly-0917 --kind note
14:10:02.118 note bench.started {"vus":4}
14:11:11.795 note stand.kept {"root":"agent/db-1","keep":"2h"}

Подсчитать, что упало:

$ graphenectl events run logs-test-2 --jq '.kind' | sort | uniq -c | sort -rn
6 activity-scheduled
1 run-terminated
1 run-started

logs​

Выборка, а не хвост: окно, страница, фильтры. Записи идут от старых к новым (--desc — новые сначала), по одной странице; страницу закрывает строка в stderr — сколько пришло и, если в выборке есть ещё, токен продолжения; одинаковые timestamp между страницами не теряются.

$ graphenectl logs run nightly-0917 --severity WARN,ERROR --stream stderr --start -30m --limit 200
14:11:06.800 WRN infra-tests │ job infra-tests exited with status 1
14:11:08.891 ERR bench │ connection refused
… 200 of more; next page: --page MTc5MDA...
$ graphenectl logs run nightly-0917 --query 'level:error AND _msg:~"timeout.*pg"'
$ graphenectl logs run nightly-0917 --facets severity,job
FIELD VALUE RECORDS
severity INFO 61
WRN 2

job infra-tests 58
bench 5
$ graphenectl logs run logs-test-2
20:55:58.269 INF Started Worker Namespace default TaskQueue run/logs-test-2
20:55:58.269 DBG ExecuteActivity ... ActivityType k8s.apply
20:56:41.002 INF infra-tests │ 3 passed in 7.80s
20:57:12.331 ERR secret "kubeconfig" not found

Каждая строка — время, трёхбуквенный уровень (DBG INF WRN ERR), источник, который библиотека проставила на записи — имя docker job, — и тело. Предупреждение жёлтое, а ошибка красная строкой целиком: в потоке вывода они не должны выглядеть как всё остальное. -o wide дописывает все атрибуты записи.

У вывода инструмента своей важности нет — pytest, компилятор, shell-скрипт записываются одним уровнем. Там уровень остаётся тем, что сказал источник, а размечаются говорящие слова: FAILED, ERROR, Traceback, 2 failed — красным, WARNING, 3 warnings — жёлтым, PASSED, 3 passed — зелёным, а пояснение pytest E … — красным целиком.

Для run сюда входит собственный stdout orchestrator-контейнера — сырая внутренность worker, которую читает сервер.

metrics​

По умолчанию печатается таблица серий с линией тренда; -o wide рисует каждую серию графиком; -o json возвращает стандартный PromQL range response как есть, --jq выполняется поверх него. С -f после snapshot приходят live points по мере прохождения через коллектор. --step задаёт шаг; --query вычисляет ваш PromQL внутри записи — включая нативные метрики инструмента (stroppy_*), в каком бы написании метки корреляции ни лежали в хранилище:

$ graphenectl metrics run nightly-0917 --query 'rate(stroppy_ops_total[1m])' --step 30s --start -1h
$ graphenectl gitsource/main metrics -f
No metrics recorded.
14:57:32.829 graphene.activity = 2
14:57:32.829 graphene.door.invoke = 2
$ graphenectl metrics run logs-test-2
METRIC N VALUE MIN MAX TREND SERIES
docker.container.memory.bytes 3 68.6MiB 68.6MiB 70.5MiB ▄██▁ activity=docker.container.observe agent=db-1
graphene.activity.seconds 2 9.46s 4.05s 9.46s ▁████ activity=docker.job agent=db-1
2 38.86s 14.59s 38.86s ▁▁▁▁█ activity=docker.job agent=runner-1
stroppy.iterations_per_second 2493 2493 2493 ▁ activity=publish-metrics agent=runner-1

Как читать строку:

  • метрика называется один раз, её серии идут ниже;
  • SERIES — набор лейблов без шума: лейблы, общие для всех строк одной записи (run, контур), и префикс graphene. отброшены;
  • гистограмма сворачивается в одну строку: N — число наблюдений, VALUE — их среднее, а тренд — это среднее во времени;
  • единица берётся из имени метрики, по соглашению самого OTel: …seconds читается как длительность, …bytes — в двоичных единицах, …percent — со знаком %.
$ graphenectl metrics run logs-test-2 -o wide
docker.container.memory.bytes (average) activity=docker.container.observe agent=db-1
70.5MiB ┤ ██████████████████
┤ ██████████████████
69.6MiB ┤▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂██████████████████
┤████████████████████████████████████
┤████████████████████████████████████
68.6MiB ┤████████████████████████████████████▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁
13:09:36 13:10:30
$ graphenectl metrics run logs-test-2 -o json
{"status":"success","data":{"resultType":"matrix","result":[...]}}

trace​

Водопад: каждый trace — дерево spans по родству, и у каждого span своя полоса на общей шкале времени — где в trace он находился и сколько длился. Упавший span красный. -o json печатает стандартный Jaeger JSON, --jq выполняется поверх него:

$ graphenectl trace run logs-test-2
trace 22913539a7ae5e371090460ae51607fd 13:10:15.510 1.58s
StartActivity:server.artifact.declare · graphene-pipeline 100µs ▏ ━
└─ RunActivity:server.artifact.declare · graphene-pipeline 1.08s ▏ ━━━━━━━━━━━━━━━━━━━━━━━━━━━
StartActivity:publish-metrics · graphene-pipeline 146µs ▏ ━
└─ RunActivity:publish-metrics · graphene-pipeline 20.9ms ▏ ━

Span, слишком короткий, чтобы быть видимым в масштабе trace, всё равно получает одну ячейку — он был.

$ graphenectl trace run logs-test-2 --jq '.data[0].spans | length'
128

Что значит ответ​

Дверь отвечает кодом, а не молчанием и не одним текстом:

ОтветЗначит
записи, затем строка страницы в stderrвыборка; … N of more называет токен следующей страницы
<ref> has no log records in this selection., код 0запись есть, выборка пуста
no record <ref>, код 2такой записи нет
invalid_argumentневерен запрос, шаг или фильтр — дальше слова самого backend
unavailablebackend не ответил или ответил 5xx
unimplementedза измерением нет backend, либо scoped-PromQL на backend без extra_filters
permission_deniedтокену нельзя читать запись, или raw-поверхность запросил не администратор
... N lines dropped в stderrfollow сбросил строки медленному потребителю