● LIVE· № 001 · SANITIZE ВСЁ, ЧТО УХОДИТ В GIT: ЧИСТКА ПУБЛИЧНЫХ РЕПО ОТ УТЕЧЕК ИДЕНТИЧНОСТИ · 2026.05.11· № 002 · IMAGEGEN-MCP: СВОЙ MCP-СЕРВЕР ДЛЯ ОБЛОЖЕК БЛОГА · 2026.05.11· № 003 · SMART PASTE: ЧИЩУ МУСОР ИЗ ТЕРМИНАЛА ПЕРЕД ВСТАВКОЙ ОДНИМ ХОТКЕЕМ · 2026.05.10· № 004 · CLAUDE CODE TEAM TELEMETRY: ЦЕНТРАЛИЗОВАННАЯ СТАТИСТИКА ПО КОМАНДЕ · 2026.05.07· № 005 · ГАЙД ДЛЯ НОВИЧКОВ: КАК ВКАТЫВАТЬСЯ В CLAUDE CODE · 2026.05.06· 11 СТАТЕЙ · 0 ЧЕРНОВИКОВ
RU / EN
·22 МИН

Claude Code Team Telemetry: централизованная статистика по команде

Как собирать OpenTelemetry-метрики и события Claude Code на свой backend, складывать в реляционную БД и крутить SQL-отчёты по стоимости, продуктивности и ошибкам.

Цель. Собирать централизованную статистику использования Claude Code по команде на одном сервере: стоимость, продуктивность, аномалии.

Подписка. Claude.ai Teams (даёт server-managed settings через ~/.claude/remote-settings.json).

Источник. Built-in OpenTelemetry экспорт Claude Code → OTLP/HTTP JSON endpoint в любом backend-сервисе → реляционная БД.

Прагматичный путь. Берём существующий backend-сервис (домен, TLS, CI/CD, БД уже есть) и добавляем туда два endpoint-а. Никакой отдельной инфры (Collector / Prometheus / Grafana / Docker / nginx) на старте. Heavy stack — опциональное расширение.


1. Архитектура

1.1 MVP — backend endpoint + БД

┌────────────────────────┐         ┌──────────────────────────────────┐
│  Dev laptop × N        │         │  Existing backend service        │
│                        │         │                                  │
│  Claude Code           │ OTLP    │                                  │
│   ├─ metrics ──────────┼─ HTTPS ─┼─► POST /otel/v1/metrics          │
│   └─ logs/events ──────┼─ JSON ──┼─► POST /otel/v1/logs             │
│                        │ Bearer  │       │                          │
│  remote-settings.json  │◄── pull │       ▼                          │
│  (env block, hourly)   │ claude.ai  ┌───────────────────────────┐   │
└────────────────────────┘         │  │ DB: claude_code_*         │   │
                                   │  │  - otlp_raw (JSON blob)   │   │
                                   │  │  - metric_points (parsed) │   │
                                   │  │  - events (parsed)        │   │
                                   │  └───────────────────────────┘   │
                                   │       │                          │
                                   │       │ scheduled парсер 1/мин   │
                                   │       ▼                          │
                                   │  отчёты SQL / Metabase / Grafana │
                                   └──────────────────────────────────┘

Стек на стороне сервера не важен — Node, Python, Go, Java, Rust, что есть. Нужны только: HTTP endpoint с Bearer-проверкой, INSERT в БД, периодическая задача-парсер.

1.2 Heavy alt — отдельный OTel Collector + Prometheus + Grafana

Опционально позже если БД не тянет или нужны готовые дашборды.

Dev laptop × N ──OTLP/gRPC──► OTel Collector ──► Prometheus ──► Grafana

1.3 Ключевые решения

  • JSON, не protobuf. Claude Code умеет OTEL_EXPORTER_OTLP_PROTOCOL=http/json — никаких protobuf-зависимостей на сервере.
  • Push, не pull. Каждый ноут шлёт OTLP/HTTP. Не открываем порты на ноутах.
  • Один Bearer-токен на команду. Per-user не нужны — user.email берётся из OAuth claude.ai.
  • Сырое хранение + ленивый парсер. Сначала JSON в otlp_raw, потом scheduled job раскладывает в типизованные таблицы. Схема OTLP может меняться — raw остаётся как audit trail.
  • Конфиг ноутов через Teams admin console. Один JSON, прилетает всем за час, без bootstrap-скриптов.

