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

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

Введение

Простометрика работает по 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

Практики

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

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