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_PASSWORD | Basic 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 приложения) и на каждый запрос:
- создаёт request-scoped LatencyProfiler и кладёт его в context;
- замеряет полное время обработки;
- после ответа пишет метрики с endpoint вида
METHOD /route/template(шаблон пути, а не сырой URL с id) и кодом статуса.
| Метрика | Тип | Лейблы | Смысл |
|---|---|---|---|
app_http_requests_total | counter | app, endpoint, code | число входящих HTTP-запросов |
app_http_request_duration_seconds | counter | app, endpoint, code, type | сумма секунд, потраченных на сегменты обработки |
app_http_stats_default | histogram | app | распределение полного времени ответа |
Лейбл type в duration-метрике раскладывает время запроса по сегментам. То, что в PHP часто называли php (время «своего» кода), в Go называется code: это остаток полного времени минус явно учтённые куски. Исходящие HTTP-вызовы к другим сервисам Ensi попадают в тип http_client, если на клиенте подключён middleware метрик (см. ниже). Другие типы можно добавлять в profiler вручную через AddTimeQuant / AddAsyncTimeQuant, если появится необходимость (например, отдельный учёт тяжёлой работы вне HTTP-клиента).
Исходящий HTTP (клиенты)
| Метрика | Тип | Лейблы | Смысл |
|---|---|---|---|
app_http_client_requests_total | counter | app, host | число исходящих запросов |
app_http_client_seconds_total | counter | app, 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: коллекторы создавать и регистрировать не стоит, а запись значений в коде должна спокойно пропускаться.
Практичный порядок такой:
- завести коллектор (
CounterVec,HistogramVec,GaugeVecи т.п.) рядом с остальными инструментами — вapp/common/metrics; - один раз зарегистрировать его в registry при инициализации (после того как
GetInstruments()уже создан); - в бизнес-коде обновлять метрику (
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-коллекторов и запросы к ним приведены в этой статье.