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

@morescreens/log-collector

v0.7.1

Published

Structured logging library for client applications.

Readme

@morescreens/log-collector

Structured logging library for client applications.

Features

  • Multiple log types — standard logs, watching activity telemetry, ad activity telemetry, content events (zap/playback), 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; logs sent before config is complete are deferred and flushed in order once it is, while Error/Critical are sent immediately as soon as collectorUrl is known
  • 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
  • Watch sessionsstartWatchSession() mints a per-viewing watch_session id, stamped onto subsequent watching activity logs so a single viewing's events can be grouped
  • Request labeling — each request appends ?log=<label>&log_type=<group> query params so logs are distinguishable and groupable in the browser's network inspector
  • Auto-retry with priority buffer — failed sends are buffered and retried with configurable buffer size; every payload carries a log_buffered flag (0/1), and retried entries also carry a log_buffered_datetime timestamp
  • Library version tracking — every payload includes log_source_version (e.g. @morescreens/[email protected]) for downstream filtering
  • TypeScript-first — full type definitions, ESM only

Installation

npm install @morescreens/log-collector

Quick Start

import {
  Logger,
  LogLevel,
  LogValidationError,
} from "@morescreens/log-collector"
import type {
  LoggerOptions,
  LogEntry,
  WatchActivityEntry,
  AdActivityEntry,
  ContentEventEntry,
} 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 |

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 | | type | string | No | | targetIndex | string | No |

Additional optional fields:

| Field | Type | | ------------------------- | -------- | | valueInt1valueInt4 | number | | valueStr1valueStr4 | string |

Raw logs (bypass the level filter)

raw(level, entry) sends a standard log at the given level without applying the logLevel filter — for logs that must always be delivered regardless of the configured minimum level. It accepts the same LogEntry as the level methods; the only difference is that the filter is skipped.

logger.raw(LogLevel.Debug, { message: "Always-on diagnostic", tag: "Heartbeat" })

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".

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

| Field | Type | Required | | ---------------------- | --------- | -------- | | message | string | Yes | | contentId | number | 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 | number | No | | epgTitle | string | No | | intervalDuration | number | No | | catchupTime | number | No | | partnerName | string | No | | valueInt | number | No | | valueStr | string | No |

Watch sessions. Call startWatchSession() at the start of each viewing (e.g. on play/restart) to mint a fresh watch_session id. It is stamped onto every subsequent sendWatchingActivity() payload and persists until the next call, so all events of one viewing share an id. Until the first call, watch_session is omitted. It applies to watching activity only — ad activity logs do not carry it.

Ad Activity

Ad telemetry sent via sendAdActivityLog(). Extends watching activity with ad-specific fields. Automatically sets targetIndex to "ad_activity_log".

logger.sendAdActivityLog({
  message: "AD_PLAY",
  contentId: 12345,
  adCreativeId: "creative-1",
  adFullscreen: true,
  adMuted: false,
  adWatched: 50,
  adSkipped: false,
  adTitle: "Summer Sale 2026",
})

Accepts all WatchActivityEntry fields plus the following ad fields:

| Field | Type | Required | Description | | -------------- | --------- | -------- | ------------------------------------ | | adCreativeId | string | Yes | Ad creative identifier | | adFullscreen | boolean | No | Fullscreen flag | | adMuted | boolean | No | Muted flag | | adWatched | number | No | Watched quartile (e.g. 0/25/50/75/100) | | adSkipped | boolean | No | Skipped flag | | adTitle | string | No | Ad title |

Content Events

A standard log enriched with content metadata, sent via sendContentEvent() — for events tied to a specific content item, such as channel zap and playback. The event kind is identified by tag (not targetIndex); the calling app sets the tag (e.g. "ZappLog", "PlaybackEvent"). Always sent at Info level and not subject to the logLevel filter.

logger.sendContentEvent({
  message: "Channel zap",
  tag: "ZappLog",
  contentId: 12345,
  contentTitle: "News at 9",
  contentKind: "live",
  valueInt1: 350, // e.g. zap time in ms
})

