Перейти к основному содержимому

Prometheus-метрики

Go-сервисы Ensi отдают метрики в формате Prometheus. Основные имена HTTP-метрик близки к PHP-сервисам, но типы коллекторов, значения лейблов и набор фоновых метрик различаются. При подключении Go-сервиса проверьте запросы и переменные платформенного дашборда по описанию ниже.

Как Prometheus в кластере подхватывает таргеты сервисов (секрет ensi-metrics-scrape-config, проверка Targets) — описано в devops-гайде сбор метрик с приложений. Ниже — что происходит внутри Goravel-сервиса: какие метрики пишутся, как включить scrape-порт и что нужно не забыть при подключении HTTP-клиентов.

Код разделён так:

  • коллекторы и запись значений живут в app/common/metrics
  • HTTP middleware входящих запросов — в app/common/http/middleware
  • учёт исходящих вызовов — в app/common/http/clientmiddleware
  • отдельный scrape-сервер — в app/adapters/prometheus
  • для разрешённых в сервисе artisan-команд scrape поднимает app/adapters/observability; состав команд приведён в разделе «Воркеры и artisan».

Включение и scrape-порт​

Метрики включаются конфигом config/metrics.go и переменными окружения:

ПеременнаяСмысл
PROMETHEUS_ENABLEDвключить сбор и scrape-сервер (metrics.enabled)
PROMETHEUS_HOSTадрес прослушивания (по умолчанию 0.0.0.0)
PROMETHEUS_PORTпорт scrape (по умолчанию 9091; значение меньше 1 отключает запуск сервера)
PROMETHEUS_NAMESPACEпрефикс имён метрик (по умолчанию app)
PROMETHEUS_AUTH_LOGIN / PROMETHEUS_AUTH_PASSWORDBasic Auth для HTTP runner; особенности artisan описаны ниже

Когда metrics.enabled=true, порт положительный и runner зарегистрирован в bootstrap/app.go, вместе с web-приложением стартует app:prometheus. Он поднимает отдельный HTTP-сервер (не путать с основным API) и отдаёт реестр коллекторов на пути /metrics. Именно этот адрес обычно регистрируют в scrape-конфиге Prometheus как таргет сервиса.

В конфиге также можно задать:

  • ignore_routes — список имён роутов (с поддержкой glob вроде path.Match), для которых входящие метрики не пишутся; полезно отфильтровать healthcheck или служебные ручки, если они зарегистрированы как именованные роуты;
  • http_stats_default_buckets — границы histogram для app_http_stats_default (по умолчанию от 5 ms до 10 s).

Имя приложения в лейбле app берётся из app.name в конфиге сервиса — то же значение, по которому удобно фильтровать в Grafana.

Какие метрики пишет сервис​

Ниже приведён набор общего Go-каркаса при PROMETHEUS_NAMESPACE=app. При другом namespace меняется префикс всех перечисленных имён. Лейблы namespace, pod, job могут добавляться конфигурацией scrape; сами HTTP-коллекторы их не задают.

Входящий HTTP​

Middleware HttpMetrics вешается на глобальный стек HTTP (в bootstrap приложения) и на каждый запрос:

  1. создаёт request-scoped LatencyProfiler и кладёт его в context;
  2. замеряет полное время обработки;
  3. после ответа пишет метрики с endpoint вида METHOD /route/template (шаблон пути, а не сырой URL с id) и кодом статуса.
МетрикаТипЛейблыСмысл
app_http_requests_totalcounterapp, endpoint, codeчисло входящих HTTP-запросов
app_http_request_duration_secondscounterapp, endpoint, code, typeсумма секунд, потраченных на сегменты обработки
app_http_stats_defaulthistogramappраспределение полного времени ответа

Лейбл type в duration-метрике раскладывает время запроса по сегментам. То, что в PHP часто называли php (время «своего» кода), в Go называется code: это остаток полного времени минус явно учтённые куски. Исходящие HTTP-вызовы к другим сервисам Ensi попадают в тип http_client, если на клиенте подключён middleware метрик (см. ниже). Другие типы можно добавлять в profiler вручную через AddTimeQuant / AddAsyncTimeQuant, если появится необходимость (например, отдельный учёт тяжёлой работы вне HTTP-клиента).

