0.1.0 • Published 10 months ago

@kiz8/logger v0.1.0

Weekly downloads
-
License
MIT
Repository
github
Last release
10 months ago

@reputation-management/logger

Универсальный логгер для системы управления репутацией, построенный на базе Winston.

Архитектура и обзор

@reputation-management/logger представляет собой унифицированное решение для логирования в проектах системы управления репутацией. Пакет предоставляет обертку над библиотекой Winston, добавляя необходимую для проекта функциональность и интеграцию.

Основные архитектурные компоненты:

  • Ядро логгера: Основано на Winston с настраиваемыми транспортами для консоли и файлов.
  • Форматирование: Структурированные JSON-логи с метаданными и читаемое форматирование для консоли.
  • Интеграции: Готовые интеграции с Hono (HTTP), Inngest (фоновые задачи) и Vercel Fluid Functions.
  • Утилиты: Вспомогательные функции для логирования ошибок и создания дочерних логгеров.

Архитектура логгера разработана для обеспечения:

  • Гибкости: Возможность настройки под различные сценарии использования
  • Расширяемости: Простое добавление новых транспортов и форматеров
  • Консистентности: Единый формат логов во всех частях системы
  • Производительности: Оптимизированное логирование для снижения влияния на производительность

Возможности

  • Гибкая конфигурация уровней логирования
  • Логирование в консоль и файлы
  • Ротация файлов логов
  • Форматирование логов с метаданными
  • Интеграция с Hono для логирования HTTP запросов
  • Интеграция с Inngest для логирования фоновых задач
  • Интеграция с Vercel Fluid Functions
  • Создание дочерних логгеров с предустановленными метаданными

Установка

npm install @reputation-management/logger

или

yarn add @reputation-management/logger

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

Базовое использование

import { defaultLogger, LogLevel } from "@reputation-management/logger";

// Использование логгера по умолчанию
defaultLogger.info("Приложение запущено");
defaultLogger.error("Произошла ошибка", { error: "Детали ошибки" });

// Создание собственного логгера
import { createLogger } from "@reputation-management/logger";

const logger = createLogger({
  level: LogLevel.DEBUG,
  console: true,
  file: true,
  logDir: "./logs",
  defaultMeta: {
    service: "my-service",
  },
});

logger.info("Сообщение с метаданными", { userId: "123", action: "login" });

// Установка уровня логирования динамически
logger.setLevel(LogLevel.WARN); // Теперь будут логгироваться только WARN и ERROR

// Получение текущего логгера из контекста
import { getLogger } from "@reputation-management/logger";
const contextLogger = getLogger();

Логирование ошибок

import {
  defaultLogger,
  createErrorMeta,
  formatError,
} from "@reputation-management/logger";

try {
  // Какой-то код, который может вызвать ошибку
  throw new Error("Пример ошибки");
} catch (error) {
  if (error instanceof Error) {
    // Вариант 1: Использование createErrorMeta
    defaultLogger.error("Произошла ошибка", createErrorMeta(error));

    // Вариант 2: Форматирование ошибки в строку
    defaultLogger.error(formatError(error));

    // Вариант 3: Полное логирование для критических ошибок
    defaultLogger.error("Критическая ошибка в системе", {
      ...createErrorMeta(error),
      component: "payment-service",
      operation: "processTransaction",
      transactionId: "12345",
      isRetryable: false,
    });
  }
}

// Логирование асинхронных ошибок
async function fetchData() {
  try {
    const response = await fetch("https://api.example.com/data");
    if (!response.ok) {
      throw new Error(`API error: ${response.status}`);
    }
    return await response.json();
  } catch (error) {
    defaultLogger.error(
      "Ошибка при запросе данных",
      error instanceof Error
        ? createErrorMeta(error)
        : { error: String(error) },
    );
    throw error; // Переброс ошибки для обработки выше
  }
}

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

import { defaultLogger } from "@reputation-management/logger";
import { v4 as uuidv4 } from "uuid";

