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

Как отправлять метрики с сервера или браузеров ваших пользователей.

Введение

Простометрика работает по push-модели. Т.е. приложение само отправляет метрики — не нужно поднимать специльные ендпоинты для скрепинга, настраивать service discovery и открывать порты наружу.

Наши клиенсткие библиотеки устроены так, что вызов любых функций ничего не блокирует: событие попадает в буфер в памяти, а фоновая отправка отправляет его пачкой на наши сервера. Счётчики агреггируются, поэтому вызов внутри очень горячего цикла стоит примерно столько же, сколько бы стоил один вызов.

Новые метрики не нужно объявлять или регистрировать заранее: как только приходит первое событие к нам, метрика сразу появляется в интерфейсе и попадает на дашборд.

API Ключи

Ключи создаются в настройках проекта. Их два вида:

Серверный ключ — для кода, который выполняется на вашем сервере: Go, Node.js, воркеры, cron. Такие ключи секретны: держите их в переменных окружения или в системе хранения секретов, как и любые другие креды. Никогда не коммитьте его и не отправляйте в браузер к пользователям.

Клиентский ключ — для кода, который работает у ваших пользователей в браузере. Он публичный: его можно коммитить и отдавать в исходном коде страницы.

Workload — это стабильное имя сервиса, которым группируются метрики: billing-api, worker/payments, web. Например, это может быть название приложения. На сервере вы передаёте его первым аргументом при инициализации.

Инструментализация с LLM

Расставлять метрики руками не обязательно — это может сделать ваш ИИ агент: Claude Code, Cursor, Codex или любой другой. Для них у нас лежит отдельная инструкция:

Read https://prostometrics.ru/llms.txt and instrument this project with Prostometrics metrics, following that guide.

Первым делом агент спросит, есть ли у вас пожелания: биллинг, фронтенд, фоновые задачи, что-то одно конкретное — или наоборот, чего касаться не нужно. Можно ничего не выбирать и просто ответить go. Дальше он сам разберётся, что за код перед ним, и предложит план метрик по трём уровням — от самого нужного до опционального. Ключ и workload он попросит в самом конце, когда вы утвердите план: сначала вы видите, что получите, и только потом даёте доступы. Поддерживаются Go, Node.js и браузерный фронтенд.

В конце агент предложит оставить в проекте ссылку на эту инструкцию и её копию на случай, если интернета под рукой не окажется. Если согласитесь, в следующий раз, когда вы добавите фичу и захотите к ней метрику, объяснять заново ничего не придётся: агент сам сходит за свежей версией, а копией воспользуется только если не сможет — и скажет об этом. Если откажетесь, в репозиторий ничего лишнего не попадёт.

Инструкция открыта и лежит по адресу prostometrics.ru/llms.txt — если ваш инструмент не ходит в интернет, откройте её и вставьте текст целиком.

Начнём

Go

go get github.com/prostoteam/prostometrics-go@latest
import "github.com/prostoteam/prostometrics-go"

// Один раз при старте приложения.
client, err := prostometrics.Init("billing-api", prostometrics.Config{
    APIKey: os.Getenv("PROSTOMETRICS_API_KEY"),
})
if err != nil {
    log.Fatal(err)
}

// Дальше — из любого места в коде.
prostometrics.Count("app.requests", 1, prostometrics.Label("route", "/orders/:id"))
prostometrics.Value("app.request.duration_ms", elapsed.Milliseconds())
prostometrics.CountUnique(userID, "app.users.dau")

// Сбросить данные при штатном завершении, чтобы не потерять накопленные события
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
_ = client.Close(ctx)

Node.js

npm install @prostoteam/prostometrics-node
import { init, count, value, countUnique, label } from "@prostoteam/prostometrics-node";

// Один раз при старте приложения.
const client = init("billing-api", { apiKey: process.env.PROSTOMETRICS_API_KEY });

// Дальше — из любого места в коде.
count("app.requests", 1, label("route", "/orders/:id"));
value("app.request.duration_ms", elapsedMs);
countUnique(userId, "app.users.dau");

// При штатном завершении процесса.
await client.close();

Браузер

npm install @prostoteam/prostometrics-web
import { init, count, value, unique, match } from "@prostoteam/prostometrics-web";

// Как можно раньше в точке входа приложения.
init({ publicKey: "42_pk_..." });

// Labels — обычный объект, а не список аргументов.
count("app.web.checkout.step", 1, { step: "payment" });
value("app.web.api.duration_ms", elapsedMs, {
    // match() сохраняет шаблон, а не сам путь: см. раздел о кардинальности.
    route: match(location.pathname, ["/orders/:id", "/cart", "/checkout/:step"]),
});
unique("app.web.users.dau", currentUser.id);

Браузерный клиент весит меньше 5 КБ в gzip и не имеет зависимостей. Его можно подключить и без сборки — тегом script с CDN; актуальный адрес с привязкой к версии есть в описании пакета.

Типы метрик

Тип Для чего Важно
count Счётчики: запросы, ошибки, шаги воронки, покупки Точен при любом объёме вызовов
value Распределения: длительности, размеры, оценки p50 / p95 / p99 считаются позже, при анализе
total Накопительные счётчики, которые растут монотонно Только если значение не уменьшается
unique Уникальные значения: DAU, MAU, активные аккаунты Приблизительный подсчёт, идентификатор не хранится
sparse Редкие показания: остаток места, ёмкость, лимиты Последнее значение переносится вперёд

Считайте через count, измеряйте через value. Если нужно и «сколько было запросов», и «насколько медленно» — отправляйте обе метрики. Счётчики точны всегда, а выборка значений прореживается под нагрузкой, поэтому количество, посчитанное по ней, окажется заниженным.

