Интеграция с системами мониторинга

TanStack Query активно взаимодействует с удалёнными API, фоновыми обновлениями, повторными запросами, кешированием и механизмами синхронизации состояния. При увеличении нагрузки или усложнении архитектуры появляется необходимость отслеживать:

  • ошибки сетевых запросов;
  • деградацию производительности;
  • аномально долгие ответы API;
  • частоту повторных запросов;
  • некорректную инвалидацию кеша;
  • бесконечные циклы refetch;
  • утечки памяти;
  • состояние фоновых операций;
  • поведение optimistic updates;
  • проблемы офлайн-синхронизации.

Интеграция с системами мониторинга позволяет превращать внутренние события TanStack Query в телеметрию приложения.


Архитектура наблюдаемости

В типичном приложении мониторинг строится из нескольких уровней:

Уровень Назначение
Логирование Запись событий и ошибок
Error tracking Централизованный сбор исключений
Performance monitoring Метрики скорости
Tracing Отслеживание цепочек запросов
Analytics Поведенческий анализ
Metrics Агрегация технических показателей

TanStack Query предоставляет несколько точек интеграции:

  • queryFn
  • mutationFn
  • onError
  • onSuccess
  • onSettled
  • QueryCache
  • MutationCache
  • custom logger
  • QueryClient listeners
  • Devtools API

Глобальный мониторинг через QueryCache

QueryCache позволяет перехватывать события всех запросов приложения.

import {
  QueryClient,
  QueryCache
} from '@tanstack/react-query'

const queryClient = new QueryClient({
  queryCache: new QueryCache({
    onError: (error, query) => {
      console.error('QUERY ERROR', {
        key: query.queryKey,
        error
      })
    },

    onSuccess: (data, query) => {
      console.log('QUERY SUCCESS', {
        key: query.queryKey
      })
    },

    onSettled: (data, error, query) => {
      console.log('QUERY FINISHED', {
        key: query.queryKey
      })
    }
  })
})

Подобный уровень перехвата особенно полезен для:

  • централизованного логирования;
  • отправки данных в Sentry;
  • построения performance-метрик;
  • аудита API;
  • анализа ошибок.

Интеграция с Sentry

Базовая интеграция ошибок

Наиболее распространённый вариант — отправка ошибок запросов в Sentry.

import * as Sentry from '@sentry/react'

const queryClient = new QueryClient({
  queryCache: new QueryCache({
    onError: (error, query) => {
      Sentry.captureException(error, {
        tags: {
          queryKey: JSON.stringify(query.queryKey)
        }
      })
    }
  })
})

Добавление контекста запроса

Минимальная отправка исключения редко бывает достаточной. Намного полезнее передавать:

  • queryKey;
  • URL;
  • параметры;
  • retry count;
  • stale status;
  • данные пользователя;
  • время выполнения.
onError: (error, query) => {
  Sentry.captureException(error, {
    tags: {
      type: 'tanstack-query',
      queryKey: JSON.stringify(query.queryKey)
    },

    extra: {
      state: query.state,
      meta: query.meta
    }
  })
}

Мониторинг мутаций

Ошибки мутаций обычно критичнее ошибок чтения.

import {
  MutationCache,
  QueryClient
} from '@tanstack/react-query'

const queryClient = new QueryClient({
  mutationCache: new MutationCache({
    onError: (error, variables, context, mutation) => {
      Sentry.captureException(error, {
        tags: {
          mutationKey: JSON.stringify(
            mutation.options.mutationKey
          )
        },

        extra: {
          variables
        }
      })
    }
  })
})

Особенно важно отслеживать:

  • платежи;
  • операции сохранения;
  • удаление данных;
  • обновление профиля;
  • административные действия.

Интеграция с Datadog

Datadog используется для инфраструктурного мониторинга и performance-аналитики.

Отправка пользовательских метрик

function trackQueryDuration(queryKey, duration) {
  window.DD_RUM?.addAction('query_duration', {
    queryKey,
    duration
  })
}

Использование:

const fetchUsers = async () => {
  const started = performance.now()

  const response = await fetch('/api/users')

  const finished = performance.now()

  trackQueryDuration(
    'users',
    finished - started
  )

  return response.json()
}

Мониторинг retry-механизмов

Частые повторные запросы часто указывают на нестабильность API.

retry: (failureCount, error) => {
  window.DD_RUM?.addError(error, {
    retryCount: failureCount
  })

  return failureCount < 3
}

Интеграция с New Relic

New Relic позволяет анализировать производительность frontend-приложений.

Измерение времени запросов

const fetchPosts = async () => {
  const start = performance.now()

  const response = await fetch('/api/posts')

  const data = await response.json()

  const duration = performance.now() - start

  window.newrelic?.addPageAction(
    'tanstack_query_request',
    {
      endpoint: '/api/posts',
      duration
    }
  )

  return data
}