2. Что собирается

2.1 Resource attributes (на каждой метрике/событии)

АтрибутОписаниеКонтроль
service.name"claude-code"всегда
service.version / app.versionверсия CLIOTEL_METRICS_INCLUDE_VERSION
os.type, os.version, host.archOS + CPUвсегда
wsl.versionтолько на WSLавто
session.idUUID сессииOTEL_METRICS_INCLUDE_SESSION_ID (default on)
organization.idID Teams-организациипри auth
user.account_uuid, user.account_idUUID аккаунта AnthropicOTEL_METRICS_INCLUDE_ACCOUNT_UUID (default on)
user.idанонимный device IDвсегда
user.emailemail из OAuthпри OAuth-логине
terminal.typeiTerm / vscode / cursor / tmuxавто
prompt.idUUID, связывает события одного промптатолько в events
workspace.host_pathsпути из desktop appтолько в events

Через OTEL_RESOURCE_ATTRIBUTES можно прилеплять кастомные labels (team, environment, cost_center и т.п.).

2.2 Метрики (8)

МетрикаТипUnitТриггерАтрибуты
claude_code.session.countcountercountстарт сессииstart_type: fresh/resume/continue
claude_code.lines_of_code.countcountercountправка кодаtype: added/removed
claude_code.pull_request.countcountercountсоздание PR—
claude_code.commit.countcountercountgit commit—
claude_code.cost.usagecounterUSDкаждый API requestmodel, query_source, speed, effort
claude_code.token.usagecountertokensкаждый API requesttype, model, query_source, speed, effort
claude_code.code_edit_tool.decisioncountercountaccept/reject правкиtool_name, decision, source, language
claude_code.active_time.totalcountersecактивность user/CLItype: user/cli

2.3 События / Logs (18)

Все события несут event.name, event.timestamp, event.sequence, prompt.id, workspace.host_paths плюс resource attrs.

EventТриггерКлючевые атрибуты
claude_code.user_promptюзер отправил промптprompt_length, command_name; prompt только с OTEL_LOG_USER_PROMPTS=1
claude_code.tool_resulttool завершёнtool_name, tool_use_id, success, duration_ms, error_type, размеры I/O
claude_code.api_requestуспешный API callmodel, cost_usd, duration_ms, токены, request_id, speed, effort
claude_code.api_errorAPI failmodel, error, status_code, duration_ms, attempt
claude_code.api_retries_exhaustedфинальный failtotal_attempts, total_retry_duration_ms
claude_code.api_request_bodyтолько с OTEL_LOG_RAW_API_BODIESвся переписка с моделью
claude_code.api_response_bodyтолько с OTEL_LOG_RAW_API_BODIESответ модели
claude_code.tool_decisionaccept/reject permissiontool_name, decision, source
claude_code.permission_mode_changedсмена режимаfrom_mode, to_mode, trigger
claude_code.auth/login или /logoutaction, success, auth_method
claude_code.mcp_server_connectionMCP connect/disconnect/failstatus, transport_type, duration_ms
claude_code.internal_errorвнутренний exceptionerror_name, error_code (без message/stack)
claude_code.plugin_installedплагин установленmarketplace.is_official, install.trigger
claude_code.skill_activatedskill вызванskill.name, invocation_trigger
claude_code.at_mentionresolved @mentionmention_type, success
claude_code.hook_execution_startхуки начали выполнятьсяhook_event, hook_name, num_hooks
claude_code.hook_execution_completeхуки завершены+ num_success, num_blocking, total_duration_ms
claude_code.compactionсжатие контекстаtrigger, success, pre_tokens, post_tokens

2.4 Traces (бета)

CLAUDE_CODE_ENHANCED_TELEMETRY_BETA=1. Спаны: tool execution, API request, hook execution. Для базовой задачи не нужны — большой объём, метрик и событий хватает.

2.5 Ссылки