Для unique идентификатор не передаётся на сервер в открытом виде — он хешируется и попадает в вероятностную структуру. Тем не менее не отправляйте туда персональные данные: непрозрачный идентификатор аккаунта подходит, email — нет.

Именование метрик

Имена — строчные, разделённые точками, от общего к частному: app.db.duration_ms. Единицу измерения выносите в суффикс: интерфейс сам подберёт формат отображения, а вам не придётся гадать, в чём измерялась метрика.

Суффикс Когда Пример
_ms Миллисекунды — основной выбор для задержек app.request.duration_ms
_s Секунды, для длительных операций app.job.duration_s
_kb, _mb, _gb Десятичные байты: трафик, размеры payload app.response.size_kb
_kib, _mib, _gib Двоичные байты: память, файловая система app.memory.used_mib
_pct Проценты в диапазоне 0..100 app.cache.hit_pct

Если подходящей единицы нет — оставьте имя описательным и без суффикса.

Для браузерных метрик используйте префикс app.web: тогда серверный и клиентский взгляд на одно и то же не смешаются. Задержка запроса, измеренная в браузере, включает сеть и очередь — это другая величина, чем время обработки на сервере, и хранить их лучше раздельно.

Labels и кардинальность

Labels — это разрезы метрики: метод, маршрут, класс статуса, тип операции. Каждое уникальное сочетание значений labels становится отдельным хранимым рядом. Отсюда единственное правило, которое действительно важно соблюдать.

Никогда не кладите в labels то, что приходит от пользователя: id заказа, id пользователя, полный URL, поисковый запрос, текст ошибки. Один такой label превращает одну метрику в миллион рядов.

Правильный разрез — шаблон маршрута, а не сам путь; 4xx, а не конкретный код; имя операции, а не текст запроса.

// Плохо: один ряд на каждый заказ.
count("app.requests", 1, label("route", "/orders/8412"));

// Хорошо: один ряд на маршрут.
count("app.requests", 1, label("route", "/orders/:id"));

В браузере это опаснее: значения приходят прямо из адресной строки и из данных пользователя. Поэтому у браузерного клиента есть match() — он сопоставляет значение со списком шаблонов, который вы задали в самом вызове, и сохраняет шаблон.

value("app.web.api.duration_ms", elapsedMs, {
    route: match(location.pathname, ["/orders/:id", "/cart", "/checkout/:step"]),
});

// "/orders/8412"     -> "/orders/:id"
// "/cart"            -> "/cart"
// "/unknown/page"    -> "other"

Список шаблонов удобно брать из конфигурации роутера вашего приложения. Тогда число рядов ограничено размером вашего кода, а не трафиком: :name совпадает с одним сегментом пути, * — с остатком, всё несовпавшее становится other.

Дополнительно браузерный клиент подстраховывает сам: если у одной метрики один label набирает 50 разных значений, дальнейшие схлопываются в other с предупреждением в консоли. А с debug: true он предупреждает, когда значение похоже на идентификатор — включайте этот режим в дев-сборке.

Браузер: что иначе

Браузерный клиент решает те же задачи, но условия у него другие: код уже уехал к пользователю, сеть ненадёжна, а вкладку могут закрыть в любой момент.

  • Отправка раз в 10 секунд, и ещё раз — когда страница скрывается или закрывается. Более частая отправка ничего не покажет раньше: интервал хранения всё равно шире.
  • Время проставляется на приёме, а не в браузере. Часам на устройстве пользователя доверять нельзя.
  • Ничего не повторяется и не сохраняется. Неудачная отправка теряет одно окно — вместо того чтобы позже прислать устаревшие измерения не по порядку.
  • Сервис может остановить или притормозить клиент. Отозванный ключ останавливает отправку насовсем, исчерпанный баланс — до пополнения. Обо всём этом клиент пишет в консоль через console.error.
  • Часть аудитории не доедет. Блокировщики рекламы режут запросы к сторонним доменам. Если это важно — проксируйте отправку через свой домен: обычно это несколько строк в конфиге nginx.

Метрики хоста

Состояние машины собирать вручную не нужно. Агент сам присылает загрузку CPU, память, аптайм, место на дисках и сетевой трафик, а также пытается обнаружить, что запущено рядом: контейнеры, базы данных, веб-серверы — и снимает метрики и с них тоже.

curl -fsSL https://raw.githubusercontent.com/prostoteam/prostometrics-agent/main/scripts/install_agent.sh | sudo bash

Практики

Короткий список, который экономит больше всего времени.

  1. Начните с малого. Пять метрик, которые вы смотрите каждый день, полезнее пятидесяти, в которых никто не разбирается. Метрику всегда можно добавить, а вот удалить лишние ряды из истории — уже нет.
  2. Инициализируйте клиент один раз при старте. Ключ и workload берите из конфигурации приложения, а не из кода.
  3. Закрывайте клиент при штатном завершении — с ограниченным таймаутом. Метрики не должны быть причиной, по которой процесс не выходит.
  4. Держите labels стабильными и короткими. Хороший label имеет десятки значений, а не тысячи, и его список известен заранее.
  5. Считайте ошибки отдельной метрикой, а не label на успешных запросах: так проще строить алерты и не нужно каждый раз вычитать одно из другого.
  6. Отправляйте исходные значения, а не заранее посчитанные средние. Среднее нельзя разложить обратно, а из исходных значений p95 считается в момент анализа.
  7. Не кладите в метрики секреты и персональные данные — ни в имена, ни в labels, ни в значения.