Исходящий HTTP (клиенты)​

МетрикаТипЛейблыСмысл
app_http_client_requests_totalcounterapp, hostчисло исходящих запросов
app_http_client_seconds_totalcounterapp, hostсумма секунд исходящих запросов

Лейбл host — хост (или host:port) базового URL клиента. Эти метрики заполняются только если в цепочку middleware сгенерированного клиента добавлен clientmiddleware.Metrics(baseURL).

Запросы Grafana и отличия от PHP​

app_http_stats_default в Go — histogram. На /metrics появляются серии app_http_stats_default_bucket с лейблом le, а также _sum и _count. Готовых серий quantile у этого коллектора нет.

Пример p95 за пять минут для выбранного приложения:

histogram_quantile(
0.95,
sum by (app, le) (rate(app_http_stats_default_bucket{app="$app"}[5m]))
)

Среднее полное время ответа:

sum by (app) (rate(app_http_stats_default_sum{app="$app"}[5m]))
/
sum by (app) (rate(app_http_stats_default_count{app="$app"}[5m]))

Для сегмента собственного кода в app_http_request_duration_seconds фильтруйте type="code"; type="php" относится к PHP-сервисам. SQL-время в отдельный сегмент автоматически не выделяется — это зависит от дополнительного инструментирования.

Переменную $app в Grafana можно строить по label_values(app_http_requests_total, app). Если дашборд охватывает несколько контуров, добавьте фильтр по лейблу окружения из scrape-конфигурации. Список приложений, построенный только по app_queue_size, может пропустить Go-сервис: общий Go-реестр не регистрирует эту метрику, app_queue_job_failed_total и app_log_messages_count автоматически.

Для HTTP-запросов, на которых ещё не было наблюдений, соответствующие серии могут отсутствовать. После включения сбора выполните запрос к API и проверьте фактический /metrics.

LatencyProfiler и разбиение времени​

На время обработки входящего запроса middleware создаёт profiler и кладёт его в context. Middleware исходящего клиента, увидев profiler в context, добавляет интервалы типа http_client. В конце запроса profiler считает длительности по типам: сумма перекрывающихся async-интервалов не двойится, а «хвост» полного wall-time записывается как code.

Так на графике «куда ушло время запроса» можно отдельно видеть работу своего кода и ожидание апстримов — в том же духе, что stack по type в стандартном дашборде Ensi.

Если исходящий вызов идёт без request context (например, из фоновой джобы без прокинутого profiler), клиентские счётчики app_http_client_* всё равно обновятся, но в app_http_request_duration_seconds этот кусок не попадёт — входящего HTTP-запроса просто нет.

Подключение метрик к HTTP-клиентам​

Глобальный HttpMetrics на входящих запросах включается в bootstrap один раз. Для клиентов к другим сервисам middleware нужно добавить явно при регистрации в app/providers/clients, рядом с логированием:

baseURL := facades.Config().GetString("ensi_clients.pim.base_url")
request := facades.Http().Client().BaseUrl(baseURL)
middlewares := clientmiddleware.AppendLogging(pim.Binding, []ensihttp.Middleware{
clientmiddleware.Metrics(baseURL),
})
return pim.New(request, middlewares), nil

ensihttp в примере — alias для gitlab.com/greensight/ensi/packages/goravel-ensi-http-client/http; pim — сгенерированный клиент. Фрагмент добавляют в его provider. В сервисе общий clientOptions может также настраивать circuit breaker и проброс initial-event. Сохраняйте эту настройку при добавлении метрик.

baseURL берут из config/ensi_clients/... — тот же URL, с которым создаётся клиент. Без Metrics исходящие вызовы не отразятся ни в client-счётчиках, ни в сегменте http_client у входящего запроса.

