TanStack Query активно управляет жизненным циклом запросов, кешем, повторными попытками, фоновыми обновлениями и мутациями. При росте приложения количество сетевых операций увеличивается, а вместе с ними усложняется диагностика проблем:
Логирование и мониторинг позволяют:
TanStack Query содержит внутренний механизм логирования, используемый для отображения ошибок и предупреждений.
В ранних версиях библиотеки logger можно было переопределять напрямую
через setLogger, однако в современных версиях основной
подход строится вокруг собственных обработчиков ошибок и интеграции с
внешними системами мониторинга.
Тем не менее концепция логирования остаётся важной.
Самый распространённый сценарий — централизованный сбор ошибок запросов.
Пример:
import {
QueryClient,
QueryClientProvider
} from '@tanstack/react-query'
const queryClient = new QueryClient({
defaultOptions: {
queries: {
onError: (error) => {
console.error('Ошибка запроса:', error)
}
}
}
})
Теперь каждая ошибка запроса будет автоматически логироваться.
Ошибки мутаций зачастую критичнее ошибок чтения данных.
Пример:
const queryClient = new QueryClient({
defaultOptions: {
mutations: {
onError: (error) => {
console.error('Ошибка мутации:', error)
}
}
}
})
Это особенно важно для:
В production-приложениях console.error почти не
используется напрямую. Вместо этого данные отправляются в
telemetry-системы.
Пример:
function reportError(error, context) {
fetch('/telemetry', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
message: error.message,
stack: error.stack,
context,
timestamp: Date.now()
})
})
}
Интеграция:
const queryClient = new QueryClient({
defaultOptions: {
queries: {
onError: (error) => {
reportError(error, {
type: 'query'
})
}
},
mutations: {
onError: (error) => {
reportError(error, {
type: 'mutation'
})
}
}
}
})
Одной из главных проблем больших приложений становится сложность понимания, какой именно запрос завершился ошибкой.
Поэтому рекомендуется логировать queryKey.
Пример:
useQuery({
queryKey: ['users', userId],
queryFn: fetchUser,
onError: (error) => {
console.error('Ошибка запроса', {
queryKey: ['users', userId],
error
})
}
})
Производительность API напрямую влияет на UX.
TanStack Query не измеряет длительность запроса автоматически, но это легко реализуется вручную.
Пример:
async function fetchUsers() {
const startedAt = performance.now()
const response = await fetch('/api/users')
const data = await response.json()
const finishedAt = performance.now()
console.log('Время запроса:', finishedAt - startedAt)
return data
}
Медленные запросы можно автоматически выделять.
Пример:
async function monitoredFetch(url) {
const startedAt = performance.now()
const response = await fetch(url)
const duration = performance.now() - startedAt
if (duration > 1000) {
console.warn('Медленный запрос', {
url,
duration
})
}
return response.json()
}
TanStack Query активно использует retry-механизм.
При нестабильном API важно понимать:
Пример:
useQuery({
queryKey: ['posts'],
queryFn: fetchPosts,
retry: 3,
retryDelay: (attempt) => {
console.log('Retry попытка:', attempt)
return attempt * 1000
}
})
TanStack Query может автоматически обновлять данные:
Это иногда вызывает незаметную перегрузку API.
Для диагностики полезно логировать refetch.
Пример:
useQuery({
queryKey: ['stats'],
queryFn: async () => {
console.log('Выполняется refetch')
return fetchStats()
},
refetchInterval: 5000
})
Одним из самых мощных механизмов мониторинга является подписка на
QueryCache.
Пример:
const queryClient = new QueryClient()
queryClient.getQueryCache().subscribe((event) => {
console.log('Событие QueryCache:', event)
})
Через события кеша можно отслеживать:
Событие содержит подробную информацию:
{
type,
query
}
Объект query включает:
query.queryKey
query.state.status
query.state.fetchStatus
query.state.data
query.state.error
Пример анализа:
queryClient.getQueryCache().subscribe((event) => {
if (event.type === 'updated') {
console.log({
key: event.query.queryKey,
status: event.query.state.status,
fetchStatus: event.query.state.fetchStatus
})
}
})
Мутации также поддерживают централизованный мониторинг.
Пример:
queryClient.getMutationCache().subscribe((event) => {
console.log('Mutation event:', event)
})
Это особенно полезно для:
Иногда важно понимать общую нагрузку frontend-приложения.
Пример:
const queries = queryClient.getQueryCache().getAll()
console.log('Количество запросов:', queries.length)
Активные запросы можно анализировать отдельно.
Пример:
const fetchingCount = queryClient.isFetching()
console.log('Активных запросов:', fetchingCount)
Для мутаций:
const mutatingCount = queryClient.isMutating()
console.log('Активных мутаций:', mutatingCount)
Для разработки TanStack Query предоставляет мощные Devtools.
Основные возможности:
Подключение:
import { ReactQueryDevtools } from '@tanstack/react-query-devtools'
function App() {
return (
<>
<Application />
<ReactQueryDevtools initialIsOpen={false} />
</>
)
}
Каждый запрос имеет несколько ключевых состояний:
status
Возможные значения:
pending
success
error
Дополнительно:
fetchStatus
Возможные значения:
idle
fetching
paused
Мониторинг этих состояний помогает находить:
TanStack Query строится вокруг концепции stale/fresh данных.
Полезно отслеживать момент устаревания данных.
Пример:
useQuery({
queryKey: ['profile'],
queryFn: fetchProfile,
staleTime: 5000,
onSuccess: () => {
console.log('Данные обновлены')
}
})
Sentry — одна из самых популярных систем мониторинга ошибок frontend-приложений.
Пример интеграции:
import * as Sentry from '@sentry/react'
const queryClient = new QueryClient({
defaultOptions: {
queries: {
onError: (error) => {
Sentry.captureException(error)
}
}
}
})
Расширенный вариант:
onError: (error, query) => {
Sentry.captureException(error, {
tags: {
queryKey: JSON.stringify(query.queryKey)
}
})
}
Datadog позволяет собирать frontend telemetry.
Пример:
import { datadogLogs } from '@datadog/browser-logs'
function logQueryError(error, queryKey) {
datadogLogs.logger.error('Query error', {
queryKey,
message: error.message
})
}
Современные distributed-системы часто используют OpenTelemetry.
Пример:
function traceRequest(queryKey, duration) {
console.log('trace', {
queryKey,
duration
})
}
Интеграция с queryFn:
async function fetchData() {
const startedAt = performance.now()
const response = await fetch('/api/data')
const duration = performance.now() - startedAt
traceRequest(['data'], duration)
return response.json()
}
Крупные системы часто используют request-id.
Frontend генерирует идентификатор:
const requestId = crypto.randomUUID()
И передаёт его в заголовках:
fetch('/api/users', {
headers: {
'X-Request-Id': requestId
}
})
Backend сохраняет тот же идентификатор в логах.
Это позволяет:
TanStack Query поддерживает offline-first архитектуру.
Мониторинг offline-режима критически важен.
Пример:
window.addEventListener('offline', () => {
console.warn('Приложение offline')
})
window.addEventListener('online', () => {
console.log('Соединение восстановлено')
})
Избыточный кеш способен вызывать утечки памяти.
Полезно отслеживать:
Пример:
const queries = queryClient.getQueryCache().getAll()
queries.forEach((query) => {
console.log({
key: query.queryKey,
updatedAt: query.state.dataUpdatedAt
})
})
Чрезмерная invalidation — одна из главных причин лишней сетевой активности.
Пример логирования:
function invalidateUsers() {
console.log('Invalidate users')
queryClient.invalidateQueries({
queryKey: ['users']
})
}
Optimistic update может приводить к сложным race condition.
Пример логирования:
useMutation({
mutationFn: updatePost,
onMutate: async (variables) => {
console.log('Optimistic update', variables)
},
onError: (error) => {
console.error('Rollback optimistic update', error)
}
})
В SSR-приложениях полезно анализировать hydration-процесс.
Пример:
console.log('Hydration started')
После восстановления кеша:
console.log('Hydration finished')
Это помогает находить:
В production логирование должно быть:
Плохой пример:
console.log(error)
Хороший пример:
logger.error({
type: 'query_error',
queryKey,
message: error.message,
timestamp: Date.now()
})
Структурированные JSON-логи значительно удобнее для аналитики.
Пример:
{
type: 'query_error',
queryKey: ['users'],
duration: 1200,
retryCount: 2,
timestamp: 1716540000
}
Такие логи легко анализируются:
Мониторинг TanStack Query часто включает сбор метрик:
Пример простого счётчика ошибок:
let errorCount = 0
function trackError() {
errorCount++
}
Высокий cache hit rate означает эффективную работу кеша.
Низкий показатель может говорить о:
Пример примитивного анализа:
function logCacheState(query) {
console.log({
key: query.queryKey,
hasData: !!query.state.data
})
}
Иногда Devtools подключаются и в production-среде, но только для администраторов или debug-режима.
Пример:
{
process.env.NODE_ENV === 'development' && (
<ReactQueryDevtools />
)
}
Либо:
const isDebugEnabled =
localStorage.getItem('debug') === 'true'
Слишком большое количество логов:
Нельзя логировать:
Огромные JSON-ответы способны:
Одна и та же ошибка может логироваться:
Это создаёт шум и затрудняет анализ.
Крупные frontend-приложения обычно строят следующую схему:
Такая архитектура позволяет превратить TanStack Query из обычной библиотеки управления серверным состоянием в полноценный источник observability-данных frontend-приложения.