npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@besales/loghub-client

v1.1.0

Published

Lightweight TypeScript client for LogHub error logging service

Readme

@besales/loghub-client

Лёгкий TypeScript-клиент для отправки логов в LogHub. Ноль внешних зависимостей — только нативный fetch (Node.js >= 18).

Установка

yarn add @besales/loghub-client
# или
npm install @besales/loghub-client

Quick Start

import { LogHubClient } from '@besales/loghub-client';

const loghub = new LogHubClient({
  endpoint: 'https://loghub.example.com',
  apiKey: 'sk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx', // ← ваш API-ключ
});

await loghub.error('Cannot connect to database', {
  stackTrace: error.stack,
  metadata: { userId: 'usr_123', requestId: 'req_456' },
});

API

Конструктор

const client = new LogHubClient(options: LogHubClientOptions);

| Параметр | Тип | По умолчанию | Описание | |----------|-----|-------------|----------| | endpoint | string | — | URL LogHub-сервиса (trailing slash удаляется) | | apiKey | string | — | API-ключ сервиса (формат sk_live_...) | | timeout | number | 5000 | Таймаут HTTP-запроса в мс | | retries | number | 2 | Количество повторных попыток при сетевых ошибках | | retryDelay | number | 1000 | Базовая задержка между retry в мс | | onError | (error: Error) => void | console.error | Callback при ошибке отправки | | throwOnError | boolean | false | Бросать ли исключение при неудаче. По умолчанию метод вернёт null |

По умолчанию логирование не бросает исключенийawait loghub.error(...) безопасен и не уронит ваш код. При неудаче вызывается onError, метод возвращает null.

Методы

class LogHubClient {
  /** Отправка лога с произвольным уровнем. null при ошибке (если throwOnError=false) */
  log(level: LogLevel, message: string, options?: LogOptions): Promise<LogResponse | null>;

  /** Shorthand-методы */
  debug(message: string, options?: LogOptions): Promise<LogResponse | null>;
  info(message: string, options?: LogOptions): Promise<LogResponse | null>;
  warn(message: string, options?: LogOptions): Promise<LogResponse | null>;
  error(message: string, options?: LogOptions): Promise<LogResponse | null>;
  fatal(message: string, options?: LogOptions): Promise<LogResponse | null>;
}

LogOptions

interface LogOptions {
  /** Stack trace ошибки */
  stackTrace?: string;
  /** Произвольные метаданные */
  metadata?: Record<string, unknown>;
}

LogResponse

interface LogResponse {
  /** ID созданного лога */
  id: string;
  /** Fingerprint ошибки (SHA-256) */
  fingerprint: string;
  /** Был ли лог дедуплицирован */
  deduplicated: boolean;
}

LogLevel

enum LogLevel {
  DEBUG = 'DEBUG',
  INFO = 'INFO',
  WARN = 'WARN',
  ERROR = 'ERROR',
  FATAL = 'FATAL',
}

| Уровень | Когда использовать | |---------|-------------------| | DEBUG | Детальные данные для диагностики | | INFO | Значимые события (запуск, миграция) | | WARN | Потенциальные проблемы (высокая нагрузка) | | ERROR | Ошибки, требующие внимания | | FATAL | Сбой, требующий немедленного вмешательства |

LogHubApiError

class LogHubApiError extends Error {
  readonly statusCode: number;
  readonly responseBody: string;
}

Выбрасывается при HTTP-ошибках сервера. Содержит код ответа и тело.

NestJS интеграция

Модуль

import { Global, Module } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { LogHubClient } from '@besales/loghub-client';

const LOGHUB_CLIENT = 'LOGHUB_CLIENT';

@Global()
@Module({
  providers: [
    {
      provide: LOGHUB_CLIENT,
      useFactory: (config: ConfigService) => {
        return new LogHubClient({
          endpoint: config.getOrThrow('LOGHUB_ENDPOINT'),
          apiKey: config.getOrThrow('LOGHUB_API_KEY'),
        });
      },
      inject: [ConfigService],
    },
  ],
  exports: [LOGHUB_CLIENT],
})
export class LogHubModule {}

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

@Injectable()
export class PaymentService {
  constructor(
    @Inject('LOGHUB_CLIENT') private readonly loghub: LogHubClient,
  ) {}

  async processPayment(dto: PaymentDto): Promise<void> {
    try {
      // ... бизнес-логика ...
    } catch (error) {
      await this.loghub.error('Payment processing failed', {
        stackTrace: error instanceof Error ? error.stack : undefined,
        metadata: { paymentId: dto.id, amount: dto.amount },
      });
      throw error;
    }
  }
}