3. PII и приватность

Что уходит по умолчанию

  • Метрики и события без содержимого: длины, длительности, счётчики, типы операций, токены, стоимость, имена моделей и инструментов.
  • user.email — основной идентификатор пользователя.
  • user.account_uuid, user.id, organization.id, session.id, prompt.id.
  • Имена встроенных tools и команд (Read, Write, Bash, Edit и т.д.).

Что НЕ уходит без явного opt-in

ДанныеПо умолчаниюВключается
Текст промптовlength onlyOTEL_LOG_USER_PROMPTS=1
Tool args (Bash команды, URL, паттерны)—OTEL_LOG_TOOL_DETAILS=1 (~4 KB cap)
File pathsв tool eventsOTEL_LOG_TOOL_DETAILS=1
File contents / Bash output—OTEL_LOG_TOOL_CONTENT=1 (60 KB cap, только в traces)
API request body—OTEL_LOG_RAW_API_BODIES=1
API response body—OTEL_LOG_RAW_API_BODIES
Имена custom skills/pluginscollapsedOTEL_LOG_TOOL_DETAILS=1
Extended-thinkingвсегда редактитсянельзя включить

Согласие пользователей

Согласие не запрашивается Claude Code-ом. Конфиг применяется молча через remote-settings.json. Юзер может узнать о телеметрии только посмотрев файл руками.

Поэтому ответственность на админах:

  1. До раскатки уведомить команду: что собираем, где хранится, кто имеет доступ.
  2. Указать в onboarding-доке.
  3. Если включаются OTEL_LOG_TOOL_DETAILS или OTEL_LOG_RAW_API_BODIES — отдельное явное оповещение, поскольку утечь могут пароли в Bash args, токены в URL, содержимое файлов.

Рекомендуемый baseline

Только метаданные. Без opt-in флагов. Достаточно для всех бизнес-метрик (cost / productivity / adoption / health). PII = только user.email.


4. Конфиг для разработчиков (через Teams admin console)

4.1 Где править

claude.ai → Admin → Settings → Claude Code → Managed settings → текстовое поле, в которое вставляется JSON.

4.2 JSON для пуша

К существующему конфигу добавляется блок env. Endpoint указывает на ваш backend-сервис:

{
  "env": {
    "CLAUDE_CODE_ENABLE_TELEMETRY": "1",
    "OTEL_METRICS_EXPORTER": "otlp",
    "OTEL_LOGS_EXPORTER": "otlp",
    "OTEL_EXPORTER_OTLP_PROTOCOL": "http/json",
    "OTEL_EXPORTER_OTLP_ENDPOINT": "https://<your-backend>/otel",
    "OTEL_EXPORTER_OTLP_HEADERS": "Authorization=Bearer REPLACE_WITH_REAL_TOKEN",
    "OTEL_METRIC_EXPORT_INTERVAL": "30000",
    "OTEL_LOGS_EXPORT_INTERVAL": "5000",
    "OTEL_RESOURCE_ATTRIBUTES": "team=backend,environment=prod"
  }
}

Claude Code сам добавит суффиксы /v1/metrics и /v1/logs к OTEL_EXPORTER_OTLP_ENDPOINT — endpoint указывает только на префикс /otel.

4.3 Поведение

  • Прилетает на ~/.claude/remote-settings.json каждого пользователя.
  • Sync: при логине + раз в час.
  • Локальные правки remote-settings.json затираются на следующем sync.
  • env из server-managed побеждает локальный ~/.claude/settings.json (выше по precedence).

4.4 Если admin console не покажет поле для env

Альтернатива — ~/.claude/settings.json-шаблон + onboarding-скрипт, либо managed-settings.json через MDM. Менее удобно, но работает (см. раздел 8).


5. Серверная часть

5.1 Нагрузка

Для команды ~25 человек:

  • ~25 пушей метрик в минуту (OTEL_METRIC_EXPORT_INTERVAL=30s)
  • ~25 пушей логов каждые 5с (OTEL_LOGS_EXPORT_INTERVAL=5s)
  • Размер payload: 5–50 KB сжатого JSON
  • Итого: ~5 RPS пиково, ~50 MB/день сырого JSON

