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

@enclosurejs/logging

v1.1.0

Published

Structured logging with pluggable transports for Enclosure apps

Readme

@enclosurejs/logging — Structured logging with pluggable transports

[!IMPORTANT] One module — console, file, widget, and custom transports. Level filtering, child loggers with merged context, debounced file output with rotation, in-app log panel. Zero external dependencies.

The Problem

Every desktop/web app needs logging but rolls its own: inconsistent formats, no level filtering, no structured context, no file rotation, no way to show logs in the app itself. Switching platforms means rewriting transport logic.

@enclosurejs/logging solves this with createLoggingModule() — a DI module that auto-wires transports from capabilities (console, file, widget), filters by level using a single integer comparison, and provides Logger with child() for hierarchical context. File transport handles buffered writes with size-based rotation. Widget transport renders a live log panel in the app.

Architecture

┌──────────────────────────────────────────────────────────────────┐
│                        createLoggingModule()                     │
│                                                                  │
│   options.console ≠ false  → ConsoleTransport (pretty | json)    │
│   options.file + FileSystemToken  → FileTransport (buffered)     │
│   options.widget + WidgetRegistryToken → WidgetTransport (DOM)   │
│   options.transports → custom transports appended                │
│                                                                  │
│                      ┌──────────────┐                            │
│                      │  RootLogger  │                            │
│                      │  level gate  │                            │
│                      │  transports  │                            │
│                      └──────┬───────┘                            │
│                             │                                    │
│               ┌─────────────┼─────────────┐                     │
│               ▼             ▼             ▼                      │
│          ChildLogger   ChildLogger   ChildLogger                 │
│          {module:'auth'} {module:'db'}  {session:'x'}            │
│                                                                  │
│   All children share root's transports + level gate.             │
│   Context merges: root < child < call-site.                      │
└──────────────────────────────────────────────────────────────────┘

Level gate: LOG_LEVEL_VALUE[entry.level] >= LOG_LEVEL_VALUE[configured] — single integer comparison, zero allocation when suppressed.

Dependency rule: @enclosurejs/core (peer). No external runtime dependencies.

Quick Start

1. Install

pnpm add @enclosurejs/logging

2. Module usage

import { createApp } from '@enclosurejs/core';
import { createLoggingModule, LoggerToken } from '@enclosurejs/logging';

const app = createApp({
    modules: [
        createLoggingModule({
            level: 'debug',
            context: { app: 'my-app' },
            file: true,
            widget: true,
        }),
    ],
});

3. Using the logger

const logger = ctx.use(LoggerToken);

logger.info('server started', { port: 3000 });
logger.warn('deprecated API called');
logger.error('request failed', new Error('timeout'), { url: '/api/data' });

const child = logger.child({ module: 'auth' });
child.info('user logged in', { userId: 42 });
// context: { app: 'my-app', module: 'auth', userId: 42 }

4. Custom transports

import { createCallbackTransport } from '@enclosurejs/logging';

const sentry = createCallbackTransport('sentry', (entry) => {
    if (entry.level === 'error' || entry.level === 'fatal') {
        captureException(entry.error);
    }
});

createLoggingModule({ transports: [sentry] });

How It Works

Log Levels

Seven levels, ordered by severity:

| Level | Value | Use case | | -------- | ----- | ------------------------------------ | | trace | 0 | Detailed debugging, hot paths | | debug | 1 | Development diagnostics | | info | 2 | Normal operational events | | warn | 3 | Recoverable issues | | error | 4 | Failed operations | | fatal | 5 | Unrecoverable — app should shut down | | silent | 6 | Gate-only: suppresses all output |

Default level: 'debug' in dev (NODE_ENV !== 'production'), 'info' in production.

Console Transport

Human-readable (pretty) or machine-readable (json) output. Pretty format: [HH:MM:SS.mmm] INF message {context}. JSON format: {"level":"info","ts":...,"msg":"...","ctx":{...}}. Maps levels to console.log/warn/error.

File Transport

