Error tracking

Фреймворк Fresh предоставляет современный подход к разработке веб-приложений на JavaScript и Deno, сочетая высокую производительность с минимальной сложностью. Одним из ключевых аспектов стабильного приложения является эффективная система отслеживания ошибок. В Fresh она строится на сочетании встроенных инструментов и сторонних решений.


Обработка ошибок на уровне маршрутов

Каждый маршрут в Fresh представлен компонентом, экспортирующим функцию handler, которая может содержать обработку HTTP-запросов. Для базовой обработки ошибок применяется конструкция try...catch.

export const handler = {
  async GET(req, ctx) {
    try {
      const data = await fetchSomeData();
      return new Response(JSON.stringify(data), { status: 200 });
    } catch (err) {
      console.error("Ошибка запроса:", err);
      return new Response("Внутренняя ошибка сервера", { status: 500 });
    }
  }
};

Особенности:

  • Использование console.error сохраняет стек ошибки в серверных логах Deno.
  • Можно возвращать информативные HTTP-статусы для клиента.
  • Для сложных приложений рекомендуется централизовать обработку ошибок, чтобы не дублировать код в каждом маршруте.

Глобальная обработка ошибок

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

import { h } from "preact";
import { createHandler } from "$fresh/server.ts";

function errorHandler(ctx, next) {
  try {
    return next();
  } catch (err) {
    console.error("Глобальная ошибка:", err);
    return new Response("Произошла ошибка", { status: 500 });
  }
}

const handler = createHandler({
  middleware: [errorHandler]
});

Преимущества глобальной обработки:

  • Централизованное логирование.
  • Возможность интеграции с внешними системами мониторинга.
  • Контроль над форматом ответа для всех ошибок.

Использование специальных страниц ошибок

Fresh поддерживает создание кастомных страниц ошибок, которые автоматически рендерятся при возникновении исключений на уровне компонентов.

// routes/_500.tsx
import { h } from "preact";

export default function InternalError() {
  return (
    <div>
      <h1>500 — Внутренняя ошибка сервера</h1>
      <p>Что-то пошло не так. Попробуйте позже.</p>
    </div>
  );
}

Ключевые моменты:

  • Файл _500.tsx автоматически отображается при ошибках 500.
  • Аналогично можно создавать _404.tsx для страниц не найдено.
  • Это улучшает пользовательский опыт и помогает отделить логику приложения от логики ошибок.

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

Для продакшн-приложений критично использовать внешние сервисы отслеживания ошибок:

  • Sentry: позволяет логировать ошибки с полным стеком и контекстом запроса.
  • Logflare или Datadog: обеспечивают централизованное хранение логов с фильтрацией и аналитикой.

Пример интеграции с Sentry:

import * as Sentry from "@sentry/browser";

Sentry.init({ dsn: DENO_ENV_SENTRY_DSN });

export const handler = {
  async GET(req, ctx) {
    try {
      const data = await fetchSomeData();
      return new Response(JSON.stringify(data));
    } catch (err) {
      Sentry.captureException(err);
      return new Response("Внутренняя ошибка сервера", { status: 500 });
    }
  }
};

Преимущества интеграции:

  • Автоматическое отслеживание исключений на клиенте и сервере.
  • Возможность получать уведомления о критических ошибках в реальном времени.
  • Аналитика для определения узких мест производительности и стабильности.

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

Эффективное отслеживание ошибок невозможно без структурированного логирования:

  • Использовать JSON-формат для логов с полями: timestamp, level, message, stack.
  • Поддерживать трассировку запросов через уникальные идентификаторы (requestId), что облегчает поиск причин ошибок.
  • Включать метрики производительности: время обработки запроса, статус ответа, источник ошибки.

Пример структурированного логирования:

function logError(err, reqId) {
  console.error(JSON.stringify({
    timestamp: new Date().toISOString(),
    level: "error",
    requestId: reqId,
    message: err.message,
    stack: err.stack
  }));
}

Асинхронные ошибки и промисы

В Fresh большое внимание уделяется асинхронным операциям, поэтому важно правильно обрабатывать ошибки промисов:

try {
  const result = await fetchData();
} catch (err) {
  logError(err, ctx.requestId);
}

Использование Promise.allSettled позволяет безопасно обрабатывать несколько параллельных запросов без остановки всего маршрута при ошибке одного:

const results = await Promise.allSettled([fetchData1(), fetchData2()]);

results.forEach((res) => {
  if (res.status === "rejected") {
    console.error("Ошибка асинхронного запроса:", res.reason);
  }
});

Тестирование обработки ошибок

Для стабильности приложения важно покрывать обработку ошибок юнит- и интеграционными тестами:

  • Использовать Deno.test с моками fetch-запросов.
  • Проверять корректность HTTP-статусов.
  • Подтверждать логирование ошибок и вызов внешних сервисов мониторинга.

Пример теста:

Deno.test("Маршрут GET возвращает 500 при ошибке", async () => {
  const resp = await handler.GET(new Request("http://localhost/test"));
  if (resp.status !== 500) throw new Error("Ожидался статус 500");
});

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