Настройка мониторинга HTTP-запросов (Response Time, Error Rate) в мобильном приложении
Response Time и Error Rate — два основных сигнала здоровья API. Если среднее время ответа /api/feed выросло с 200 мс до 1800 мс — пользователь уже это чувствует, но в App Store отзывы появятся через день. С мониторингом — вы знаете об этом через 5 минут.
Что измеряем и где храним
Два уровня сбора метрик:
Клиентский (in-app): измеряем время ответа от устройства пользователя — включает задержку сети. Отражает реальный опыт, но зашумлен: плохая сеть у одного пользователя не означает проблему сервера.
Серверный (APM): инструментируем сервер, измеряем только серверное время. Не видит сетевую задержку, но точно показывает состояние бэкенда.
Правильно: оба уровня. Клиентский — для понимания UX, серверный — для диагностики.
Перехватчик для Axios в React Native
import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios';
type RequestMetric = {
endpoint: string;
method: string;
statusCode: number;
durationMs: number;
timestamp: number;
error?: string;
};
const metricsBuffer: RequestMetric[] = [];
const FLUSH_INTERVAL_MS = 30_000;
const FLUSH_BATCH_SIZE = 50;
function createMonitoredAxios(): AxiosInstance {
const instance = axios.create({ baseURL: API_BASE_URL });
instance.interceptors.request.use((config: AxiosRequestConfig) => {
(config as any).metadata = { startTime: Date.now() };
return config;
});
instance.interceptors.response.use(
(response: AxiosResponse) => {
recordMetric(response.config, response.status, null);
return response;
},
(error) => {
const status = error.response?.status ?? 0;
recordMetric(error.config, status, error.message);
return Promise.reject(error);
}
);
return instance;
}
function recordMetric(config: any, status: number, error: string | null) {
const durationMs = Date.now() - (config?.metadata?.startTime ?? Date.now());
const url = config?.url ?? 'unknown';
const endpoint = new URL(url, API_BASE_URL).pathname; // нормализуем без query params
metricsBuffer.push({
endpoint,
method: (config?.method ?? 'GET').toUpperCase(),
statusCode: status,
durationMs,
timestamp: Date.now(),
error: error ?? undefined,
});
if (metricsBuffer.length >= FLUSH_BATCH_SIZE) flushMetrics();
}
URL нормализуем в pathname — не хотим тысячи уникальных метрик /api/users/123, /api/users/456. Нужен паттерн /api/users/:id.
Агрегация на клиенте: P50/P95/P99
Среднее (mean) время ответа обманчиво: 90% запросов за 100 мс и 10% за 5000 мс дают среднее 590 мс — не отражает реальной картины. Перцентили точнее:
function calculatePercentiles(durations: number[]): { p50: number; p95: number; p99: number } {
const sorted = [...durations].sort((a, b) => a - b);
const p = (percentile: number) => sorted[Math.floor(sorted.length * percentile / 100)];
return { p50: p(50), p95: p(95), p99: p(99) };
}
P99 — время ответа для 99% запросов. Если P99 растёт при стабильном P50 — проблема с медленными запросами у небольшой части пользователей (конкретный endpoint, конкретная ОС, конкретный регион).
Отправка метрик: батчинг и приоритизация
async function flushMetrics() {
if (metricsBuffer.length === 0) return;
const batch = metricsBuffer.splice(0, FLUSH_BATCH_SIZE);
try {
await fetch(`${METRICS_ENDPOINT}/ingest`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ metrics: batch, appVersion: APP_VERSION, platform: Platform.OS }),
});
} catch {
// При ошибке отправки — кладём обратно в буфер, но не более MAX_BUFFER_SIZE
metricsBuffer.unshift(...batch.slice(0, MAX_BUFFER_SIZE - metricsBuffer.length));
}
}
Метрики отправляем через специальный non-critical fetch — ошибки при отправке не должны влиять на UX. Буфер ограничен — при длительном offline не накапливаем гигабайты.
Готовые решения: Firebase Performance Monitoring
@react-native-firebase/perf делает большую часть автоматически: перехватывает fetch/XHR, измеряет время, отправляет в Firebase. В консоли — дашборд с перцентилями по endpoint.
import perf from '@react-native-firebase/perf';
// Кастомный trace для критичной операции
const trace = await perf().startTrace('checkout_flow');
trace.putAttribute('userId', userId);
// ... операция ...
await trace.stop();
Для большинства приложений Firebase Performance — правильный выбор. Для enterprise с требованиями к self-hosted — Datadog RUM Mobile или кастомная отправка в InfluxDB/Prometheus.
Алерты по Response Time
Порог алерта: P95 > 2× baseline за 5-минутное окно. Например, если baseline P95 = 400 мс и P95 вырос до 900 мс — алерт в Slack. Error rate > 5% за 10 минут — PagerDuty.
Оценка
Firebase Performance Monitoring с кастомными трейсами и базовыми алертами: 1 неделя. Кастомная система метрик с батчингом, перцентилями и Datadog/Grafana-дашбордом: 2–4 недели.







