RTK Query строится вокруг централизованного API-слоя, в котором
каждый запрос проходит через единый pipeline: baseQuery,
middleware, кэширование и жизненный цикл подписок. Это делает систему
удобной точкой для внедрения логирования на разных уровнях — от
низкоуровневых HTTP-запросов до бизнес-аналитики.
Самый простой и управляемый способ логирования — обёртка над
baseQuery. Это позволяет фиксировать:
Пример расширенного fetchBaseQuery:
import { fetchBaseQuery } from '@reduxjs/toolkit/query/react';
const rawBaseQuery = fetchBaseQuery({
baseUrl: '/api',
});
export const baseQueryWithLogging = async (args, api, extraOptions) => {
const start = performance.now();
console.log('[RTK Query] Request start:', args);
const result = await rawBaseQuery(args, api, extraOptions);
const duration = performance.now() - start;
if (result.error) {
console.error('[RTK Query] Request error:', {
args,
error: result.error,
duration,
});
} else {
console.log('[RTK Query] Request success:', {
args,
duration,
});
}
return result;
};
Такой слой формирует основу наблюдаемости API-слоя без вмешательства в бизнес-логику.
RTK Query интегрируется в Redux store через middleware
api.middleware. Это даёт возможность добавлять
дополнительные middleware поверх него.
Каждый запрос RTK Query проходит через Redux actions с типами:
api/executeQuery/pendingapi/executeQuery/fulfilledapi/executeQuery/rejectedНа этом уровне удобно собирать аналитику:
export const analyticsMiddleware = (store) => (next) => (action) => {
const result = next(action);
if (action.type.startsWith('api/')) {
const meta = action.meta;
console.log('[Analytics]', {
type: action.type,
endpoint: meta?.arg?.endpointName,
timing: meta?.requestStartTime
? Date.now() - meta.requestStartTime
: null,
});
}
return result;
};
Такой middleware позволяет централизованно собирать статистику без модификации endpoint-ов.
RTK Query предоставляет встроенные хуки жизненного цикла:
onQueryStartedonCacheEntryAddedОни позволяют фиксировать события на уровне конкретного endpoint.
getUser: builder.query({
query: (id) => `/users/${id}`,
async onQueryStarted(arg, { queryFulfilled }) {
const start = performance.now();
console.log('[Endpoint] getUser started:', arg);
try {
const result = await queryFulfilled;
const duration = performance.now() - start;
console.log('[Endpoint] getUser success:', {
arg,
duration,
data: result.data,
});
} catch (err) {
const duration = performance.now() - start;
console.error('[Endpoint] getUser failed:', {
arg,
duration,
error: err,
});
}
},
});
Этот подход даёт точечное наблюдение за конкретными запросами без глобального шума.
RTK Query активно использует кэш, и его поведение также может быть источником аналитики.
getMessages: builder.query({
query: (chatId) => `/chat/${chatId}/messages`,
async onCacheEntryAdded(arg, { cacheDataLoaded, cacheEntryRemoved }) {
console.log('[Cache] Entry added:', arg);
await cacheDataLoaded;
console.log('[Cache] Data loaded:', arg);
await cacheEntryRemoved;
console.log('[Cache] Entry removed:', arg);
},
});
Это позволяет отслеживать:
Логи RTK Query часто отправляются не в консоль, а в аналитические системы:
const sendMetric = (event, payload) => {
fetch('/analytics', {
method: 'POST',
body: JSON.stringify({ event, payload }),
});
};
Интеграция через baseQuery:
export const baseQueryWithAnalytics = async (args, api, extraOptions) => {
const start = performance.now();
const result = await rawBaseQuery(args, api, extraOptions);
const duration = performance.now() - start;
sendMetric('api_request', {
endpoint: args,
success: !result.error,
duration,
});
return result;
};
RTK Query позволяет прокидывать meta, что упрощает
последующую аналитику.
const baseQuery = fetchBaseQuery({
baseUrl: '/api',
prepareHeaders: (headers, { getState, endpoint }) => {
headers.set('x-endpoint', endpoint);
return headers;
},
});
Также можно возвращать собственный meta:
return {
data: result.data,
meta: {
duration,
timestamp: Date.now(),
},
};
Это позволяет централизованно обрабатывать данные в middleware.
Для анализа производительности важно разделять:
const start = performance.now();
const result = await rawBaseQuery(args, api, extraOptions);
const networkTime = performance.now() - start;
api.dispatch({
type: 'metrics/apiCall',
payload: {
endpoint: args,
networkTime,
cached: result.meta?.fromCache ?? false,
},
});
Ошибки RTK Query часто содержат структурированную информацию:
Расширенное логирование:
if (result.error) {
console.group('[API Error]');
console.log('Endpoint:', args);
console.log('Status:', result.error.status);
console.log('Dat a:', result.error.data);
console.log('Full error:', result.error);
console.groupEnd();
}
Это упрощает диагностику проблем на уровне API.
В production логирование должно быть ограничено, чтобы не создавать overhead и не утекали данные.
const isDev = process.env.NODE_ENV === 'development';
export const baseQueryWithLogging = async (args, api, extraOptions) => {
if (isDev) {
console.log('[RTK]', args);
}
const result = await rawBaseQuery(args, api, extraOptions);
if (isDev && result.error) {
console.error('[RTK Error]', result.error);
}
return result;
};
RTK Query можно рассматривать как поток событий:
На основе этих событий строится полноценная аналитика поведения пользователя:
Middleware превращает Redux store в поток событий:
const eventMiddleware = (store) => (next) => (action) => {
if (action.type.startsWith('api/')) {
analytics.track('rtk_event', {
type: action.type,
endpoint: action.meta?.arg?.endpointName,
});
}
return next(action);
};
При масштабировании приложения логирование перестаёт быть локальной задачей. Формируется единый слой наблюдаемости:
Такой подход позволяет восстановить полный путь любого запроса: от пользовательского действия до ответа сервера, включая кэш и повторные подписки.