Использование OpenTelemetry

OpenTelemetry становится стандартом observability-инфраструктуры.

Создание trace для query

import { trace } from '@opentelemetry/api'

const tracer = trace.getTracer('frontend')

async function fetchTodos() {
  return tracer.startActiveSpan(
    'fetch-todos',
    async (span) => {
      try {
        const response = await fetch('/api/todos')

        const data = await response.json()

        span.setAttribute(
          'http.status_code',
          response.status
        )

        return data
      } catch (error) {
        span.recordException(error)

        throw error
      } finally {
        span.end()
      }
    }
  )
}

Связка frontend и backend tracing

При использовании OpenTelemetry можно формировать единый distributed trace:

Browser
  ↓
Frontend Query
  ↓
Gateway API
  ↓
Microservice
  ↓
Database

Это позволяет находить:

  • медленные микросервисы;
  • узкие места;
  • проблемные SQL-запросы;
  • сетевые задержки;
  • каскадные ошибки.

Custom Logger

TanStack Query поддерживает собственный логгер.

import { setLogger } from '@tanstack/react-query'

setLogger({
  log: (...args) => {
    console.log('[RQ LOG]', ...args)
  },

  warn: (...args) => {
    console.warn('[RQ WARN]', ...args)
  },

  error: (...args) => {
    console.error('[RQ ERROR]', ...args)
  }
})

Интеграция логгера с внешними сервисами

setLogger({
  log: () => {},

  warn: (message) => {
    monitoringService.warn(message)
  },

  error: (error) => {
    monitoringService.error(error)
  }
})

Метрики производительности

Измерение latency

Одним из главных показателей считается время выполнения запроса.

async function monitoredFetch(url) {
  const started = performance.now()

  const response = await fetch(url)

  const duration = performance.now() - started

  metrics.histogram(
    'query.duration',
    duration
  )

  return response.json()
}

Измерение cache hit ratio

Эффективность кеширования напрямую влияет на производительность.

const query = queryClient.getQueryData(['users'])

if (query) {
  metrics.increment('cache.hit')
} else {
  metrics.increment('cache.miss')
}

Отслеживание количества запросов

queryCache.subscribe((event) => {
  if (event.type === 'added') {
    metrics.increment('query.created')
  }

  if (event.type === 'removed') {
    metrics.increment('query.removed')
  }
})

Мониторинг background refetch

Фоновые обновления могут создавать скрытую нагрузку.

queryCache.subscribe((event) => {
  const query = event.query

  if (!query) {
    return
  }

  if (query.state.fetchStatus === 'fetching') {
    metrics.increment('background.fetch')
  }
})

Анализ stale state

Некорректная конфигурация staleTime часто приводит к:

  • чрезмерным запросам;
  • перегрузке API;
  • скачкам UI;
  • бесконечным обновлениям.

Мониторинг stale-state помогает выявлять подобные проблемы.

queryCache.subscribe((event) => {
  const query = event.query

  if (!query) {
    return
  }

  const isStale = query.isStale()

  monitoring.track('query_state', {
    stale: isStale,
    key: query.queryKey
  })
})

Мониторинг offline-режима

TanStack Query поддерживает offline-first архитектуру.

Отслеживание пауз запросов

queryCache.subscribe((event) => {
  const query = event.query

  if (
    query?.state.fetchStatus === 'paused'
  ) {
    monitoring.track('offline_query', {
      key: query.queryKey
    })
  }
})

Мониторинг восстановления соединения

window.addEventListener('online', () => {
  monitoring.track('network_restored')
})

window.addEventListener('offline', () => {
  monitoring.track('network_lost')
})

Мониторинг optimistic updates

Optimistic update — потенциальный источник рассинхронизации данных.

useMutation({
  mutationFn: updateTodo,

  onMutate: async (newTodo) => {
    monitoring.track('optimistic_started')
  },

  onError: () => {
    monitoring.track('optimistic_reverted')
  },

  onSuccess: () => {
    monitoring.track('optimistic_confirmed')
  }
})

Отслеживание memory leaks

Большое количество запросов способно привести к росту памяти.

Мониторинг Query Cache

setInterval(() => {
  const queries =
    queryClient
      .getQueryCache()
      .getAll()

  monitoring.gauge(
    'query_cache_size',
    queries.length
  )
}, 10000)

Контроль времени жизни кеша

Слишком большие значения gcTime увеличивают потребление памяти.

const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      gcTime: 1000 * 60 * 5
    }
  }
})

Интеграция с Grafana

Grafana обычно используется вместе с:

  • Prometheus;
  • Loki;
  • Tempo;
  • OpenTelemetry.

Отправка frontend-метрик

metrics.increment('query.success')

metrics.increment('query.error')

metrics.histogram(
  'query.duration',
  duration
)

Полезные dashboard-метрики

