1. Введение
Давайте представим, что вы администратор сайта, и каждый день к вам приходит тысяча пользователей. В логах вы видите, что всё работает, ошибок почти нет (разве что кто-то иногда забывает пароль или ошибается с капчей). «Всё отлично!» — думаете вы.
И вдруг кто-то пишет в поддержку: сайт ужасно тормозит, страницы открываются по 10 секунд. Вы лезете в логи — ошибок нет! Ура? Нет, потому что логи рассказывают что случилось (или не случилось), но не говорят, насколько быстро или медленно это произошло, сколько ресурсов для этого понадобилось, и как менялось это поведение с ростом нагрузки.
Вот тут на сцену и выходят метрики — измеримые характеристики работы приложения. Это не только число ошибок, но и среднее время отклика, объём потребляемой памяти, количество запросов в секунду и другие показатели, по которым можно судить о здоровье системы.
Сравнение:
Логи — это "что случилось".
Метрики — это "насколько хорошо/плохо работает система".
Трассировка — это "почему система так работает (в деталях)".
Какие бывают метрики, и что стоит собирать?
Основные виды метрик:
| Тип метрики | Пример | Для чего нужна |
|---|---|---|
| Счетчики (Counters) | Число запросов, ошибок, сбоев | Тренды, алерты, нагрузка |
| Гистограммы | Время ответа, размер пакета | Распределение значений, перцентили |
| Гейджи (Gauge) | Использование памяти, CPU | Текущее состояние ресурса |
| Сумматоры (Sum) | Общий объём данных, байтов | Общий объём операций за период |
Примеры:
- Среднее и 95-й перцентиль времени ответа на GET-запрос.
- Количество пользователей онлайн прямо сейчас.
- Использование памяти (Private Bytes, Working Set).
- Частота возникновения ошибок типа 500/503.
- Запросы к БД в минуту.
Эти показатели позволяют не только находить проблемы, но и предотвращать их — ведь повышенная нагрузка на сервер или «ползущий» рост времени ответа могут сигнализировать о будущих сбоях.
2. Как устроен сбор метрик в .NET и экосистеме OpenTelemetry
Общая архитектура
В современном .NET (начиная с .NET 6, особенно в .NET 8/9) существует стандартная система сбора метрик, основанная на OpenTelemetry.
Вот как это работает:
- Код приложения вызывает методы для увеличения счётчиков, регистрации гейджей, записи гистограмм.
- SDK OpenTelemetry Metrics собирает эти метрики (в памяти) и периодически отправляет их.
- Экпортёр метрик передаёт их в выбранную систему мониторинга (Prometheus, Application Insights, Grafana Cloud, Datadog и т.д.).
- Бэкенд мониторинга агрегирует, хранит, визуализирует, строит алерты и дашборды.
Схематичная блок-схема:
[Ваше приложение]
⬇
[Сбор метрик (OpenTelemetry SDK)]
⬇
[Экспортёр метрик (Prometheus, AI, Datadog, ...)]
⬇
[Система мониторинга/дашборды/алерты]
3. Практика: Основы работы с метриками в C#
Простые внутренние метрики: System.Diagnostics.Metrics
.NET предоставляет встроенный механизм для метрик — System.Diagnostics.Metrics.
Основные игроки: Meter, Counter<T>, Histogram<T>, ObservableGauge<T>.
Пример: счётчик посещений страницы
// Создаём Meter (обычно один на всё приложение)
using System.Diagnostics.Metrics;
static Meter meter = new Meter("MyCompany.MyApp", "1.0");
// Регистрируем счетчик
static Counter<long> homePageVisits = meter.CreateCounter<long>("HomePageVisits");
// Где-то в коде контроллера или сервиса...
public void HomePageRequested()
{
homePageVisits.Add(1);
// Дальше остальной код обработки страницы
}
Комментарии:
- Meter — это «фабрика» для метрик, с уникальным именем (пространство имён приложения/компании).
- CreateCounter<long> — создаёт счётчик; инкремент через Add(1).
Пример: измерение времени ответа
static Histogram<double> pageLoadTime = meter.CreateHistogram<double>("PageLoadTimeMs");
// В обработчике запроса:
public void OnRequest()
{
var stopwatch = System.Diagnostics.Stopwatch.StartNew();
// ...здесь сам запрос...
stopwatch.Stop();
pageLoadTime.Record(stopwatch.Elapsed.TotalMilliseconds);
}
Собираем гейджи для динамических значений
Гейдж — показатель, который меняется во времени: количество подключённых пользователей, текущий объём памяти и т.д.
static ObservableGauge<int> onlineUserGauge = meter.CreateObservableGauge(
"OnlineUsers",
() => GetOnlineUserCount());
// Где GetOnlineUserCount - метод, возвращающий актуальное значение
static int GetOnlineUserCount()
{
// Здесь должна быть ваша реальная логика!
return ActiveUserList.Count;
}
В реальном мире всё это работает асинхронно: приложение сообщает значения метрик, а экспортёр забирает их и отдаёт наружу (например, Prometheus скрэпит endpoint "/metrics").
Добавление метрик в современное ASP.NET Core-приложение
Для ASP.NET Core многое доступно из коробки. Достаточно добавить пакет OpenTelemetry.Instrumentation.AspNetCore, и появятся метрики HTTP-запросов, времени ответа, числа ошибок и т.д.
Пример настройки в Program.cs:
using OpenTelemetry.Metrics;
using OpenTelemetry.Resources;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddOpenTelemetry()
.WithMetrics(metrics =>
{
metrics
.SetResourceBuilder(ResourceBuilder.CreateDefault().AddService("MyApp"))
.AddAspNetCoreInstrumentation() // метрики HTTP
.AddRuntimeInstrumentation() // метрики среды выполнения .NET CLR
.AddProcessInstrumentation() // CPU/память процесса
.AddMeter("MyCompany.MyApp") // ваши метрики
.AddPrometheusExporter(); // экспорт в Prometheus
});
var app = builder.Build();
app.MapGet("/", () => "Hello World!");
app.Run();
Теперь ваше приложение будет выдавать метрики по адресу /metrics, которые можно скрэпить через Prometheus или другие системы.
4. Практические примеры использования метрик
Мониторинг производительности в реальных проектах
Подключаем метрики, чтобы узнать:
- Какой средний и пиковый RPS (requests per second) выдерживает API?
- Где «узкие места»: один эндпоинт работает 300 мс, другой — 2000 мс?
- Сколько времени тратится на вызовы БД? (добавляем собственные гистограммы)
Пример: контролируем время ответа к базе данных
static Histogram<double> dbQueryDuration = meter.CreateHistogram<double>("DbQueryDurationMs");
public async Task<List<Product>> GetProductsAsync()
{
var sw = Stopwatch.StartNew();
var result = await _db.Products.ToListAsync();
sw.Stop();
dbQueryDuration.Record(sw.Elapsed.TotalMilliseconds);
return result;
}
Пример: подсчитываем число ошибок
static Counter<long> apiErrors = meter.CreateCounter<long>("ApiErrors");
public IActionResult SomeEndpoint()
{
try
{
// какое-то действие
return Ok();
}
catch (Exception)
{
apiErrors.Add(1);
throw;
}
}
Работа с лейблами (тэгами) для метрик
Важно группировать данные по полезным признакам: эндпоинт, тип ошибки, тип пользователя и т.д.
homePageVisits.Add(
1,
KeyValuePair.Create<string, object>("UserType", "Admin"));
Или для гистограммы:
dbQueryDuration.Record(
sw.Elapsed.TotalMilliseconds,
KeyValuePair.Create<string, object>("QueryType", "GetProducts"));
Благодаря тегам в Grafana можно строить графики не только по всему приложению, но и по конкретным сегментам.
5. Интеграция с Prometheus, Application Insights, Datadog, Grafana
Экспортёры и интеграция
- Prometheus — популярный open-source мониторинг, де-факто стандарт для облаков и Kubernetes.
- Application Insights — облачная интеграция для Azure.
- Datadog, Grafana Cloud — для профессиональных инфраструктур.
Все эти системы могут собирать метрики из .NET через экспортёры OpenTelemetry. Документация по экспортёрам OTel
Prometheus (шаги):
- Добавить NuGet пакет: OpenTelemetry.Exporter.Prometheus.
- Добавить .AddPrometheusExporter() в регистрацию метрик.
- В Grafana настроить datasource на Prometheus и построить дашборды.
Полезные ссылки:
6. Особенности, подводные камни и типичные ошибки
Один из частых промахов — чрезмерная детализация тегов. Если присваивать тегам слишком много уникальных значений (например, ID пользователя/заказа), число временных рядов взлетит — это приведёт к перегрузке хранилища метрик и росту затрат (так называемая cardinality explosion). Держите теги «грубозернистыми».
Разработчики иногда игнорируют System.Diagnostics.Metrics и готовые инструменты, делая «велосипед» через логи и таймеры. В итоге мониторинг хуже интегрируется и сложнее поддерживается. Используйте стандартные инструменты и автоматическую инструментализацию.
Ещё один промах — метрики собираются, но не экспортируются. Настройка экспортёра обязательна: добавьте, например, .AddPrometheusExporter() и убедитесь, что эндпоинт /metrics доступен для скрэпинга.
И, наконец, путаница между типами метрик: среднее время отклика считают через Counter, хотя нужно использовать Histogram — иначе вы не увидите пики и распределение. Счётчики — для количества; гистограммы — для времени/размеров/распределений; гейджи — для текущих состояний.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