Accepts all LogEntry fields (message, tag, type, valueInt1valueInt4, valueStr1valueStr4) plus the following content fields:

| Field | Type | Required | | ---------------------- | --------- | -------- | | contentId | number | Yes | | contentTitle | string | No | | contentKind | string | No | | contentAssetType | string | No | | contentAudioOnly | boolean | No | | contentOriginalTitle | string | No | | deliveryMethod | string | No | | partnerName | string | No | | url | string | No | | epgId | number | No | | epgTitle | string | No | | seriesTitle | string | No | | seriesOriginalTitle | string | No | | seriesSeason | number | No | | seriesEpisode | number | No |

Push Notifications

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

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.

raw(level, entry)

Sends a standard log at the given LogLevel, bypassing the logLevel filter. Accepts the same LogEntry as the level methods.

sendContentEvent(entry)

Sends a content event (e.g. zap or playback) — a standard log enriched with content metadata, identified by entry.tag. Always sent at Info level and not filtered by logLevel.

startWatchSession()

Mints a fresh watch_session id, stamped onto subsequent sendWatchingActivity() payloads until the next call. Call once per viewing (e.g. on play/restart). Watching logs before the first call omit watch_session; ad and standard logs are unaffected.

configure(partial)

Merges additional options into the current configuration. Each field is validated independently: valid fields are applied, while invalid ones are skipped and reported via console.warn. It never throws, so one bad field can't discard the valid fields in the same call. Can be called multiple times — each call merges on top of the previous state, so you can progressively provide fields as they become available.

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

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

// A bad field is skipped + warned; the valid one is still applied
logger.configure({ installation: "abc-123", uuid: 123 as any }) // uuid ignored, installation set

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 | number | 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 | number | No | No | — | | subscriberId | number | No | No | — |

Behavior before configuration is complete

Send methods may be called before all "Required at send" fields are set. What happens depends on the log:

  • Standard logs below Error (debug, info, warn, and raw() at those levels) and content events (sendContentEvent) are deferred — held in a bounded in-memory buffer (capped at maxBufferSize) and flushed in order, each with its original timestamp, as soon as configure() supplies the last required field.
  • Error and Critical (error, critical, or raw() at those levels) are sent immediately once collectorUrl is known, without waiting for the rest of the config — so a failure during startup is still reported. Any required field not yet set is simply omitted from that payload.
  • If collectorUrl itself is not set, everything is deferred, including Error/Critical — there is nowhere to send.
  • Watching and ad activity (sendWatchingActivity, sendAdActivityLog) are not deferred: they throw LogValidationError if required fields are missing.

The first deferral logs a one-time console.warn; deferred logs are flushed with log_buffered: 0 (they were never buffered for retry).

Request labeling

Every request appends ?log=<label>&log_type=<group> query params to collectorUrl, so requests are easy to tell apart in the browser's network inspector (which otherwise shows only the shared path). log is the lowercased event message for watching/ad activity (play, pause, ad_break, …) or the level name for everything else (info, warn, error, …). log_type is the category — watch, ad, or log — so a single filter catches a whole sequence (e.g. log_type=watch). Neither affects the request body and the collector can ignore them.

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():

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.

Every payload carries a log_buffered field: 0 for direct sends and 1 for entries released from the retry buffer. Buffered entries additionally carry log_buffered_datetime, the timestamp of the (re)send attempt that released them (format: "YYYY-MM-DD HH:mm:ss.SSSSSS <timezone>"). On repeated retries the datetime is updated each time, so the value on the delivered payload reflects the final attempt. Direct sends omit log_buffered_datetime entirely — the gap between log_buffered_datetime and log_datetime identifies the retry wait time downstream.

Error Handling

The library throws LogValidationError when the constructor or a send method receives invalid values. It extends Error and includes:

  • field — the name of the invalid field
  • value — the offending value
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
  }
}