Метрика Назначение
query.duration Время запросов
query.error.rate Частота ошибок
query.retry.count Повторные запросы
cache.hit.rate Эффективность кеша
mutation.failure.rate Ошибки мутаций
background.refetch.count Фоновые обновления
optimistic.rollback.rate Откаты optimistic update
offline.queue.size Размер offline-очереди

Интеграция с Prometheus

Prometheus обычно не взаимодействует напрямую с frontend, однако frontend может отправлять агрегированные данные через backend.

Формирование frontend telemetry

const telemetry = {
  queryKey,
  duration,
  status,
  retries
}

navigator.sendBeacon(
  '/metrics',
  JSON.stringify(telemetry)
)

Мониторинг пользовательского опыта

Технические ошибки не всегда отражают реальное качество UX.

Измерение loading time

const started = performance.now()

const { data } = useQuery({
  queryKey: ['products'],
  queryFn: fetchProducts
})

const duration = performance.now() - started

Мониторинг частоты loading state

if (isLoading) {
  metrics.increment('ui.loading')
}

Высокая частота загрузок часто говорит о:

  • слишком маленьком staleTime;
  • отсутствии prefetch;
  • неправильной инвалидации;
  • чрезмерном refetch.

Correlation ID

Correlation ID позволяет связывать frontend-ошибки с backend-логами.

async function api(url) {
  const correlationId = crypto.randomUUID()

  const response = await fetch(url, {
    headers: {
      'X-Correlation-ID': correlationId
    }
  })

  return response.json()
}

Интеграция через Axios interceptors

При использовании Axios удобно внедрять мониторинг через interceptors.

import axios from 'axios'

const api = axios.create()

api.interceptors.request.use((config) => {
  config.metadata = {
    started: performance.now()
  }

  return config
})

api.interceptors.response.use(
  (response) => {
    const duration =
      performance.now() -
      response.config.metadata.started

    monitoring.track('request_success', {
      duration
    })

    return response
  },

  (error) => {
    monitoring.track('request_error')

    return Promise.reject(error)
  }
)

Интеграция с React Error Boundary

Ошибки TanStack Query можно связывать с глобальной системой UI-ошибок.

useQuery({
  queryKey: ['profile'],
  queryFn: fetchProfile,
  throwOnError: true
})

Далее ошибка попадает в:

<ErrorBoundary>
  <App />
</ErrorBoundary>

Внутри Error Boundary возможно:

  • логирование;
  • отправка stack trace;
  • уведомление пользователя;
  • автоматический recovery.

Мониторинг SSR и hydration

В SSR-приложениях важно отслеживать:

  • время дегидрации;
  • размер serialized cache;
  • ошибки hydration;
  • повторные запросы после hydration.

Метрика размера dehydrated state

const state = dehydrate(queryClient)

const size = JSON.stringify(state).length

monitoring.track('dehydrated_size', {
  size
})

Слишком большой dehydrated state увеличивает:

  • размер HTML;
  • TTFB;
  • hydration time;
  • потребление памяти браузера.

Feature flags и мониторинг

Разные стратегии кеширования могут сравниваться через feature flags.

const staleTime =
  featureFlags.newCaching
    ? 1000 * 60 * 5
    : 0

Далее собираются метрики:

  • количество запросов;
  • скорость UI;
  • ошибки;
  • нагрузка API.

Алертинг

Системы мониторинга позволяют формировать автоматические алерты.

Типичные правила

Событие Условие
Высокий error rate > 5%
Долгие запросы > 3 сек
Excessive retries > 10 retries/min
Cache miss spike Резкий рост
Mutation failures Рост ошибок мутаций
Offline queue growth Увеличение очереди

Антипаттерны мониторинга

Логирование всех успешных запросов

onSuccess: () => {
  console.log('success')
}

При высокой нагрузке подобное создаёт огромный поток логов.


Отправка чувствительных данных

Недопустимо отправлять:

  • токены;
  • cookies;
  • пароли;
  • банковские данные;
  • персональные данные.

Синхронный мониторинг

Плохой вариант:

await monitoring.send()

Мониторинг не должен блокировать UI.


Избыточный tracing

Слишком подробный tracing способен:

  • ухудшать производительность;
  • перегружать систему хранения;
  • усложнять анализ.

Практическая схема production-мониторинга

Типичная production-связка:

TanStack Query
    ↓
Axios interceptors
    ↓
OpenTelemetry
    ↓
Collector
    ↓
Grafana / Datadog / New Relic

Дополнительно:

TanStack Query
    ↓
Sentry
    ↓
Error Tracking

Подобная архитектура обеспечивает:

  • централизованное наблюдение;
  • трассировку запросов;
  • анализ производительности;
  • диагностику ошибок;
  • контроль стабильности frontend-приложения;
  • аудит сетевого взаимодействия;
  • выявление деградации UX;
  • раннее обнаружение проблем API.