Любая реляционная БД (PostgreSQL / MySQL / MariaDB) спокойно тянет, дополнительная нагрузка минимальна.

5.2 Schema — две таблицы (raw + parsed)

DDL ниже на MySQL. Под Postgres — JSONB вместо JSON, BIGSERIAL вместо BIGINT UNSIGNED AUTO_INCREMENT, TIMESTAMP(3) вместо DATETIME(3). Семантика та же.

Raw bucket — складываем как есть, парсим потом:

CREATE TABLE claude_code_otlp_raw (
    id           BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    received_at  DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
    kind         VARCHAR(16) NOT NULL,            -- 'metrics' | 'logs'
    payload      JSON NOT NULL,
    parsed_at    DATETIME(3) NULL,                 -- метка батч-парсера
    INDEX idx_received (received_at),
    INDEX idx_unparsed (parsed_at, id)
) ENGINE=InnoDB
  ROW_FORMAT=COMPRESSED
  DEFAULT CHARSET=utf8mb4;

Метрики (типизованные точки):

CREATE TABLE claude_code_metric_points (
    id            BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    raw_id        BIGINT UNSIGNED NOT NULL,
    ts            DATETIME(3) NOT NULL,
    user_email    VARCHAR(255),
    user_id       VARCHAR(64),
    session_id    VARCHAR(64),
    org_id        VARCHAR(64),
    metric_name   VARCHAR(128) NOT NULL,
    value         DOUBLE NOT NULL,
    model         VARCHAR(64),
    query_source  VARCHAR(32),
    speed         VARCHAR(16),
    effort        VARCHAR(16),
    type          VARCHAR(32),
    INDEX idx_ts (ts),
    INDEX idx_user_metric (user_email, metric_name, ts),
    INDEX idx_metric_ts (metric_name, ts)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

События:

CREATE TABLE claude_code_events (
    id            BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    raw_id        BIGINT UNSIGNED NOT NULL,
    ts            DATETIME(3) NOT NULL,
    event_name    VARCHAR(64) NOT NULL,
    user_email    VARCHAR(255),
    session_id    VARCHAR(64),
    prompt_id     VARCHAR(64),
    attrs         JSON,
    INDEX idx_ts (ts),
    INDEX idx_event_ts (event_name, ts),
    INDEX idx_user_event (user_email, event_name, ts)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

5.3 Endpoint и парсер — два компонента

Реализация на любом стеке. Логика идентична:

Ingestion endpoint — принимает POST на /otel/v1/metrics и /otel/v1/logs:

  1. Проверить заголовок Authorization: Bearer <token> против ожидаемого. На несовпадение — 401.
  2. INSERT body как есть в claude_code_otlp_raw (kind = metrics или logs).
  3. Ответить 200 {}. Без парсинга в hot path.

Парсер — periodic job (cron / scheduler) каждую минуту:

  1. SELECT N сырых записей с parsed_at IS NULL (батч 500–2000).
  2. Для каждой:
    • JSON.parse → пройтись по resourceMetrics / resourceLogs.
    • Для метрик: вытащить из dataPoints имя, значение, timestamp + атрибуты ресурса и точки. INSERT в claude_code_metric_points.
    • Для логов: вытащить event.name, timestamp, атрибуты. INSERT в claude_code_events (остальное в JSON-колонку attrs).
  3. UPDATE parsed_at = NOW() на обработанных.

Атрибуты в OTLP лежат как массив [{"key":"...","value":{"stringValue":"..."}}]. Хелпер раскладывает в Map<String, String>, перебирая stringValue / intValue / doubleValue / boolValue.

5.4 TTL — крон-удаление старых

Простейший вариант — отдельная scheduled-задача в самом приложении: раз в час DELETE FROM claude_code_otlp_raw WHERE received_at < NOW() - INTERVAL 90 DAY LIMIT 50000. Раз в день — то же для типизованных таблиц с горизонтом 365 дней.

В MySQL можно через EVENT (event_scheduler = ON), в Postgres — через pg_cron. Внешний cron + psql/mysql тоже годится.

5.5 Развёртывание — пошагово

  1. Сгенерировать токен: openssl rand -hex 32 → положить в secret store.
  2. Применить миграцию (Flyway / Liquibase / Alembic / sqlx-migrate) с тремя таблицами.
  3. Добавить ingestion endpoint + парсер в код приложения.
  4. Прокинуть токен в конфиг приложения через переменную окружения / secret manager. Никогда не коммитить.
  5. Деплой через CI.
  6. Smoke-тест с локальной машины:
    curl -X POST https://<your-backend>/otel/v1/metrics \
         -H "Authorization: Bearer $TOKEN" \
         -H "Content-Type: application/json" \
         -d '{"resourceMetrics":[]}'
    # ожидаем 200 {}
  7. Включить env-блок в Teams admin console (раздел 4.2). В течение часа метрики начнут приходить.
  8. Проверить через час: SELECT COUNT(*), MIN(received_at), MAX(received_at) FROM claude_code_otlp_raw;.

6. Безопасность

УгрозаМитигация
Сторонний пуш на endpointBearer-токен в Authorization, проверка в endpoint
Перехват трафикаTLS на домене (или managed proxy)
Утечка токенаОдин shared токен, ротация через admin console + перевыпуск в secret store
Доступ к данным в БДСуществующие права доступа (read-only роль для аналитики)
PII утечки в метрикахНе включать OTEL_LOG_* флаги без явного оповещения команды
Bash args с секретамиЕсли включён OTEL_LOG_TOOL_DETAILS — фильтр в парсере (regex маска токенов / паролей перед сохранением)
DoS на endpointRate-limit на уровне приложения (token bucket) или reverse-proxy перед ним

Опциональный hardening:

  • IP whitelist на уровне API gateway (если разработчики через VPN).
  • Алерт на падение INSERT-RPS в claude_code_otlp_raw за 1 час в рабочее время — индикатор поломки телеметрии или массового отключения.
  • Ротация токена: при компрометации генерим новый, обновляем в admin console — за час разъедется. Старый оставить валидным ещё час (overlap), потом отрубить.

7. Отчёты — SQL

Запросы работают по claude_code_metric_points и claude_code_events. Можно крутить из любого SQL-клиента (DataGrip, Metabase, Apache Superset, CLI). Готовые дашборды в Grafana — позже через MySQL/Postgres datasource.

7.1 Cost

-- суммарная стоимость за сутки
SELECT ROUND(SUM(value), 2) AS cost_usd
FROM claude_code_metric_points
WHERE metric_name = 'claude_code.cost.usage'
  AND ts >= NOW() - INTERVAL 1 DAY;
 
-- топ-10 пользователей по тратам за неделю
SELECT user_email, ROUND(SUM(value), 2) AS cost_usd
FROM claude_code_metric_points
WHERE metric_name = 'claude_code.cost.usage'
  AND ts >= NOW() - INTERVAL 7 DAY
GROUP BY user_email
ORDER BY cost_usd DESC
LIMIT 10;
 
-- стоимость по моделям за сутки
SELECT model, ROUND(SUM(value), 2) AS cost_usd
FROM claude_code_metric_points
WHERE metric_name = 'claude_code.cost.usage'
  AND ts >= NOW() - INTERVAL 1 DAY
GROUP BY model
ORDER BY cost_usd DESC;
 
-- стоимость по effort level
SELECT effort, ROUND(SUM(value), 2) AS cost_usd
FROM claude_code_metric_points
WHERE metric_name = 'claude_code.cost.usage'
  AND ts >= NOW() - INTERVAL 1 DAY
GROUP BY effort;

7.2 Tokens

-- токены по типам за час
SELECT type, SUM(value) AS tokens
FROM claude_code_metric_points
WHERE metric_name = 'claude_code.token.usage'
  AND ts >= NOW() - INTERVAL 1 HOUR
GROUP BY type;
 
-- cache hit ratio
SELECT
    SUM(CASE WHEN type = 'cacheRead' THEN value ELSE 0 END)
    / SUM(CASE WHEN type IN ('input', 'cacheRead') THEN value ELSE 0 END) AS cache_hit_ratio
FROM claude_code_metric_points
WHERE metric_name = 'claude_code.token.usage'
  AND ts >= NOW() - INTERVAL 1 HOUR;

7.3 Productivity

-- строк добавлено/удалено по людям за неделю
SELECT user_email, type, SUM(value) AS lines
FROM claude_code_metric_points
WHERE metric_name = 'claude_code.lines_of_code.count'
  AND ts >= NOW() - INTERVAL 7 DAY
GROUP BY user_email, type
ORDER BY user_email;
 
-- accept rate правок
SELECT
    SUM(CASE WHEN type LIKE 'accept%' THEN value ELSE 0 END)
    / SUM(value) AS accept_rate
FROM claude_code_metric_points
WHERE metric_name = 'claude_code.code_edit_tool.decision'
  AND ts >= NOW() - INTERVAL 1 DAY;

7.4 Engagement

-- активное время в минутах по людям за день
SELECT user_email, ROUND(SUM(value) / 60) AS minutes
FROM claude_code_metric_points
WHERE metric_name = 'claude_code.active_time.total'
  AND ts >= NOW() - INTERVAL 1 DAY
GROUP BY user_email
ORDER BY minutes DESC;
 
-- DAU за неделю
SELECT DATE(ts) AS day, COUNT(DISTINCT user_email) AS dau
FROM claude_code_metric_points
WHERE metric_name = 'claude_code.session.count'
  AND ts >= NOW() - INTERVAL 7 DAY
GROUP BY day
ORDER BY day;

7.5 Reliability

-- API error rate за последний час
SELECT
    SUM(CASE WHEN event_name = 'claude_code.api_error' THEN 1 ELSE 0 END)
    / COUNT(*) AS error_rate
FROM claude_code_events
WHERE event_name IN ('claude_code.api_request', 'claude_code.api_error')
  AND ts >= NOW() - INTERVAL 1 HOUR;
 
-- топ ошибок tools
SELECT
    JSON_UNQUOTE(JSON_EXTRACT(attrs, '$.tool_name')) AS tool,
    JSON_UNQUOTE(JSON_EXTRACT(attrs, '$.error_type')) AS error,
    COUNT(*) AS cnt
FROM claude_code_events
WHERE event_name = 'claude_code.tool_result'
  AND JSON_EXTRACT(attrs, '$.success') = 'false'
  AND ts >= NOW() - INTERVAL 1 DAY
GROUP BY tool, error
ORDER BY cnt DESC
LIMIT 20;

7.6 Алерты

Реализация — scheduled-задача в самом приложении или внешний cron, шлёт в Slack webhook / email.

АлертSQL-условиеSeverity
Daily cost spikeсегодняшняя стоимость > 2× от среднего за 7 днейwarning
Per-user cost capлюбой юзер > $100/daywarning
API error rateошибок > 5% от запросов за 15 минcritical
Telemetry silence0 INSERT в otlp_raw за 1 час в рабочее времяwarning
Parser stuckнеобработанных строк (parsed_at IS NULL) > 10000warning

8. Альтернативы распространению конфига

Если Teams admin console не позволит пушить env (UI ограничен только plugins/permissions):

8.1 ~/.claude/settings.json через onboarding

Bootstrap-скрипт мерджит env-блок в локальный ~/.claude/settings.json каждого разработчика. Минусы: разработчик может удалить, забыть, не запустить.

8.2 managed-settings.json через MDM

Системный путь, защищён правами root:

  • macOS: /Library/Application Support/ClaudeCode/managed-settings.json
  • Linux: /etc/claude-code/managed-settings.json

Раскатка через Jamf / Ansible / Puppet. Перебить локально нельзя. Хорошо для жёсткого compliance, требует MDM-инфры.

8.3 Сравнение

СпособПринуждениеУсилияPer-user токеныРекомендация
Teams admin console (remote-settings.json)сильноеминимумнетпервый выбор
MDM (managed-settings.json)максимальноевысокиевозможно через шаблоныесли есть MDM
Bootstrap-скрипт + ~/.claude/settings.jsonслабоенизкиедаfallback

9. Расширения (когда понадобится)

9.1 Grafana поверх БД

Когда захочется красивых дашбордов:

  1. Поднять Grafana (отдельный контейнер или managed).
  2. Добавить datasource (MySQL / Postgres) → подключить к существующей БД с read-only ролью.
  3. Импортировать дашборды (или собрать из SQL раздела 7).

Без переноса данных, без OTel Collector, без Prometheus.

9.2 Переезд на OTel Collector + Prometheus

Если объём событий превысит ~1M/день или захочется готовых OTel-инструментов:

Claude Code → OTel Collector (auth) → ClickHouse / Prometheus + Grafana

Свой endpoint остаётся как fallback / staging. Конфиг ноутов меняется в admin console на новый endpoint.

9.3 Логи в Loki / отдельный лог-стек

При росте claude_code_events парсенные события можно дублировать в Loki через Vector / Fluent Bit. Лучше для full-text поиска по содержимому промптов (если когда-то включится OTEL_LOG_USER_PROMPTS=1).

9.4 Grafana Cloud как замена self-hosted

Free tier (10k серий, 50 GB логов) покрывает ~25 человек. Только OTel Collector локально или прямой OTLP push в Grafana Cloud. Минус — данные снаружи, конфликт с compliance.

9.5 Per-user токены

Если потребуется отзывать доступ point-wise:

  • Сгенерировать N токенов, сохранить в Vault / SSM.
  • Onboarding-скрипт читает токен и кладёт в ~/.claude/settings.json.
  • В endpoint — валидация против таблицы claude_code_telemetry_tokens (token_hash, user_id, revoked_at).

Для команды до ~30 человек обычно overkill. Один shared токен + ротация при компрометации проще.

9.6 Обогащение бизнес-контекстом

В парсере при сохранении в типизованные таблицы добавить JOIN с внутренней таблицей users (если email сматчится) и складывать team, department, cost_center, tenure_days. Полезно для декомпозиции ROI по командам.

9.7 Защита от утечек в Bash args

Если включён OTEL_LOG_TOOL_DETAILS=1, в парсере перед сохранением — regex-маска по типичным паттернам секретов (password|token|secret|api[_-]?key|bearer + значение → [REDACTED]).


10. Roadmap

ЭтапШагиОценка
MVPмиграция БД + ingestion endpoint + парсер в существующий backend, env-блок в admin console0.5 дн.
Отчётыбазовые SQL из раздела 7, Metabase/Superset/Grafana поверх БД0.5–1 дн.
Соглашение по приватностианонс команде, обновление wiki, описание состава данных0.5 дн.
Алертыscheduled-проверки + Slack webhook (cost spike, error rate, parser stuck)0.5 дн.
Расширение (опц.)Grafana дашборды, OTel Collector для масштаба, JOIN с users для team-разреза1–2 дн.

Critical path: ~1 рабочий день от старта до рабочих SQL-отчётов.


11. Чеклист перед раскаткой

  • Миграция БД создана (claude_code_otlp_raw, claude_code_metric_points, claude_code_events)
  • Bearer-токен сгенерирован (openssl rand -hex 32) и сохранён в secret store
  • Endpoint и парсер добавлены в код, покрыты unit-тестами
  • Деплой через CI прошёл, миграция применилась
  • Smoke-тест curl на /otel/v1/metrics отвечает 200
  • Команда уведомлена о телеметрии и составе данных
  • Admin console обновлён с env-блоком (раздел 4.2)
  • Через 1 час: в otlp_raw ≥3 разных user.email
  • Парсер отрабатывает: parsed_at IS NULL стабильно низкий
  • Базовые отчёты из раздела 7 возвращают непустые результаты
  • Алерты настроены (cost spike, error rate, telemetry silence, parser stuck)
  • TTL/cleanup задачи настроены

12. Маскировка промптов до отправки в Anthropic — UserPromptSubmit hook

Отдельная задача от телеметрии. Здесь речь не о том, что Claude Code шлёт нам в OTel, а о том, что юзер шлёт в Anthropic API. Если в чате упоминаются секреты, бренды клиентов, внутренние URL — они уйдут в облако. Hook позволяет перехватить и отредактировать промпт до отправки.

12.1 Механизм

UserPromptSubmit — встроенный hook Claude Code, срабатывает между Enter и API-вызовом.

КаналОписание
stdinJSON: prompt, cwd, session_id, transcript_path
Exit 0промпт идёт дальше как есть
Exit 2промпт блокируется, stderr показывается юзеру как ошибка
JSON на stdout{"decision":"approve","modifiedPrompt":"..."} — заменить текст; {"decision":"block","reason":"..."} — отказать

12.2 Скрипт-санитайзер (пример)

#!/usr/bin/env bash
set -euo pipefail
 
INPUT=$(cat)
PROMPT=$(echo "$INPUT" | jq -r '.prompt')
 
REDACTED=$(echo "$PROMPT" | sed -E \
  -e 's/(password|passwd|secret|token|api[_-]?key|bearer)[[:space:]=:"'"'"']+[^[:space:]"]+/\1=[REDACTED]/gi' \
  -e 's/AKIA[0-9A-Z]{16}/[AWS_KEY_REDACTED]/g' \
  -e 's/AIza[0-9A-Za-z_-]{35}/[GCP_KEY_REDACTED]/g' \
  -e 's/eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}/[JWT_REDACTED]/g' \
  -e 's/(jdbc|postgres|postgresql|mysql|mongodb|redis):\/\/[^[:space:]"'"'"']+/\1:\/\/[CONN_REDACTED]/gi')
 
if [ "$PROMPT" != "$REDACTED" ]; then
  jq -n --arg p "$REDACTED" '{decision:"approve", modifiedPrompt:$p}'
fi
exit 0

Список бренд-имён и доменов клиентов — собрать с legal/security и обновлять централизованно.

12.3 Раскатка через Teams admin console

Hooks поддерживаются в server-managed settings. Расширяем JSON из раздела 4.2:

{
  "env": { "...": "..." },
  "hooks": {
    "UserPromptSubmit": [
      {
        "matcher": "*",
        "hooks": [
          { "type": "command", "command": "/usr/local/bin/redact-prompt" }
        ]
      }
    ]
  }
}

Сам скрипт раздаём через MDM / dotfiles repo / пакетный менеджер в /usr/local/bin/ или ~/.local/bin/. Если admin console не пушит сам бинарник — нужен onboarding-шаг.

12.4 Ограничения hook-а

КейсПоведение
@file mentionsрезолвятся после хука — содержимое файла хуку не видно
Drag-drop изображений / binaryв .prompt только текст, binary хуку недоступен
Системный prompt + tool resultsне видит, только пользовательский ввод
Slash-commandsвидит строку /command args
Followup-сообщениясрабатывает на каждом prompt-submit
Скоростьсинхронный, держать <50ms — иначе тормозит UX

12.5 Дополнительные хуки для защиты

UserPromptSubmit режет утечку через текст. Файлы и команды нужно закрывать отдельно:

HookЧто блокируетПример
PreToolUse на Readчтение чувствительных путей.env, ~/.aws/credentials, ~/.ssh/, /etc/shadow
PreToolUse на Bashкоманды с чувствительными paths/argscat ~/.aws/*, printenv, env, gcloud auth print-access-token
PreToolUse на Edit / Writeзапись секретов в коммитимые файлы.env без .gitignore, hardcoded keys

12.6 Что хук НЕ закрывает

  • Уже отправленные сообщения в текущей сессии — нельзя retro-редактить.
  • Содержимое файлов прочитанных через Read или @mention — хук работает только над текстом промпта. Утечка файлов с секретами решается через PreToolUse:Read.
  • Транскрипт в ~/.claude/projects/<dir>/sessions/*.jsonl хранится сырым на ноуте — секреты в нём останутся локально.

12.7 Связь с телеметрией

  • Если OTEL_LOG_USER_PROMPTS=0 (baseline) — оригинальный промпт не уходит в OTel-БД. Хук защищает только от утечки в Anthropic.
  • Если когда-то включится OTEL_LOG_USER_PROMPTS=1 — Claude Code пишет в телеметрию уже модифицированный промпт (после хука). Санитайзер автоматом защитит и Anthropic, и нашу БД.

12.8 Документация