20 KiB
Наблюдаемость
Эксплуатационные журналы stdout
admin-api и mcp-server используют один жизненный цикл наблюдаемости и
одинаковую однострочную JSON-схему:
{
"timestamp": "2026-07-26T12:34:56.789Z",
"level": "INFO",
"service": "admin-api",
"version": "0.3.1",
"environment": "production",
"target": "admin_api::request_context",
"event": "admin.request.completed",
"request_id": "019...",
"fields": {
"method": "GET",
"route": "/api/operations",
"status": 200
}
}
Обязательны timestamp, level, service, version, environment,
target, event и объект fields. Поля request_id и trace_id независимы
и появляются только при наличии соответствующего контекста. Имена событий
статичны и имеют вид <компонент>.<объект>.<исход>.
Форматирование не использует ANSI. Одна строка stdout всегда соответствует одному JSON-объекту.
Сквозная корреляция
Admin API и MCP принимают x-request-id как непрозрачный идентификатор.
Допустимое входное значение сохраняется без изменений. Если заголовок
отсутствует или содержит пробелы, управляющие символы, ,, ;, не-ASCII
символы либо больше 128 байт, Crank создаёт UUIDv7.
Один идентификатор:
- возвращается в
x-request-idуспешного или ошибочного ответа; - записывается в корневой span входного запроса;
- передаётся в runtime;
- заменяет статические или полученные из mapping значения
x-request-idиx-correlation-idперед исходящим REST-запросом; - сохраняется в прикладной истории вызова.
request id не является trace id и не подменяет распределённую
трассировку.
Очистка
Перед сериализацией рекурсивно очищаются пароли, секреты, токены, ключи доступа, authorization, cookie, query, полные payload, body, arguments, result и response. Составные имена полей проверяются по тем же правилам. Из URL удаляются учётные данные, query и fragment.
Пределы одного события:
- строка: 1024 байта;
- массив: 32 элемента;
- объект: 64 поля;
- вложенность: 8 уровней;
- вся строка JSON вместе с завершающим переводом строки: 16 КиБ.
Усечение сохраняет корректный UTF-8 и JSON. Секрет заменяется целиком на
[REDACTED]; его часть, длина или хеш не выводятся. Если после очистки
событие превышает 16 КиБ, fields заменяется ограниченным признаком
усечения. Произвольное представление Debug не считается безопасным
структурированным значением: для вложенных данных используется
предварительно очищенный JSON.
Уровень и окружение задаются через CRANK_LOG_LEVEL и
CRANK_ENVIRONMENT. Для stdout не требуются внешние службы, фоновые очереди
или сетевые endpoint. Их отсутствие не влияет на readiness.
Критические ошибки
Необязательный Sentry-совместимый канал включается одним значением:
CRANK_SENTRY_DSN=https://public-key@errors.example.com/1
Пустой или отсутствующий CRANK_SENTRY_DSN полностью отключает канал.
Неверное непустое значение останавливает запуск до приёма запросов, при этом
само значение не попадает в ошибку. Стандартная переменная SENTRY_DSN не
используется.
Канал получает только необработанные panic и явно классифицированные критические ошибки. Ожидаемые ошибки HTTP, MCP, runtime и внешних API остаются в обычных журналах, показателях и трассах. Для критического события допустимы только:
- закрытая категория
panic,startup,internalилиdata_integrity; service,releaseиenvironment;- доступные
request_idиtrace_id; - статическое сообщение без исходного текста ошибки.
До отправки удаляются request, user, breadcrumbs, URL, query, cookie, authorization, payload, произвольные contexts и extra, а также исходный текст panic или ошибки. Performance tracing, журналы, показатели и отслеживание сессий Sentry SDK отключены. Отказ внешнего приёмника не меняет результат продуктового запроса и не создаёт рекурсивное событие.
GlitchTip и другие Sentry-совместимые приёмники не входят в Community Compose.
Их доступность не участвует в /health и /ready.
Показатели Prometheus
admin-api и mcp-server создают отдельные поверхности показателей. Они не
смешиваются с продуктовыми маршрутами:
| Сервис | Значение по умолчанию |
|---|---|
admin-api |
127.0.0.1:9464 |
mcp-server |
127.0.0.1:9465 |
На каждом адресе существуют только:
/metrics— показатели Prometheus;/health— статическая проверка самого listener-а.
Любой другой путь возвращает 404. /health этой поверхности не заменяет
/ready рабочего сервиса. Ошибка запуска listener-а останавливает процесс до
приёма рабочих запросов.
Loopback доступен без авторизации. Для любого non-loopback адреса обязателен
отдельный CRANK_METRICS_BEARER_TOKEN; без него конфигурация отклоняется при
старте. Токен не переиспользует сессию администратора или ключ MCP. Оба
маршрута внешней поверхности требуют Authorization: Bearer ....
Community Compose не содержит Prometheus или Grafana и не публикует порты 9464/9465 на host. Для сетевого сборщика необходимо явно:
- задать
CRANK_ADMIN_METRICS_BIND=0.0.0.0:9464иCRANK_MCP_METRICS_BIND=0.0.0.0:9465; - передать непустой
CRANK_METRICS_BEARER_TOKEN; - подключить сборщик к внутренней сети Compose либо отдельно опубликовать нужные порты с ограничением firewall.
Токен сборщика хранится в файле, а не в prometheus.yml. Актуальная
конфигурация Prometheus использует authorization.credentials_file:
scrape_configs:
- job_name: crank-admin
static_configs:
- targets: ["admin-api:9464"]
authorization:
type: Bearer
credentials_file: /run/secrets/crank_metrics_token
- job_name: crank-mcp
static_configs:
- targets: ["mcp-server:9465"]
authorization:
type: Bearer
credentials_file: /run/secrets/crank_metrics_token
Семейства первой версии:
crank_http_requests_total,crank_http_request_duration_seconds,crank_http_inflight;crank_mcp_requests_total,crank_mcp_active_sessions,crank_mcp_active_streams;crank_tool_invocations_total,crank_tool_invocation_duration_seconds;crank_upstream_requests_total,crank_upstream_request_duration_seconds;crank_runtime_inflight,crank_runtime_limit_rejections_total,crank_runtime_cache_total,crank_idempotency_total,crank_confirmation_total;crank_db_pool_connections;crank_catalog_tools,crank_catalog_estimated_context_tokens,crank_catalog_warnings;crank_invocation_history_lost_total,crank_telemetry_export_failures_total.
Recorder добавляет к рядам проверенные статические labels service,
version, environment. Остальные labels имеют закрытый набор значений.
Имена, типы и допустимые значения labels определены в независимом нижнем
crate crank-metrics. Product-код вызывает только его типизированный фасад;
прямая production-зависимость от metrics вне crank-metrics и
crank-observability запрещена архитектурной проверкой Cargo metadata.
Запрещено использовать workspace, идентификаторы агента, операции или
запроса, фактический URL, текст ошибки, payload и пользовательский текст.
HTTP route берётся только из шаблона Axum; неизвестные маршруты и методы
сворачиваются в unmatched и OTHER.
crank_mcp_active_sessions отражает число ещё не истёкших транспортных
сессий в общем хранилище, а crank_mcp_active_streams — число открытых в
этом экземпляре SSE-потоков. Прикладные ошибки JSON-RPC и
tools/call.result.isError=true учитываются отдельно от успешного HTTP 200.
Счётчики cache, idempotency и confirmation описывают переходы
соответствующих стадий: hit/miss и ошибки хранилища, execute/replay/conflict
и required/approved/invalid token. Отменённый future runtime или upstream
завершает соответствующее измерение закрытым исходом aborted, поэтому
фактически начатый вызов не исчезает из показателей.
Buckets длительности фиксированы кодом:
0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5,
10, 30, 60 секунд. Это пока техническая шкала, а не SLO. Фактические
series и расход памяти измеряются в истории 1.8; произвольный численный
бюджет до замеров не назначается.
Распределённые трассы
Crank принимает и передаёт стандартный W3C traceparent на границах Admin
API, MCP и исходящих REST-вызовов. x-request-id остаётся отдельным
идентификатором запроса. Baggage не извлекается и не передаётся.
Экспорт отключён по умолчанию: при пустых
OTEL_EXPORTER_OTLP_TRACES_ENDPOINT и OTEL_EXPORTER_OTLP_ENDPOINT
provider, batch processor и фоновый поток не создаются. После включения
используется только OTLP/HTTP binary protobuf. Community Compose не включает
Collector или хранилище трасс: оператор подключает внешний совместимый
приёмник.
Уровень эксплуатационных журналов не отключает трассы. В OTLP попадают
только явно отмеченные spans с внутренней целью crank::trace; события
tracing не экспортируются, чтобы их поля не обходили очистку JSON-журналов.
Новые экспортируемые spans должны использовать ту же цель и содержать только
ограниченные безопасные атрибуты.
Очередь экспорта конечна. Экспорт выполняется вне обработки продуктового
запроса, поэтому недоступность приёмника не меняет HTTP, MCP или runtime
результат. Ошибки доставки учитываются в
crank_telemetry_export_failures_total{signal_type="trace",exporter="otlp"}
без журналирования endpoint, headers и без создания новой OTLP-трассы.
Входящий корректный traceparent становится родителем корневого span.
Некорректный заголовок трактуется как отсутствующий: запрос продолжается с
новой трассой, а исходное значение не возвращается и не журналируется.
Перед исходящим REST-запросом доверенный текущий контекст заменяет любой
traceparent, заданный в настройках операции.
Стадии выполнения инструмента
Crank создаёт дочерний span только тогда, когда соответствующая стадия фактически выполняется:
| Span | Значение |
|---|---|
mcp.rate_limit |
проверка ограничения частоты MCP-запроса |
mcp.access.check |
машинная проверка доступа |
mcp.catalog.load |
получение опубликованного каталога |
mcp.tools.resolve |
разрешение конкретного инструмента |
approval.check |
применимая проверка подтверждения |
runtime.execute |
выполнение инструмента в runtime |
runtime.arguments.map |
проверка схемы и отображение аргументов |
runtime.idempotency |
применимая проверка идемпотентности |
upstream.http |
фактический HTTP-вызов внешней системы |
runtime.response.transform |
отображение и проверка ответа |
auth.resolve |
применимое разрешение профиля авторизации и секретов |
approval.recovery |
фоновое восстановление подтверждённого вызова |
history.write |
запись прикладной истории |
db.query |
значимая операция PostgreSQL |
Например, ответ из кэша или повтор идемпотентного результата не создаёт
upstream.http. Операция без подтверждения и идемпотентности не создаёт
соответствующие spans. Отсутствующая стадия никогда не изображается успешной.
Атрибуты стадий имеют закрытый словарь: outcome, error.category,
db.system и db.operation. Значения задаются перечислениями из
crank-trace; пользовательские идентификаторы, URL, заголовки, query,
аргументы, ответы и текст ошибок туда передать нельзя. Ошибка содержит только
категорию, достаточную для локализации стадии. request_id присутствует
только в проверенном корневом span и сопоставляется с записью прикладной
истории.
Перед OTLP-сериализацией экспортёр повторно применяет разрешительный список имён и атрибутов, удаляет события и ссылки span и очищает текстовое описание статуса ошибки. Эта последняя граница действует независимо от очистки эксплуатационных JSON-журналов и не позволяет новому инструментированию случайно экспортировать пользовательские данные.
Точный список переменных и правила OpenBao приведены в описании runtime-конфигурации.
Прикладные журналы и использование
Crank сохраняет в PostgreSQL данные о тестовых запусках и вызовах опубликованных MCP-инструментов. Это отдельная прикладная история, а не копия stdout.
Если внешнее действие завершилось, но PostgreSQL отклонил запись истории,
Crank не меняет фактический результат и не повторяет действие. Вместо
синтетической записи создаётся эксплуатационный инцидент DC-08:
admin.invocation_history.lostилиmcp.invocation_history.lostв stdout;- внутренний монотонный счётчик без пользовательских меток;
crank_invocation_history_lost_totalи закрытый показатель потери экспорта;- только закрытые поля
source,invocation_statusиerror_category.
Текст ошибки PostgreSQL, payload и секреты в событие не передаются. Внешняя публикация не влияет на результат уже завершённого действия.
Журналы
В журнал попадают:
- операция;
- агент, если вызов пришел через MCP;
- request id;
- статус;
- HTTP status code внешнего API;
- время выполнения;
- краткий preview запроса и ответа;
- категория ошибки, если вызов завершился ошибкой.
При обновлении опубликованного MCP-каталога событие mcp.catalog.analyzed
содержит tool_count, serialized_bytes, estimated_context_tokens,
largest_tool_estimated_context_tokens, recommended_context_tokens,
exceeds_recommended_budget и число предупреждений качества. По этим полям
можно заметить рост цены tools/list до того, как он ухудшит выбор
инструментов моделью.
Использование
Раздел использования агрегирует:
- количество вызовов;
- успешные и ошибочные вызовы;
- долю ошибок;
- задержки p50, p95 и p99;
- распределение вызовов по операциям.
Для чего это нужно
- проверить, вызывают ли агенты нужные инструменты;
- увидеть ошибки маппинга или внешнего API;
- найти медленные endpoint-ы;
- понять, какие инструменты реально используются.