Логирование и аналитика

RTK Query строится вокруг централизованного API-слоя, в котором каждый запрос проходит через единый pipeline: baseQuery, middleware, кэширование и жизненный цикл подписок. Это делает систему удобной точкой для внедрения логирования на разных уровнях — от низкоуровневых HTTP-запросов до бизнес-аналитики.

Перехват запросов через baseQuery

Самый простой и управляемый способ логирования — обёртка над 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-слоя без вмешательства в бизнес-логику.


Middleware уровня RTK Store для аналитики

RTK Query интегрируется в Redux store через middleware api.middleware. Это даёт возможность добавлять дополнительные middleware поверх него.

Логирование всех экшенов RTK Query

Каждый запрос RTK Query проходит через Redux actions с типами:

  • api/executeQuery/pending
  • api/executeQuery/fulfilled
  • api/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-ов.


Использование lifecycle hooks для детального трекинга

RTK Query предоставляет встроенные хуки жизненного цикла:

  • onQueryStarted
  • onCacheEntryAdded

Они позволяют фиксировать события на уровне конкретного endpoint.

onQueryStarted для логирования запроса

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 активно использует кэш, и его поведение также может быть источником аналитики.

onCacheEntryAdded

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 часто отправляются не в консоль, а в аналитические системы:

  • Google Analytics
  • Amplitude
  • Mixpanel
  • собственные backend-сервисы

Пример интеграции с условной аналитикой

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, что упрощает последующую аналитику.

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

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

Ошибки RTK Query часто содержат структурированную информацию:

  • status
  • data
  • error message

Расширенное логирование:

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.


Разделение логирования для dev и prod окружений

В 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

RTK Query можно рассматривать как поток событий:

  • запрос инициирован
  • запрос отправлен
  • ответ получен
  • кэш обновлён
  • подписка удалена

На основе этих событий строится полноценная аналитика поведения пользователя:

  • частота обращений к endpoint-ам
  • задержки загрузки
  • повторные запросы
  • влияние кэша на нагрузку

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);
};

Централизованная система наблюдаемости API

При масштабировании приложения логирование перестаёт быть локальной задачей. Формируется единый слой наблюдаемости:

  • baseQuery → сетевые метрики
  • middleware → события Redux
  • endpoint hooks → бизнес-уровень
  • analytics service → внешняя система

Такой подход позволяет восстановить полный путь любого запроса: от пользовательского действия до ответа сервера, включая кэш и повторные подписки.