// Использование трассировки запросов
function processOrder(order, user) {
  const traceId = uuidv4();
  const logger = defaultLogger.child({
    traceId,
    userId: user.id,
    orderId: order.id,
  });

  logger.info("Начало обработки заказа");

  // Логирование каждого шага с тем же traceId
  try {
    logger.info("Проверка наличия товаров", {
      products: order.products.map((p) => p.id),
    });

    logger.info("Проверка платежной информации", {
      paymentMethod: order.paymentMethod,
    });

    logger.info("Создание заказа завершено", {
      status: "completed",
      total: order.total,
    });

    return { success: true, orderId: order.id };
  } catch (error) {
    logger.error("Ошибка при обработке заказа", createErrorMeta(error));
    return { success: false, error: error.message };
  }
}

Интеграция с Hono

import { Hono } from "hono";
import {
  defaultLogger,
  honoLoggerMiddleware,
  honoErrorLoggerMiddleware,
} from "@reputation-management/logger";

const app = new Hono();

// Базовая интеграция
// ===================

// Добавление middleware для логирования запросов
app.use("*", honoLoggerMiddleware(defaultLogger));

// Добавление middleware для логирования ошибок
app.onError(honoErrorLoggerMiddleware(defaultLogger));

// Маршруты приложения
app.get("/", (c) => c.text("Hello World"));

// Расширенная интеграция
// ======================

// Настройка middleware с дополнительными опциями
app.use(
  "*",
  honoLoggerMiddleware(defaultLogger, {
    excludePaths: ["/health", "/metrics"], // Исключить пути из логирования
    logBody: true, // Логировать тело запроса
    maskHeaders: ["authorization", "x-api-key"], // Маскировать заголовки
    getRequestId: (c) => c.req.header("x-request-id") || uuidv4(), // Функция для получения ID запроса
  }),
);

// Создание middleware для конкретной группы маршрутов
const apiRoutes = new Hono();

// Логгер с предустановленными метаданными для API
const apiLogger = defaultLogger.child({ component: "api" });

// Применение логгера только для API маршрутов
apiRoutes.use("*", honoLoggerMiddleware(apiLogger));

// Добавление API маршрутов к основному приложению
app.route("/api", apiRoutes);

// Пример обработки запроса с пользовательским логированием
app.post("/api/users", async (c) => {
  const requestLogger = apiLogger.child({
    requestId: c.get("requestId"),
    path: "/api/users",
  });

  requestLogger.info("Получен запрос на создание пользователя");

  try {
    const body = await c.req.json();
    requestLogger.debug("Тело запроса", { body });

    // Логика создания пользователя
    const user = await createUser(body);

    requestLogger.info("Пользователь успешно создан", { userId: user.id });
    return c.json({ success: true, user });
  } catch (error) {
    requestLogger.error(
      "Ошибка при создании пользователя",
      createErrorMeta(error),
    );
    return c.json({ success: false, error: error.message }, 500);
  }
});

export default app;

Интеграция с Inngest

import { Inngest } from "inngest";
import {
  defaultLogger,
  createInngestLogger,
} from "@reputation-management/logger";

// Создание клиента Inngest
const inngest = new Inngest({ name: "My App" });

// Создание функции Inngest
const myFunction = inngest.createFunction(
  { name: "My Function" },
  { event: "my/event" },
  async ({ event, step }) => {
    // Создание логгера для функции
    const logger = createInngestLogger(defaultLogger, "My Function");

    // Логирование начала выполнения
    logger.start("my/event", event.data);

    try {
      // Выполнение шага
      logger.stepStart("process-data");
      const result = await step.run("process-data", async () => {
        // Логирование внутри шага
        logger.info("Обработка данных", { data: event.data });
        return { processed: true };
      });
      logger.stepSuccess("process-data", result);

      // Дополнительные шаги с логированием
      logger.stepStart("notify-user");
      await step.run("notify-user", async () => {
        logger.info("Отправка уведомления пользователю", {
          userId: event.data.userId,
        });
        // Реализация отправки уведомления
        return { notified: true };
      });
      logger.stepSuccess("notify-user");

      // Логирование успешного завершения
      logger.success(result);
      return result;
    } catch (error) {
      // Логирование ошибки
      logger.error(error);
      throw error;
    }
  },
);