Свои метрики​

Стандартный набор покрывает HTTP. Если нужна доменная метрика — размер кэша, число обработанных сообщений Kafka, длительность тяжёлого шага в джобе — её добавляют в тот же Prometheus-реестр, который уже отдаёт scrape-сервер. Отдельный HTTP-эндпоинт или второй registry поднимать не нужно.

Точка расширения — metrics.GetInstruments().Registry(). Это тот же *prometheus.Registry, куда при старте зарегистрированы app_http_*. Когда PROMETHEUS_ENABLED выключен, Registry() вернёт nil: коллекторы создавать и регистрировать не стоит, а запись значений в коде должна спокойно пропускаться.

Практичный порядок такой:

  1. завести коллектор (CounterVec, HistogramVec, GaugeVec и т.п.) рядом с остальными инструментами — в app/common/metrics;
  2. один раз зарегистрировать его в registry при инициализации (после того как GetInstruments() уже создан);
  3. в бизнес-коде обновлять метрику (Inc, Add, Observe, Set).

Пример счётчика обработанных событий:

var syncEventsTotal *prometheus.CounterVec

func RegisterDomainMetrics() {
reg := metrics.GetInstruments().Registry()
if reg == nil {
return
}

syncEventsTotal = prometheus.NewCounterVec(prometheus.CounterOpts{
Name: "app_sync_events_total",
Help: "Number of processed sync events",
}, []string{"app", "entity", "result"})
reg.MustRegister(syncEventsTotal)
}

func RecordSyncEvent(entity, result string) {
if syncEventsTotal == nil {
return
}
app := facades.Config().GetString("app.name", "Goravel")
syncEventsTotal.WithLabelValues(app, entity, result).Inc()
}

RegisterDomainMetrics вызывают при старте приложения (provider / bootstrap), до того как пойдут запросы или воркеры начнут работу. Иначе первые события могут пройти до регистрации коллектора.

Несколько правил, чтобы метрика оставалась полезной в Grafana и не раздувала кардинальность:

  • имя лучше начинать с app_, в духе платформенных метрик;
  • в лейблы кладут низкокардинальные измерения (entity, result, queue), а не id сущностей, UUID и сырые URL;
  • лейбл app берут из app.name, как у стандартных HTTP-метрик — так удобнее фильтровать в общих дашбордах;
  • для «куда ушло время внутри HTTP-запроса» чаще достаточно LatencyProfiler.AddTimeQuant / AddAsyncTimeQuant (новый type в app_http_request_duration_seconds), а отдельный histogram/counter заводят, когда нужна метрика вне разбиения одного входящего запроса или с другой семантикой.

После регистрации значение появляется на том же /metrics, что и HTTP-счётчики. Локально достаточно включить scrape и сделать curl на порт PROMETHEUS_PORT.

Воркеры и artisan​

Запуск scrape зависит от регистрации в bootstrap/app.go, вызова StartArtisanSidecars и списка metricsArtisanCommands в app/adapters/observability/sidecars.go.

РеализацияArtisan-команды, для которых включён scrape
Go-шаблонqueue:work, kafka:consume, schedule:run

Наличие очереди или scheduler в сервисе само по себе не добавляет scrape для их команд. Для новой долгой команды проверьте и при необходимости расширьте список, затем убедитесь, что сервер действительно запускается.

HTTP runner создаёт сервер через NewServerWithBasicAuth и читает metrics.basic_auth. Текущая artisan-обвязка вызывает NewServer без Basic Auth. Поэтому одинаковые env-переменные не гарантируют одинаковую аутентификацию web- и consumer-таргетов; конфигурацию scrape выбирают по фактическому процессу.

В Kubernetes у соответствующего деплоя должен быть доступен PROMETHEUS_PORT, а target — указывать на процесс с работающим scrape-сервером. Если несколько процессов работают в одном сетевом пространстве, назначьте им разные порты. Настройка получения метрик описана в DevOps-гайде; типы Go-коллекторов и запросы к ним приведены в этой статье.