Глобальный exception filter

import { ExceptionFilter, Catch, ArgumentsHost, HttpException, Inject } from '@nestjs/common';
import { LogHubClient } from '@besales/loghub-client';

@Catch()
export class GlobalExceptionLogHubFilter implements ExceptionFilter {
  constructor(
    @Inject('LOGHUB_CLIENT') private readonly loghub: LogHubClient,
  ) {}

  async catch(exception: unknown, host: ArgumentsHost): Promise<void> {
    const ctx = host.switchToHttp();
    const request = ctx.getRequest();
    const response = ctx.getResponse();

    const status = exception instanceof HttpException
      ? exception.getStatus()
      : 500;

    // Отправляем в LogHub только 5xx ошибки
    if (status >= 500) {
      const error = exception instanceof Error ? exception : new Error(String(exception));

      // Fire-and-forget: не блокируем ответ
      this.loghub.error(error.message, {
        stackTrace: error.stack,
        metadata: {
          method: request.method,
          url: request.url,
          statusCode: status,
          userAgent: request.headers['user-agent'],
        },
      }).catch(() => {
        // Не падаем, если LogHub недоступен
      });
    }

    response.status(status).send({
      statusCode: status,
      message: exception instanceof HttpException
        ? exception.message
        : 'Internal server error',
    });
  }
}

Express интеграция

import express from 'express';
import { LogHubClient } from '@besales/loghub-client';

const app = express();
const loghub = new LogHubClient({
  endpoint: process.env.LOGHUB_ENDPOINT!,
  apiKey: process.env.LOGHUB_API_KEY!,
});

// Global error handler (должен быть последним middleware)
app.use((err: Error, req: express.Request, res: express.Response, _next: express.NextFunction) => {
  loghub.error(err.message, {
    stackTrace: err.stack,
    metadata: {
      method: req.method,
      url: req.originalUrl,
      ip: req.ip,
    },
  }).catch(() => {
    // Не падаем, если LogHub недоступен
  });

  res.status(500).json({ error: 'Internal server error' });
});

Telegram-бот интеграция

import { Telegraf } from 'telegraf';
import { LogHubClient } from '@besales/loghub-client';

const bot = new Telegraf(process.env.BOT_TOKEN!);
const loghub = new LogHubClient({
  endpoint: process.env.LOGHUB_ENDPOINT!,
  apiKey: process.env.LOGHUB_API_KEY!,
});

bot.catch((error, ctx) => {
  loghub.error(error instanceof Error ? error.message : 'Unknown bot error', {
    stackTrace: error instanceof Error ? error.stack : undefined,
    metadata: {
      updateType: ctx.updateType,
      chatId: ctx.chat?.id,
      userId: ctx.from?.id,
    },
  }).catch(() => {});
});

Обработка ошибок

Retry-стратегия

  • 5xx ошибки и сетевые ошибки — автоматический retry с exponential backoff (retryDelay * 2^attempt)
  • 4xx ошибки — без retry (клиентская ошибка, retry бессмысленен)
  • После исчерпания попыток вызывается onError. По умолчанию метод возвращает null и не бросает — передайте throwOnError: true, чтобы пробрасывать исключение

Fire-and-forget

По умолчанию ошибки логирования не пробрасываются, поэтому .catch() не обязателен:

// безопасно: при сбое LogHub вернёт null, исключение не бросится
await loghub.error('Something failed', { stackTrace: error.stack });

Пользовательский onError

const loghub = new LogHubClient({
  endpoint: 'https://loghub.example.com',
  apiKey: 'sk_live_...',
  onError: (error) => {
    // Собственная обработка: Sentry, Winston, etc.
    myLogger.warn('LogHub unavailable', { error: error.message });
  },
});

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

| Параметр | Значение по умолчанию | Описание | |----------|----------------------|----------| | timeout | 5000 мс | Таймаут одного запроса | | retries | 2 | Макс. повторных попыток | | retryDelay | 1000 мс | Базовая задержка (удваивается) |

Лимиты сервера

| Параметр | Значение | |----------|----------| | message | Макс. 2 000 символов | | stackTrace | Макс. 10 000 символов | | metadata | Макс. 50 KB (JSON) | | Размер тела запроса | Макс. 1 MB | | Окно дедупликации | 3 минуты (одинаковые ошибки подавляются) |

Лицензия

MIT