// Регистрация множества функций с одним логгером
const createUserFunction = inngest.createFunction(
  { name: "Create User" },
  { event: "user/created" },
  async ({ event, step }) => {
    const logger = createInngestLogger(defaultLogger, "Create User", {
      tenant: event.data.tenantId,
      environment: process.env.NODE_ENV,
    });

    logger.start("user/created", { userId: event.data.userId });

    // Реализация функции...

    return { success: true };
  },
);

// Мониторинг выполнения Inngest функций
function setupInngestMonitoring(inngestClient) {
  const monitoringLogger = defaultLogger.child({
    component: "inngest-monitor",
  });

  // Слушаем события выполнения функций
  inngestClient.onFunctionRun((event) => {
    monitoringLogger.info("Inngest функция запущена", {
      functionId: event.functionId,
      eventId: event.eventId,
      attemptNumber: event.attemptNumber,
    });
  });

  // Слушаем события завершения функций
  inngestClient.onFunctionComplete((event) => {
    monitoringLogger.info("Inngest функция завершена", {
      functionId: event.functionId,
      eventId: event.eventId,
      duration: event.duration,
      status: "success",
    });
  });

  // Слушаем события ошибок функций
  inngestClient.onFunctionError((event) => {
    monitoringLogger.error("Ошибка выполнения Inngest функции", {
      functionId: event.functionId,
      eventId: event.eventId,
      error: event.error,
      status: "failed",
    });
  });
}

Интеграция с Vercel Fluid Functions

import {
  defaultLogger,
  createVercelFunctionLogger,
} from "@reputation-management/logger";

export default async function handler(req, res) {
  // Создание логгера для функции
  const logger = createVercelFunctionLogger(defaultLogger, "api/example");

  // Логирование начала выполнения с расширенными метаданными
  logger.start({
    method: req.method,
    url: req.url,
    userAgent: req.headers["user-agent"],
    requestId:
      req.headers["x-request-id"] || Math.random().toString(36).substring(2),
    ip: req.headers["x-forwarded-for"] || req.connection.remoteAddress,
  });

  try {
    // Логирование информации о запросе
    logger.info("Обработка запроса", {
      query: req.query,
      headers: filterSensitiveHeaders(req.headers),
      body: req.body ? sanitizeRequestBody(req.body) : undefined,
    });

    // Измерение времени выполнения
    const startTime = Date.now();

    // Выполнение бизнес-логики
    const result = await processRequest(req);

    // Логирование времени выполнения
    const executionTime = Date.now() - startTime;
    logger.info("Запрос обработан", { executionTime });

    // Логирование успешного завершения
    logger.success({
      result: omitSensitiveData(result),
      executionTime,
    });

    return res.status(200).json(result);
  } catch (error) {
    // Расширенное логирование ошибки
    logger.error("Ошибка при обработке запроса", {
      error: error instanceof Error ? error.message : String(error),
      stack: error instanceof Error ? error.stack : undefined,
      path: req.url,
      method: req.method,
      requestId: req.headers["x-request-id"],
    });

    // Определение кода ошибки
    const statusCode = error.statusCode || 500;
    const errorMessage =
      statusCode === 500 ? "Internal Server Error" : error.message;

    // Возврат ответа с ошибкой
    return res.status(statusCode).json({
      error: errorMessage,
      requestId: req.headers["x-request-id"],
    });
  } finally {
    // Логирование завершения обработки запроса
    logger.info("Обработка запроса завершена");
  }
}