Buffered JSON-lines output with size-based rotation. Entries are batched in memory and flushed on an interval (default 1s) or immediately on error/fatal for crash safety. Rotation: app.logapp.1.logapp.2.log → ... → oldest deleted. Requires FileSystemToken capability.

Widget Transport

In-app log panel via WidgetRegistry. Circular buffer of last N entries, rendered as a scrollable DOM panel with timestamps, level badges, and a level filter dropdown. Requires WidgetRegistryToken capability.

Callback Transport

Wraps a user function as a transport. Useful for forwarding to Sentry, analytics, or custom sinks.

Child Loggers

logger.child({ module: 'auth' }) creates a child that inherits the parent's transports and merges context. Creating a child is cheap (one object spread). Context priority: root < child < call-site. setLevel() on a child changes the root level (shared gate).

API

Exports (@enclosurejs/logging)

| Export | Kind | Purpose | | ------------------------- | -------- | ---------------------------------------------------- | | createLoggingModule | function | Module factory — auto-wires transports from DI | | LoggerToken | token | DI token to resolve the logger | | RootLogger | class | Logger implementation (for advanced use / testing) | | createConsoleTransport | function | Console transport factory | | createFileTransport | function | File transport factory (requires FileSystem) | | createCallbackTransport | function | Callback transport factory | | createWidgetTransport | function | Widget transport factory (requires WidgetRegistry) | | LOG_LEVEL_VALUE | const | Numeric priority map for level comparison | | LoggingOptions | type | Config for createLoggingModule | | RootLoggerOptions | type | Config for RootLogger constructor | | ConsoleTransportOptions | type | Console transport config | | ConsoleFormat | type | 'pretty' \| 'json' | | FileTransportOptions | type | File transport config | | WidgetTransportOptions | type | Widget transport config | | LogLevel | type | All 7 levels including 'silent' | | ActiveLogLevel | type | 6 levels excluding 'silent' | | LogEntry | type | Immutable structured log record | | Logger | type | Logger interface | | LogTransport | type | Transport interface (write/flush/dispose) |

LoggingOptions

| Field | Type | Required | Description | | ------------ | ----------------------------------- | -------- | --------------------------------------------------------------- | | level | LogLevel | No | Minimum log level. Default: 'debug' (dev) / 'info' (prod) | | transports | LogTransport[] | No | Custom transports appended after auto-configured ones | | console | ConsoleTransportOptions \| false | No | Console config, or false to disable. Default: enabled, pretty | | file | FileTransportOptions \| boolean | No | File config, true for defaults, false to disable | | widget | WidgetTransportOptions \| boolean | No | Widget config, true for defaults, false to disable | | context | Record<string, unknown> | No | Root context fields added to every log entry |

FileTransportOptions

| Field | Type | Default | Description | | --------------- | -------- | ----------------- | ------------------------------------- | | dir | string | 'logs' | Directory for log files | | filename | string | 'app.log' | Current log filename | | maxSize | number | 5 * 1024 * 1024 | Max file size before rotation (bytes) | | maxFiles | number | 5 | Max rotated files kept | | flushInterval | number | 1000 | Auto-flush interval (ms) | | bufferSize | number | 100 | Entries before forced flush |

Configuration

No config files. createLoggingModule() accepts all options inline:

// Minimal — console transport with pretty format, auto level
createLoggingModule();

// Production — JSON format, file output, error-only console
createLoggingModule({
    level: 'warn',
    console: { format: 'json' },
    file: { dir: 'data/logs', maxSize: 10 * 1024 * 1024 },
});

// Development — all transports, debug level
createLoggingModule({
    level: 'debug',
    file: true,
    widget: true,
    context: { env: 'dev' },
});

Types Exported

| Type | Used by | | ------------------------- | ----------------------------------------- | | LogLevel | Level configuration, filtering | | ActiveLogLevel | Log entry level field (excludes 'silent') | | LogEntry | Transport implementations, log consumers | | Logger | Any code using the logger via DI | | LogTransport | Custom transport implementations | | LoggingOptions | Module factory configuration | | RootLoggerOptions | Direct RootLogger construction | | ConsoleTransportOptions | Console transport configuration | | ConsoleFormat | Console output format selection | | FileTransportOptions | File transport configuration | | WidgetTransportOptions | Widget transport configuration |

