npm package: @morescreens/log-collector
Description: Structured logging library for client applications.
Latest version: 0.0.8

# @morescreens/log-collector

Structured logging library for client applications.

## Features

- **Multiple log types** — standard logs, watching activity telemetry, and push notification logging
- **Log levels** — Debug, Info, Warn, Error, Critical with configurable minimum level
- **Two-phase init** — construct with minimal config, call `configure()` later when remaining fields are available
- **Singleton** — create once, retrieve anywhere with `Logger.getInstance()`
- **Session tracking** — auto-generated session ID per Logger instance, included in standard log payloads for grouping logs from the same session
- **Auto-retry with priority buffer** — failed sends are buffered and retried with configurable buffer size
- **TypeScript-first** — full type definitions, ESM only

## Installation

```bash
npm install @morescreens/log-collector
```

## Quick Start

```ts
import { Logger, LogLevel, LogValidationError } from "@morescreens/log-collector"
import type { LoggerOptions, LogEntry, WatchActivityEntry } from "@morescreens/log-collector"

// 1. Create the singleton (only logSource is required)
const logger = new Logger({ logSource: "my-app" })

// 2. Configure remaining fields when available
logger.configure({
  collectorUrl: "https://collector.example.com/log",
  installation: "abc-123",
  uuid: "device-uuid",
  applicationInstallationId: "42",
  applicationPublicationId: "pub-1",
  cappVersion: "1.0.0",
  timezone: "Europe/Sarajevo",
  logLevel: LogLevel.Warn,
})

// 3. Send logs
logger.warn({ message: "Retry attempt failed" })
logger.error({ message: "Something failed", tag: "network" })

// 4. Retrieve the singleton from anywhere
const same = Logger.getInstance()
```

## Log Types

### Standard Logs

General-purpose application logging with five severity levels. Messages below the configured `logLevel` are discarded.

| Method     | Level    | Enum Value |
| ---------- | -------- | ---------- |
| `debug`    | Debug    | `2`        |
| `info`     | Info     | `3`        |
| `warn`     | Warn     | `4`        |
| `error`    | Error    | `5`        |
| `critical` | Critical | `6`        |

```ts
logger.debug({ message: "EPG data loaded" })
logger.info({ message: "Channel changed", tag: "ZappLog" })
logger.warn({ message: "Retry attempt failed", valueStr1: url, valueInt1: retryCount })
logger.error({ message: "API request failed", valueStr1: "GET /api/channels", valueStr2: "503" })
logger.critical({ message: `${err.message} stack: ${err.stack}` })
```

Each method accepts a `LogEntry`:

| Field        | Type     | Required |
| ------------ | -------- | -------- |
| `message`    | `string` | Yes      |
| `tag`        | `string` | No       |
| `targetIndex`| `string` | No       |

Additional optional fields:

| Field                              | Type     |
| ---------------------------------- | -------- |
| `valueInt1` – `valueInt4`          | `number` |
| `valueStr1` – `valueStr4`         | `string` |

### Watching Activity

Content viewing telemetry sent via `sendWatchingActivity()`. Used to track what users are watching, with metadata about the content and series. Automatically sets `targetIndex` to `"watching_activity_log"`.

```ts
logger.sendWatchingActivity({
  message: "PLAY",
  contentId: "12345",
  contentKind: "vod",
  intervalDuration: 30,
})
```

| Field                  | Type      | Required |
| ---------------------- | --------- | -------- |
| `message`              | `string`  | Yes      |
| `contentId`            | `string`  | Yes      |
| `contentExternalId`    | `string`  | No       |
| `contentAssetType`     | `string`  | No       |
| `contentKind`          | `string`  | No       |
| `contentAudioOnly`     | `boolean` | No       |
| `contentOriginalTitle` | `string`  | No       |
| `contentProviderName`  | `string`  | No       |
| `contentSeriesId`      | `string`  | No       |
| `deliveryMethod`       | `string`  | No       |
| `seriesTitle`          | `string`  | No       |
| `seriesOriginalTitle`  | `string`  | No       |
| `seriesSeason`         | `number`  | No       |
| `seriesEpisode`        | `number`  | No       |
| `epgId`                | `string`  | No       |
| `epgTitle`             | `string`  | No       |
| `intervalDuration`     | `number`  | No       |
| `catchupTime`          | `number`  | No       |
| `partnerName`          | `string`  | No       |
| `valueInt`             | `number`  | No       |
| `valueStr`             | `string`  | No       |

### Push Notifications

Logs push notification events via `sendPushNotification()`. Automatically sets `targetIndex` to `"push_notification_log"`.

```ts
logger.sendPushNotification({ message: "Notification received" })
```

Accepts the same `LogEntry` shape as standard logs.

## API Reference

### `new Logger(options)`

Creates the singleton Logger instance. Only `logSource` is required at construction; all other fields can be set later via `configure()`. Calling `new Logger()` again replaces the singleton.

### `Logger.getInstance()`

Returns the singleton instance. Throws if no Logger has been created yet.

### `configure(partial)`

Merges additional options into the current configuration. Validates the merged result. Can be called multiple times — each call merges on top of the previous state, so you can progressively provide fields as they become available.

```ts
// Set URL early
logger.configure({ collectorUrl: "https://collector.example.com/log" })

// Add user context later when available
logger.configure({ profileId: "user-42", subscriberId: "sub-7" })
```

## Configuration

All fields in `LoggerOptions`. Fields marked "Required at send" must be set (via constructor or `configure()`) before calling any send method — otherwise a `LogValidationError` is thrown.

| Field                       | Type       | Required at init | Required at send | Default |
| --------------------------- | ---------- | ---------------- | ---------------- | ------- |
| `logSource`                 | `string`   | Yes              | Yes              | —       |
| `collectorUrl`              | `string`   | No               | Yes              | —       |
| `installation`              | `string`   | No               | Yes              | —       |
| `uuid`                      | `string`   | No               | Yes              | —       |
| `applicationInstallationId` | `string`   | No               | Yes              | —       |
| `applicationPublicationId`  | `string`   | No               | Yes              | —       |
| `cappVersion`               | `string`   | No               | Yes              | —       |
| `timezone`                  | `string`   | No               | Yes              | —       |
| `logLevel`                  | `LogLevel` | No               | No               | `Info`  |
| `enableConsoleLog`          | `boolean`  | No               | No               | `false` |
| `maxBufferSize`             | `number`   | No               | No               | `100`   |
| `profileId`                 | `string`   | No               | No               | —       |
| `subscriberId`              | `string`   | No               | No               | —       |

## Buffering & Retry

Failed HTTP sends are automatically buffered and retried on the next send attempt. The buffer holds up to `maxBufferSize` entries (default: 100) and can be configured via the constructor or `configure()`:

```ts
const logger = new Logger({ logSource: "my-app", maxBufferSize: 200 })
```

When the buffer is full, low-priority logs (below Error) are evicted first to preserve error and critical entries. If the buffer is entirely error/critical, the oldest entry is evicted to make room for new high-priority logs.

When buffered messages are retried, they are sent with `[buffered][datetime]` appended to the original message, making them identifiable in the logs.

## Error Handling

The library throws `LogValidationError` when options or entries contain invalid values. It extends `Error` and includes:

- `field` — the name of the invalid field
- `value` — the offending value

```ts
import { LogValidationError } from "@morescreens/log-collector"

try {
  logger.info({ message: 123 as any })
} catch (err) {
  if (err instanceof LogValidationError) {
    console.error(err.field) // "message"
    console.error(err.value) // 123
  }
}
```