// Вспомогательные функции
function filterSensitiveHeaders(headers) {
  const filtered = { ...headers };
  const sensitiveHeaders = ["authorization", "cookie", "x-api-key"];

  for (const header of sensitiveHeaders) {
    if (filtered[header]) {
      filtered[header] = "***";
    }
  }

  return filtered;
}

function sanitizeRequestBody(body) {
  if (!body) return undefined;

  const sanitized = { ...body };
  const sensitiveFields = ["password", "token", "secret", "creditCard"];

  for (const field of sensitiveFields) {
    if (sanitized[field]) {
      sanitized[field] = "***";
    }
  }

  return sanitized;
}

function omitSensitiveData(data) {
  // Реализация удаления чувствительных данных из ответа
  return data;
}

async function processRequest(req) {
  // Пример реализации бизнес-логики
  return { success: true, data: { processed: true } };
}

Мониторинг и анализ логов

import { createLogger, LogLevel } from "@reputation-management/logger";
import { format, transports } from "winston";

// Логгер с расширенными возможностями мониторинга
const monitoringLogger = createLogger({
  level: LogLevel.INFO,
  console: true,
  file: true,
  defaultMeta: {
    service: "monitoring-service",
    version: process.env.APP_VERSION || "1.0.0",
    environment: process.env.NODE_ENV || "development",
  },
});

// Добавление транспорта для отправки критических ошибок в систему оповещения
if (process.env.NODE_ENV === "production") {
  monitoringLogger.add(
    new transports.Http({
      host: "alerts.example.com",
      path: "/api/log",
      ssl: true,
      level: "error",
      format: format.json(),
      auth: {
        username: process.env.ALERT_API_USERNAME,
        password: process.env.ALERT_API_PASSWORD,
      },
    }),
  );
}

// Функция для измерения производительности операций
function measurePerformance(operationName, callback) {
  const startTime = performance.now();
  const result = callback();
  const endTime = performance.now();

  monitoringLogger.info("Метрика производительности", {
    operation: operationName,
    durationMs: endTime - startTime,
    timestamp: new Date().toISOString(),
  });

  return result;
}

// Измерение асинхронных операций
async function measureAsyncPerformance(operationName, asyncCallback) {
  const startTime = performance.now();
  try {
    const result = await asyncCallback();
    const endTime = performance.now();

    monitoringLogger.info("Метрика производительности", {
      operation: operationName,
      durationMs: endTime - startTime,
      timestamp: new Date().toISOString(),
      success: true,
    });

    return result;
  } catch (error) {
    const endTime = performance.now();

    monitoringLogger.error("Ошибка операции с метрикой", {
      operation: operationName,
      durationMs: endTime - startTime,
      timestamp: new Date().toISOString(),
      success: false,
      error: error instanceof Error ? error.message : String(error),
    });

    throw error;
  }
}

// Пример использования мониторинга
async function monitoredOperation() {
  return measureAsyncPerformance("database-query", async () => {
    // Асинхронный запрос к базе данных
    const result = await db.query("SELECT * FROM users LIMIT 100");
    return result;
  });
}

Создание дочерних логгеров

import { defaultLogger } from "@reputation-management/logger";

// Создание логгера для конкретного сервиса
const userServiceLogger = defaultLogger.child({
  service: "user-service",
  component: "auth",
});

userServiceLogger.info("Пользователь вошел в систему", { userId: "123" });

// Иерархия дочерних логгеров
const authLogger = defaultLogger.child({ component: "auth" });
const loginLogger = authLogger.child({ operation: "login" });
const registrationLogger = authLogger.child({ operation: "registration" });

// Использование дочерних логгеров
function userLogin(username, password) {
  loginLogger.info("Попытка входа в систему", { username });

  // Логика аутентификации...

  loginLogger.info("Успешный вход", { username });
}

function userRegistration(userData) {
  registrationLogger.info("Начало регистрации пользователя");

  // Логика регистрации...

  registrationLogger.info("Пользователь зарегистрирован", {
    username: userData.username,
    email: userData.email,
  });
}

