Структурированное логирование

Структурированное логирование является одним из ключевых аспектов построения масштабируемых и поддерживаемых приложений во Fresh. В отличие от обычного текстового логирования, структурированные логи представляют собой объекты с заранее определённой схемой, что упрощает последующую фильтрацию, агрегацию и анализ данных.

Принципы структурированного логирования

  1. Формат логов Логи должны иметь единый формат, чаще всего JSON, где каждая запись содержит обязательные поля:

    • timestamp — отметка времени события в ISO 8601 формате.
    • level — уровень логирования (debug, info, warn, error).
    • message — описание события.
    • context — объект с дополнительными данными, относящимися к событию (например, идентификатор запроса, имя пользователя, состояние приложения).
  2. Контекстность Логи должны передавать контекст выполнения. В Fresh это часто реализуется через middleware или глобальные объекты состояния, которые добавляют к каждой записи уникальные идентификаторы запросов и метаданные.

  3. Предсказуемость структуры Все записи должны следовать единой схеме. Это позволяет использовать лог-агрегаторы (например, Loki, Elasticsearch, Grafana) для построения аналитики без дополнительных преобразований.

Настройка логирования во Fresh

Fresh не имеет встроенной сложной системы логирования, поэтому чаще всего используется комбинация стандартного console с обёртками для структурирования. Простейший пример:

function log(level, message, context = {}) {
  const logEntry = {
    timestamp: new Date().toISOString(),
    level,
    message,
    context,
  };
  console.log(JSON.stringify(logEntry));
}

// Использование
log("info", "Запрос обработан успешно", { userId: 42, path: "/api/data" });

Такой подход позволяет сразу отправлять логи в систему сбора без дополнительного парсинга.

Логирование HTTP-запросов

Fresh активно использует маршрутизацию через routes. Для логирования каждого запроса удобно использовать middleware:

import { HandlerContext } from "$fresh/server.ts";

export async function handler(req: Request, ctx: HandlerContext) {
  const requestId = crypto.randomUUID();
  const startTime = Date.now();

  try {
    const resp = await ctx.next(); // передача запроса дальше
    const duration = Date.now() - startTime;
    log("info", "Запрос обработан", {
      requestId,
      path: new URL(req.url).pathname,
      method: req.method,
      status: resp.status,
      durationMs: duration
    });
    return resp;
  } catch (err) {
    log("error", "Ошибка при обработке запроса", {
      requestId,
      path: new URL(req.url).pathname,
      method: req.method,
      error: err.message,
    });
    throw err;
  }
}

Особое внимание уделяется requestId — уникальному идентификатору запроса. Он позволяет связывать все логи, относящиеся к одной операции, даже если обработка включает асинхронные вызовы или внутренние сервисы.

Уровни логирования

Использование уровней логирования упрощает фильтрацию информации:

  • debug — подробная отладочная информация, которая нужна только разработчику.
  • info — ключевые события работы приложения.
  • warn — предупреждения, которые не мешают выполнению, но требуют внимания.
  • error — ошибки, приводящие к сбоям или некорректной работе.

В Fresh можно реализовать условную фильтрацию, чтобы, например, в production выводить только info, warn и error, а в development — все уровни:

const LOG_LEVELS = { debug: 0, info: 1, warn: 2, error: 3 };
const CURRENT_LEVEL = "info";

function log(level, message, context = {}) {
  if (LOG_LEVELS[level] >= LOG_LEVELS[CURRENT_LEVEL]) {
    console.log(JSON.stringify({ timestamp: new Date().toISOString(), level, message, context }));
  }
}

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

Структурированные логи легко интегрировать с внешними платформами мониторинга и алертинга:

  • Grafana Loki — хранение логов в формате JSON с возможностью поиска по любому полю.
  • Elasticsearch + Kibana — построение дашбордов и сложных фильтров.
  • Datadog / Sentry — сбор метрик ошибок и уведомления о критических событиях.

Для интеграции достаточно направлять сериализованные JSON-записи через HTTP или писать их в файл, который считывается агентом логирования.

Лучшие практики

  • Всегда включать метаданные запроса (requestId, userId, sessionId), чтобы связывать события.
  • Использовать одинаковые ключи в context для всех логов.
  • Никогда не логировать чувствительные данные (пароли, токены).
  • Настраивать уровни логирования под среду исполнения.
  • Регулярно проверять, что структура логов соответствует ожиданиям внешних систем аналитики.

Структурированное логирование во Fresh превращает обычные сообщения об ошибках в мощный инструмент анализа и мониторинга, позволяя быстро выявлять проблемы и улучшать качество приложения.