Safety

Level Gate Safety

  • Level check is a single integer comparison — zero allocation when suppressed
  • silent level suppresses all output including fatal
  • setLevel() changes filtering at runtime without restarting

Transport Safety

  • File transport catches I/O errors and re-prepends buffered lines — no data loss on transient failures
  • File transport flushes immediately on error/fatal — crash safety
  • Widget transport uses a circular buffer — bounded memory regardless of log volume
  • All transports are flushed and disposed on module shutdown

Context Safety

  • Base context is frozen at construction — immutable shared state
  • Child context is frozen — no mutation after creation
  • Context merges create new objects — no shared references between loggers

DI Safety

  • File transport only created when FileSystemToken is available
  • Widget transport only created when WidgetRegistryToken is available
  • Missing capabilities are silently skipped — no crash on capability absence

Benchmarks

Not applicable. Logging is I/O-bound with a single integer comparison as the hot path. The level gate avoids any allocation when a message is suppressed. File transport is explicitly buffered and debounced. There is no CPU-intensive hot path to benchmark.

Bundle Size

| Output | File | Size | | ------------ | ------------ | -------- | | Runtime (JS) | index.js | 14.44 KB | | Types (DTS) | index.d.ts | 6.00 KB | | Total | | 20.44 KB |

Single entrypoint — all transports bundled. Zero runtime dependencies.

Quality

| Metric | Value | | ---------------------- | ------------------------------------------------------------------ | | Unit tests | 66 (all pass) | | Test files | 6 (logger, console, file, callback, widget, module) | | Source files | 8 (index.ts, types.ts, logger.ts, module.ts, 4 transports) | | External dependencies | 0 | | Peer dependencies | @enclosurejs/core | | Coverage (logging/src) | statements 95.97%, branches 91.93%, functions 93.54%, lines 95.97% | | Coverage (transports) | statements 93.83%, branches 87.35%, functions 100%, lines 93.83% |

Quality Layers

Layer 1: STATIC ANALYSIS (every commit)
  tsc --noEmit        strict mode, zero errors
  eslint              ESLint 9 flat config, zero warnings
  prettier --check    formatting

Layer 2: UNIT TESTS (every commit)
  66 tests            logger, 4 transports, DI module integration
  v8 coverage         stmts 96%, branches 92%, funcs 94%, lines 96%

Layer 3: BENCHMARKS
  N/A                 I/O-bound, integer level gate — no CPU hot path

Layer 4: PACKAGE HEALTH
  0 external deps     pure TypeScript + @enclosurejs/core
  tsup build          ESM + DTS output, single entrypoint

File Structure

packages/logging/
├── src/
│   ├── index.ts                Barrel: all public exports
│   ├── types.ts                LogLevel, LogEntry, Logger, LogTransport, LOG_LEVEL_VALUE
│   ├── logger.ts               RootLogger + ChildLogger — level gate, dispatch, context merge
│   ├── module.ts               createLoggingModule(), LoggerToken — auto-wires transports from DI
│   ├── transports/
│   │   ├── console.ts          Pretty or JSON console output
│   │   ├── file.ts             Buffered JSON-lines with size-based rotation
│   │   ├── callback.ts         User function wrapper
│   │   └── widget.ts           In-app log panel via WidgetRegistry
│   └── __tests__/
│       ├── logger.test.ts      23 tests — root + child loggers, level filtering, context
│       ├── console.test.ts     11 tests — pretty/json format, level→method mapping
│       ├── file.test.ts        11 tests — buffering, rotation, crash flush, JSON-lines
│       ├── callback.test.ts     3 tests — name, handler delegation
│       ├── widget.test.ts       7 tests — registration, buffer, mount, dispose
│       └── module.test.ts      11 tests — DI, capability gating, custom transports
├── package.json
├── tsconfig.json
└── tsup.config.ts

License

MIT