// Передача логгеров между модулями
function createAuthService(options = {}) {
  const logger =
    options.logger || defaultLogger.child({ service: "auth-service" });

  return {
    login: (username, password) => {
      logger.info("Вход в систему", { username });
      // ...
    },
    logout: (userId) => {
      logger.info("Выход из системы", { userId });
      // ...
    },
    // Получение логгера для использования другими функциями
    getLogger: () => logger,
  };
}

// Использование
const authService = createAuthService({
  logger: defaultLogger.child({
    service: "custom-auth-service",
    tenant: "tenant-123",
  }),
});

// Получение логгера из сервиса для дальнейшего использования
const serviceLogger = authService.getLogger();
serviceLogger.debug("Отладочная информация из сервиса аутентификации");

Конфигурация

Логгер поддерживает следующие параметры конфигурации:

ПараметрТипПо умолчаниюОписание
levelLogLevelLogLevel.INFOУровень логирования
logDirstring'./logs'Директория для хранения логов
maxSizestring'20m'Максимальный размер файла лога перед ротацией
maxDaysnumber14Максимальное количество дней хранения логов
consolebooleantrueВключить логирование в консоль
filebooleantrueВключить логирование в файл
datePatternstring'YYYY-MM-DD'Формат даты для имени файла
filePrefixstring'app'Префикс для имени файла
defaultMetaobject{ service: 'reputation-management' }Дополнительные метаданные для каждого лога
colorizebooleantrueВключить цветное форматирование в консоли

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

Логгер поддерживает следующие уровни логирования (в порядке убывания важности):

  1. ERROR - Ошибки, требующие внимания
  2. WARN - Предупреждения, которые не являются ошибками
  3. INFO - Информационные сообщения
  4. HTTP - HTTP запросы
  5. VERBOSE - Подробная информация
  6. DEBUG - Отладочная информация
  7. SILLY - Детальная отладочная информация

API документация

Основные компоненты

Logger

Класс Logger предоставляет основные методы для логирования:

МетодОписание
error(message, meta?)Логирование ошибок
warn(message, meta?)Логирование предупреждений
info(message, meta?)Логирование информационных сообщений
http(message, meta?)Логирование HTTP запросов
verbose(message, meta?)Логирование подробной информации
debug(message, meta?)Логирование отладочной информации
silly(message, meta?)Логирование детальной отладочной информации
child(meta)Создание дочернего логгера с предустановленными метаданными

Экспортируемые функции

ФункцияОписание
createLogger(options)Создает новый экземпляр логгера с указанными опциями
defaultLoggerПредустановленный экземпляр логгера с настройками по умолчанию
createErrorMeta(error)Создает метаданные для логирования ошибок, включая стек вызовов
formatError(error)Форматирует ошибку в строку для логирования
honoLoggerMiddleware(logger)Создает middleware для логирования HTTP запросов в Hono
honoErrorLoggerMiddleware(logger)Создает middleware для логирования ошибок в Hono
createInngestLogger(logger, functionName)Создает логгер для функций Inngest
createVercelFunctionLogger(logger, functionPath)Создает логгер для Vercel Fluid Functions

Типы

ТипОписание
LogLevelПеречисление уровней логирования (ERROR, WARN, INFO, HTTP, VERBOSE, DEBUG, SILLY)
LoggerOptionsИнтерфейс опций для создания логгера
LogMetaТип для метаданных логирования (расширяет Record<string, any>)

Параметры конфигурации (LoggerOptions)

ПараметрТипПо умолчаниюОписание
levelLogLevelLogLevel.INFOМинимальный уровень логирования
logDirstring'./logs'Директория для хранения файлов логов
maxSizestring'20m'Максимальный размер файла лога перед ротацией
maxDaysnumber14Максимальное количество дней хранения логов
consolebooleantrueВключить логирование в консоль
filebooleantrueВключить логирование в файл
datePatternstring'YYYY-MM-DD'Формат даты для имени файла
filePrefixstring'app'Префикс для имени файла
defaultMetaobject{ service: 'reputation-management' }Дополнительные метаданные для каждого лога
colorizebooleantrueВключить цветное форматирование в консоли

