Документация
Как отправлять метрики с сервера или браузеров ваших пользователей.
Введение
Простометрика работает по push-модели: метрики отправляет само приложение. Поэтому не нужно поднимать эндпоинты для скрейпинга, настраивать service discovery и открывать порты наружу.
Вызовы наших библиотек ничего не блокируют: событие попадает в буфер в памяти и в фоне уходит к нам пачкой вместе с остальными. Счётчики складываются ещё до отправки, поэтому вызов в горячем цикле обходится почти так же дёшево, как один-единственный.
Новые метрики не нужно объявлять заранее: как только к нам приходит первое событие, метрика появляется в интерфейсе и на дашборде.
API-ключи
Ключи создаются в настройках проекта. Их два вида:
Серверный ключ — для кода, который выполняется на вашем сервере: Go, Node.js, Python, воркеры, cron. Он секретный: держите его в переменных окружения или в хранилище секретов, как и остальные креды. Не коммитьте его и не отправляйте в браузер.
Клиентский ключ — для кода, который работает у ваших пользователей в браузере. Он умеет только записывать метрики в свой workload, поэтому его можно коммитить и оставлять в исходном коде страницы.
Workload — имя, под которым собираются метрики, обычно название приложения: billing, backend, web. На сервере его передают первым аргументом при инициализации.
Метрики может расставить ИИ-агент
Подойдёт 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, Python и браузерный фронтенд.
В конце агент предложит оставить в проекте ссылку на эту инструкцию и её копию на случай, если интернета под рукой не окажется. Если согласитесь, в следующий раз, когда вы добавите фичу и захотите к ней метрику, объяснять заново ничего не придётся: агент сам сходит за свежей версией, а копией воспользуется только если не сможет — и скажет об этом. Если откажетесь, в репозиторий ничего лишнего не попадёт.
Инструкция открыта и лежит по адресу 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("requests", 1, prostometrics.Label("route", "/orders/:id"))
prostometrics.Value("request.duration_ms", elapsed.Milliseconds())
prostometrics.CountUnique(userID, "users.dau")
prostometrics.CountTop(userID, "article.reads", articleID)
prostometrics.Success("payment", err == nil, prostometrics.Label("provider", "stripe"))
// Сбросить данные при штатном завершении, чтобы не потерять накопленные события
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, countTop, success, label } from "@prostoteam/prostometrics-node";
// Один раз при старте приложения.
const client = init("billing-api", { apiKey: process.env.PROSTOMETRICS_API_KEY });
// Дальше — из любого места в коде.
count("requests", 1, label("route", "/orders/:id"));
value("request.duration_ms", elapsedMs);
countUnique(userId, "users.dau");
countTop(userId, "article.reads", articleId);
success("payment", paymentError === null, label("provider", "stripe"));
// При штатном завершении процесса.
await client.close();
Python
pip install prostometrics
import os
import prostometrics
# Один раз при старте приложения.
prostometrics.init("billing-api", api_key=os.environ["PROSTOMETRICS_API_KEY"])
# Дальше — из любого места в коде. Метки — именованные аргументы.
prostometrics.count("requests", 1, route="/orders/:id")
prostometrics.value("request.duration_ms", elapsed_ms)
prostometrics.count_unique(user_id, "users.dau")
prostometrics.count_top(user_id, "article.reads", article_id)
prostometrics.success("payment", error is None, provider="stripe")
Накопленные события уходят сами, когда процесс завершается. Клиент работает без доработок и в потоковых приложениях (Django, Flask, Celery), и на event loop (FastAPI, aiohttp). Под gunicorn и uWSGI, где процесс разворачивается в несколько воркеров, тоже.
Браузер
npm install @prostoteam/prostometrics-web
import { init, count, value, unique, top, success, match } from "@prostoteam/prostometrics-web";
// Как можно раньше в точке входа приложения.
init({ publicKey: "42_pk_..." });
// Labels — обычный объект, а не список аргументов.
count("web.checkout.step", 1, { step: "payment" });
value("web.api.duration_ms", elapsedMs, {
// match() сохраняет шаблон, а не сам путь: см. раздел о кардинальности.
route: match(location.pathname, ["/orders/:id", "/cart", "/checkout/:step"]),
});
unique("web.users.dau", currentUser.id);
top("web.article.reads", currentUser.id, articleId);
success("web.checkout", order.confirmed, { step: "payment" });
Браузерный клиент весит меньше 5 КБ в gzip и не имеет зависимостей. Его можно подключить и без сборки — тегом script с CDN; актуальный адрес с привязкой к версии есть в описании пакета.
Типы метрик
| Тип | Для чего | Важно |
|---|---|---|
count |
Счётчики: запросы, ошибки, шаги воронки, покупки | Точен при любом объёме вызовов |
value |
Распределения: длительности, размеры, оценки | p50 / p95 / p99 считаются позже, при анализе |
total |
Накопительные счётчики, которые растут монотонно | Только если значение не уменьшается |
unique |
Уникальные значения: DAU, MAU, активные аккаунты | Приблизительный подсчёт, идентификатор не хранится |
top |
Рейтинг: какие статьи, товары или адреса открыло больше всего людей | Список из двух колонок за любой период; считает разных людей, а не открытия |
sparse |
Редкие показания: остаток места, ёмкость, лимиты | Последнее значение переносится вперёд |
success |
Исходы «да или нет»: платёж прошёл, задача выполнена, письмо доставлено | На графике сразу доля успешных в процентах; сколько именно не прошло — отдельным count |
Считайте через count, измеряйте через value. Если нужно и «сколько было запросов», и «насколько медленно» — отправляйте обе метрики. Счётчики точны всегда, а выборка значений прореживается под нагрузкой, поэтому если считать количество по ней, оно окажется заниженным.
Для unique идентификатор не передаётся на сервер в открытом виде — он хешируется и попадает в вероятностную структуру. Тем не менее не отправляйте туда персональные данные: непрозрачный идентификатор аккаунта подходит, email — нет.
top принимает два аргумента: идентификатор человека (по тем же правилам, что unique) и элемент — строку до 256 байт, например идентификатор статьи. Элемент хранится как есть и меткой не считается, поэтому разных элементов может быть сколько угодно. Для каждого элемента сервер держит приблизительный счётчик разных людей за день, месяц и год, а карточка показывает элементы с наибольшим числом людей за выбранный период.
Как называть метрики
Имена пишутся строчными буквами через точку, от общего к частному: db.duration_ms. Единицу измерения выносите в суффикс: интерфейс сам подберёт формат отображения, а вам не придётся гадать, в чём измерялась метрика.
Не добавляйте в начало имени название приложения или сервиса — ни app., ни backend., ни имя репозитория. Кто прислал метрику, уже записано в workload, и интерфейс показывает его рядом с каждой метрикой; префикс лишь повторит это и отодвинет вправо ту часть имени, ради которой вы её открываете. Первый сегмент — то, что измеряется: request, db, cache, checkout.
Не заводите сегмент-рубрику вроде business, technical или stats. Он говорит, куда вы бы сложили метрику, но не что она меряет, и Простометрика его никак не использует. Продуктовые метрики называйте по сущности и событию — order.completed, checkout.started — в том же пространстве имён, что и технические.
| Суффикс | Когда | Пример |
|---|---|---|
_ms |
Миллисекунды — основной выбор для задержек | request.duration_ms |
_s |
Секунды, для длительных операций | job.duration_s |
_kb, _mb, _gb |
Десятичные байты: трафик, размеры payload | response.size_kb |
_kib, _mib, _gib |
Двоичные байты: память, файловая система | memory.used_mib |
_pct |
Проценты в диапазоне 0..100 | cache.hit_pct |
Если подходящей единицы нет — оставьте имя описательным и без суффикса.
Префикс нужен только один — web для браузерных метрик, чтобы серверный и клиентский взгляд на одно и то же не смешались. Задержка в браузере включает сеть и очередь, поэтому с временем обработки на сервере её лучше не смешивать.
Labels и кардинальность
Labels — это разрезы метрики: метод, маршрут, класс статуса, тип операции. Каждое сочетание значений labels хранится отдельным рядом.
Поэтому никогда не кладите в labels то, что приходит от пользователя: id заказа, id пользователя, полный URL, поисковый запрос, текст ошибки. Один такой label превращает одну метрику в миллион рядов.
Правильный разрез — шаблон маршрута вместо самого пути, 4xx вместо конкретного кода, имя операции вместо текста запроса.
// Плохо: один ряд на каждый заказ.
count("requests", 1, label("route", "/orders/8412"));
// Хорошо: один ряд на маршрут.
count("requests", 1, label("route", "/orders/:id"));
В браузере это опаснее: значения приходят прямо из адресной строки и из данных пользователя. Поэтому у браузерного клиента есть match() — он сопоставляет значение со списком шаблонов, который вы задали в самом вызове, и сохраняет шаблон.
value("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://prostometrics.ru/agent.sh | sudo bash
Практики
Короткий список, который экономит больше всего времени.
- Начните с малого. Пять метрик, которые вы смотрите каждый день, полезнее пятидесяти, в которых никто не разбирается. Метрику всегда можно добавить, а вот удалить лишние ряды из истории — уже нет.
- Инициализируйте клиент один раз при старте. Ключ и workload держите в конфигурации приложения.
- Закрывайте клиент при штатном завершении — с ограниченным таймаутом. Метрики не должны быть причиной, по которой процесс не выходит.
- Держите labels стабильными и короткими. У хорошего label десятки значений, и его список известен заранее.
- Считайте ошибки отдельной метрикой. Так проще строить алерты, и не нужно каждый раз вычитать одно из другого.
- Отправляйте исходные значения, а не заранее посчитанные средние. Среднее нельзя разложить обратно, а из исходных значений p95 считается в момент анализа.
- Не кладите в метрики секреты и персональные данные — ни в имена, ни в labels, ни в значения.