npm package: @inso_web/els-client
Description: Легковесный TypeScript‑клиент для Error Logs Service (ELS) — сбор, батчинг и отправка ошибок из браузера и Node.js. Zero dependencies, retry с поддержкой Retry-After, silent fail, sendBeacon, идемпотентность по traceId, universal (browser + Node >=18).
Author: INSOWEB
Latest version: 0.3.0

# @inso_web/els-client

[![npm version](https://img.shields.io/npm/v/@inso_web/els-client.svg)](https://www.npmjs.com/package/@inso_web/els-client)
[![npm downloads](https://img.shields.io/npm/dm/@inso_web/els-client.svg)](https://www.npmjs.com/package/@inso_web/els-client)
[![bundle size](https://img.shields.io/bundlephobia/minzip/@inso_web/els-client)](https://bundlephobia.com/package/@inso_web/els-client)
[![TypeScript](https://img.shields.io/badge/TypeScript-strict-blue.svg)](https://www.typescriptlang.org/)
[![license MIT](https://img.shields.io/npm/l/@inso_web/els-client.svg)](./LICENSE)

Лёгкий TypeScript‑клиент для **Error Logs Service (ELS)** — облачного сервиса сбора ошибок приложений. Собирает, батчит и отправляет ошибки и события из браузера и Node.js — **без транзитивных зависимостей**.

---

## Что это

ELS — сервис для сбора и аналитики ошибок приложений. Этот пакет — тонкий клиент, который:

- принимает ошибки (`Error`, объекты `ErrorEntry`),
- буферизует их в очереди,
- отправляет батчами на ELS по HTTPS,
- никогда не ломает приложение, если сервис недоступен.

Лёгкая альтернатива тяжёлым SDK мониторинга — крошечный клиентский бандл и нулевые зависимости.

## Фичи

| Фича | Описание |
|---|---|
| Zero dependencies | Никаких транзитивных зависимостей — только ваш код |
| Крошечный размер | ~3 KB gzip |
| TypeScript strict | Полные типы из коробки |
| Universal | Работает в браузере и Node.js 18+ |
| Retry | Экспоненциальный бэкофф + уважение `Retry-After` на 429 |
| Batching | `ELSQueue` с авто‑флашем по таймеру или размеру |
| Silent fail | Никогда не бросает наружу — только `console.error` |
| sendBeacon | Надёжная доставка при `pagehide`/`beforeunload` |
| Идемпотентность | Авто‑`traceId` (UUID v4) для дедупликации на сервере |
| ESM + CJS | Оба формата, корректный `exports` map |
| **Logger API** | **Pino‑совместимые методы `info/warn/error/debug/trace/fatal` + `child()` для drop‑in миграции** |

---

## Использование как логгер (Pino‑compatible)

`ELSClient` реализует Pino‑совместимый интерфейс `Logger`. Это значит, что вы можете использовать его как обычный логгер с привычным API — без отдельной библиотеки.

```ts
import { ELSClient } from '@inso_web/els-client';

// Создаёшь один раз при старте приложения
export const log = new ELSClient({
  endpoint: 'https://api.insoweb.ru/els',
  apiKey: process.env.ELS_API_KEY!,
  appSlug: 'my-service',
  serviceName: 'api',
  deploymentEnv: 'PRODUCTION',
  minLevel: 'info',
});

// Используешь в любом месте кода
log.info('Server started on port 3000');
log.warn({ userId: 42 }, 'High request rate');
log.error(err, 'Database query failed');

// Контекстные child‑логгеры
const reqLog = log.child({ requestId: 'r1', userId: 42 });
reqLog.info('processing');
reqLog.error(err, 'failed');
```

**Поведение**:
- Методы — fire‑and‑forget. Не возвращают Promise, не throw, не ломают приложение.
- Сетевые ошибки тихо логируются в `console.error`, ваш код не падает.
- Уровни ниже `minLevel` отбрасываются без отправки.

**Маппинг уровней на ELS `level`**:
- `fatal` → `critical`
- `error` → `error`
- `warn` → `warning`
- `info` → `info`
- `debug` → `debug`
- `trace` → `debug`

## Migration from Pino / Winston / Loki

Переход с других логгеров — в одну строку. API совпадает.

### Pino → @inso_web/els-client

```diff
- import pino from 'pino';
- const log = pino({ level: 'info' });
+ import { ELSClient } from '@inso_web/els-client';
+ const log = new ELSClient({ endpoint, apiKey, appSlug, minLevel: 'info' });
// log.info(), log.warn(), log.error(), log.child() — API совпадает
```

### Winston → @inso_web/els-client

```diff
- import winston from 'winston';
- const log = winston.createLogger({ level: 'info', transports: [...] });
+ import { ELSClient } from '@inso_web/els-client';
+ const log = new ELSClient({ endpoint, apiKey, appSlug, minLevel: 'info' });
```

### pino-loki → @inso_web/els-client

```diff
- import pino from 'pino';
- const log = pino({ ... }, pinoLoki({ host: 'loki.local' }));
+ import { ELSClient } from '@inso_web/els-client';
+ const log = new ELSClient({ endpoint: 'https://api.insoweb.ru/els', ... });
```

Концептуально то же: положили лог → он улетел в централизованное хранилище. Но без дополнительных пакетов‑транспортов и с zero dependencies.

---

## Quick Start

```bash
npm install @inso_web/els-client
```

```ts
import { ELSClient } from '@inso_web/els-client';

const els = new ELSClient({
  endpoint: 'https://api.insoweb.ru/els',
  apiKey: process.env.ELS_API_KEY!,
  appSlug: 'my-app',
  deploymentEnv: 'PRODUCTION',
});

try {
  riskyCode();
} catch (err) {
  await els.sendError({
    message: (err as Error).message,
    stack: (err as Error).stack,
    url: typeof location !== 'undefined' ? location.href : '',
    level: 'error',
  });
}
```

## Installation

```bash
npm install @inso_web/els-client
# или
pnpm add @inso_web/els-client
# или
yarn add @inso_web/els-client
```

## Usage

### Одиночная ошибка

```ts
await els.sendError({
  message: 'Payment failed',
  url: location.href,
  stack: err.stack,
  level: 'error',
});
```

### Батчинг через очередь

```ts
import { ELSClient, ELSQueue } from '@inso_web/els-client';

const client = new ELSClient({ /* ... */ });
const queue = new ELSQueue(client, {
  flushIntervalMs: 5_000,
  maxBatchSize: 20,
  useBeacon: true, // авто‑flush на pagehide через sendBeacon
});

queue.enqueue({ message: 'warn', url: location.href, level: 'warning' });
```

### В браузере

```ts
window.addEventListener('error', (e) => {
  queue.enqueue({
    message: e.message,
    url: location.href,
    stack: e.error?.stack,
    level: 'error',
    source: 'client',
  });
});

window.addEventListener('unhandledrejection', (e) => {
  queue.enqueue({
    message: String(e.reason?.message ?? e.reason),
    url: location.href,
    stack: e.reason?.stack,
    level: 'error',
    source: 'client',
  });
});
```

### В Node.js

```ts
process.on('uncaughtException', (err) => {
  void client.sendError({
    message: err.message,
    stack: err.stack,
    url: process.argv[1] ?? 'node',
    level: 'critical',
    source: 'server',
  });
});
```

## Configuration (ELSConfig)

| Опция | Тип | По умолчанию | Описание |
|---|---|---|---|
| `endpoint` | `string` | — | URL инстанса ELS (обязательно) |
| `apiKey` | `string` | — | API‑ключ приложения (обязательно) |
| `appSlug` | `string` | — | Slug приложения (обязательно) |
| `deploymentEnv` | `'DEV'\|'STAGING'\|'PRODUCTION'` | `'DEV'` | Окружение |
| `serviceName` | `string` | — | Имя сервиса/модуля |
| `timeout` | `number` | `10000` | Таймаут HTTP в мс |
| `retries` | `number` | `3` | Число ретраев при сетевых ошибках и 429 |
| `authHeader` | `'bearer'\|'x-api-key'` | `'bearer'` | Формат передачи ключа |

## API

```ts
class ELSClient {
  constructor(config: ELSConfig);
  sendError(entry: ErrorEntry): Promise<void>;
  sendBatch(entries: ErrorEntry[]): Promise<BatchResult | null>;
  flush(): Promise<void>;
}

class ELSQueue {
  constructor(client: ELSClient, opts?: QueueOptions);
  enqueue(entry: ErrorEntry): void;
  flush(): Promise<void>;
  stop(): void;
}
```

Типы `ErrorEntry`, `ErrorLevel`, `ErrorSource`, `DeploymentEnv`, `BatchResult` экспортируются.

## Advanced

- **Идемпотентность**: каждому событию клиент присваивает `traceId` (UUID v4), если не задан вручную. Повторные события с тем же `traceId` сервер считает дубликатами.
- **`Retry-After`**: при 429 клиент уважает заголовок `Retry-After` (секунды), иначе использует линейный бэкофф.
- **sendBeacon**: `ELSQueue` на `pagehide` использует `navigator.sendBeacon`, чтобы доставить буфер даже при закрытии вкладки.
- **Silent fail**: клиент не бросает сетевых ошибок наружу — только логирует в `console.error`.

## FAQ

**Чем отличается от Sentry?** Меньший бандл, нулевые зависимости, минимальный API. Подходит когда нужен лёгкий error reporter без overhead'а тяжёлого SDK.

**Как это использовать в React/Vue?** Берите обёртки [`@inso_web/els-react`](https://www.npmjs.com/package/@inso_web/els-react) и [`@inso_web/els-vue`](https://www.npmjs.com/package/@inso_web/els-vue).

**Работает в edge/workers?** Да — используется только глобальный `fetch`.

## Comparison

|  | ELS client | Sentry (SaaS) | LogRocket |
|---|---|---|---|
| Размер бандла | ~3 KB | ~70 KB | ~50 KB |
| Zero deps | Да | Нет | Нет |
| TypeScript strict | Да | Да | Частично |
| Минималистичный API | Да | Нет | Нет |

## Пакеты‑обёртки

- [`@inso_web/els-react`](https://www.npmjs.com/package/@inso_web/els-react) — Provider, hooks, ErrorBoundary для React 17+
- [`@inso_web/els-vue`](https://www.npmjs.com/package/@inso_web/els-vue) — plugin и composable для Vue 3

## Examples

Runnable примеры: [`./examples/`](./examples/)

- `node-basic` — Node.js простой sender
- `node-batch` — batch + queue с graceful shutdown
- `browser-vanilla` — HTML через esm.sh CDN

## License

[MIT](./LICENSE) © INSOWEB