Расширенное использование

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

import { createLogger, LogLevel, format } from "@reputation-management/logger";
import { format as winstonFormat } from "winston";

// Создание логгера с пользовательским форматированием
const logger = createLogger({
  level: LogLevel.INFO,
  console: true,
  file: true,
  // Добавление пользовательского форматера
  format: winstonFormat.combine(
    winstonFormat.timestamp(),
    winstonFormat.json(),
    winstonFormat((info) => {
      info.custom_field = "custom_value";
      return info;
    })(),
  ),
});

Расширение логгера дополнительными транспортами

import { createLogger, LogLevel } from "@reputation-management/logger";
import { transports } from "winston";

// Создание логгера с дополнительным транспортом
const logger = createLogger({
  level: LogLevel.INFO,
  console: true,
  file: true,
});

// Добавление транспорта Syslog
logger.add(
  new transports.Syslog({
    host: "logs.papertrailapp.com",
    port: 12345,
    protocol: "tls4",
    localhost: "my-app",
    eol: "\n",
  }),
);

Создание иерархии логгеров для разных компонентов

import { defaultLogger } from "@reputation-management/logger";

// Логгер для API-слоя
const apiLogger = defaultLogger.child({ component: "api" });

// Логгеры для разных контроллеров
const userController = apiLogger.child({ controller: "user" });
const orderController = apiLogger.child({ controller: "order" });

// Использование
userController.info("Получение профиля пользователя", { userId: "123" });
orderController.info("Создание заказа", { orderId: "456", userId: "123" });

Лучшие практики логирования в продакшене

  1. Структурированное логирование: Всегда используйте метаданные для контекста

    logger.info("Пользователь авторизован", {
      userId: user.id,
      role: user.role,
      requestId: context.requestId,
    });
  2. Централизованное управление уровнями логирования:

    // config.ts
    export const LOG_LEVEL = process.env.LOG_LEVEL || "info";
    
    // logger.ts
    import { LOG_LEVEL } from "./config";
    import { createLogger, LogLevel } from "@reputation-management/logger";
    
    export const logger = createLogger({
      level: LOG_LEVEL as LogLevel,
      // другие настройки
    });
  3. Трассировка запросов через метаданные:

    // middleware.ts
    import { v4 as uuidv4 } from "uuid";
    
    app.use("*", (c, next) => {
      c.set("requestId", uuidv4());
      return next();
    });
    
    // route-handler.ts
    app.get("/api/users", (c) => {
      const requestId = c.get("requestId");
      logger.info("Запрос списка пользователей", { requestId });
      // ...
    });

Руководство для разработчиков

Установка и настройка

# Клонирование репозитория
git clone [URL-репозитория]
cd packages/logger

# Установка зависимостей
pnpm install

# Сборка пакета
pnpm run build

# Запуск тестов
pnpm run test

# Проверка линтером
pnpm run lint

Добавление новых интеграций

Для добавления интеграции с новой платформой:

  1. Создайте новый файл в директории src/integrations/
  2. Реализуйте фабричную функцию для создания специализированного логгера
  3. Добавьте экспорт в src/index.ts
  4. Обновите документацию и добавьте примеры использования

Структура проекта

packages/logger/
src/
  ├── constants.ts    # Константы и настройки по умолчанию
  ├── format.ts       # Форматирование логов
  ├── index.ts        # Основной экспорт
  ├── integrations/   # Интеграции с другими платформами
  ├── logger.ts       # Основная реализация логгера
  ├── types.ts        # Типы и интерфейсы
  └── utils.ts        # Вспомогательные функции

Вклад в развитие проекта

Мы приветствуем вклад сообщества! Подробнее о процессе разработки и правилах можно узнать в CONTRIBUTING.md.

Лицензия

MIT

0.1.0

10 months ago