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

@orionaisystems/betterlogs

v0.12.0

Published

A durable, context-aware logger for Node.js and browser TypeScript projects.

Readme

@orionaisystems/betterlogs

@orionaisystems/betterlogs is a reusable logging package for TypeScript projects that want elegant console output locally and stronger delivery guarantees in production. It keeps the default developer experience clean while supporting scoped child loggers, structured metadata, redaction, async context propagation, durable batching, durable spool inspection, pluggable transports, broker helpers, health-aware delivery, exporter-friendly diagnostics, diagnostics endpoint adapters, generic production presets, and framework adapters.

Installation

npm install @orionaisystems/betterlogs

For browser-focused usage, import the dedicated subpath:

import { createBrowserLogger } from "@orionaisystems/betterlogs/browser";

Quick Start

import {
  createAsyncContextBindingsProvider,
  createDefaultRedactionRules,
  createLogger,
  createPartialKeyRedactionRule,
  runWithLogContext
} from "@orionaisystems/betterlogs";

const log = createLogger({
  scope: "api",
  minLevel: "debug",
  bindingsProvider: createAsyncContextBindingsProvider(),
  redact: [
    ...createDefaultRedactionRules(),
    createPartialKeyRedactionRule(["email"], {
      keepStart: 2,
      keepEnd: 10
    })
  ]
});

await runWithLogContext(
  {
    requestId: "req_123",
    correlationId: "corr_987",
    context: {
      route: "/users",
      tenantId: "team_42"
    }
  },
  async () => {
    const requestLog = log.child("users");
    const timer = requestLog.time("User created", {
      id: "u_123"
    });

    timer.finish({
      level: "success",
      meta: {
        email: "[email protected]",
        password: "demo-password-value"
      }
    });

    await requestLog.flush();
  }
);

Example output:

2026-03-30 11:45:22 SUCCESS [api:users] [req:req_123] [corr:corr_987] User created
{
  "meta": {
    "durationMs": 18,
    "email": "us********ple.com",
    "id": "u_123",
    "password": "[REDACTED]"
  }
}

Why BetterLogs

  • clean human-readable pretty output by default
  • structured JSON formatting and flattening for ingestion pipelines and MCP-oriented consumers
  • async-aware logger bindings for request IDs, correlation IDs, and shared context
  • durable batching with spool-file persistence and acknowledgement-aware retries
  • CLI and programmatic inspection for durable spool, rotated log, and archive JSONL files
  • built-in buffering, file output, file rotation, archival retention, HTTP delivery, and queue helpers
  • transport retry, health tracking, health transition hooks, circuit breaker wrapping, Prometheus-friendly diagnostics, and explicit flush() support
  • framework adapters for Express-style, Fastify-style, Koa-style, and fetch-style runtimes
  • redaction helpers, OpenTelemetry bridge utilities, and test-friendly memory transports

API Overview

createLogger(options?)

Creates a logger instance.

import { createLogger } from "@orionaisystems/betterlogs";

const log = createLogger();

Logger Methods

log.trace(message, meta?);
log.debug(message, meta?);
log.info(message, meta?);
log.success(message, meta?);
log.warn(message, meta?);
log.error(message, meta?);
log.fatal(message, meta?);
log.child(scope);
log.withContext(context);
log.withRequestId(requestId);
log.withCorrelationId(correlationId);
log.withBindings(bindings);
const timer = log.time(message, meta?);
await log.flush();

flush() waits for async hooks and transports, then asks flush-capable transports to drain buffered work.

Logger Options

type LoggerOptions = {
  scope?: string;
  minLevel?: LogLevel;
  timestamps?: boolean;
  colors?: boolean;
  prettyPrintObjects?: boolean;
  showStackTrace?: boolean;
  format?: "pretty" | "json" | "browser";
  formatter?: LogFormatter;
  transports?: LogTransport | LogTransport[];
  hooks?: LogHook | LogHook[];
  sample?: LogSampler | LogSampler[];
  serializers?: LogSerializer | LogSerializer[];
  redact?: LogRedactionRule | LogRedactionRule[];
  context?: Record<string, unknown>;
  requestId?: string;
  correlationId?: string;
  bindingsProvider?: LoggerBindingsProvider;
};

| Option | Type | Default | Description | | --- | --- | --- | --- | | scope | string | undefined | Adds a scope block like [api] to every entry. | | minLevel | LogLevel | "info" | Filters out logs below the configured severity. | | timestamps | boolean | true | Prints a local timestamp at the start of each entry. | | colors | boolean | true | Enables colored pretty output for the main Node entry point. | | prettyPrintObjects | boolean | true | Uses multi-line formatting for structured details. | | showStackTrace | boolean | true | Includes stacks when logging Error objects. | | format | "pretty" \| "json" \| "browser" | "pretty" | Chooses the default formatter used by the built-in console transport. | | formatter | LogFormatter | undefined | Overrides the default formatter for the built-in console transport. | | transports | LogTransport \| LogTransport[] | built-in console transport | Supplies custom transports. Pass [] to disable default output. | | hooks | LogHook \| LogHook[] | [] | Observes each LogRecord before transports run. Hooks may be async. | | sample | LogSampler \| LogSampler[] | [] | Drops records before hooks and transports run. Useful for cost control, high-volume debug logs, and burst protection. | | serializers | LogSerializer \| LogSerializer[] | [] | Custom serializers for domain objects in metadata or context. | | redact | LogRedactionRule \| LogRedactionRule[] | [] | Redaction rules for sensitive keys or exact paths. Supports full replacement and partial masking. | | context | Record<string, unknown> | {} | Attaches reusable structured context to every entry. | | requestId | string | undefined | Adds a request ID tag and top-level record field. | | correlationId | string | undefined | Adds a correlation ID tag and top-level record field. | | bindingsProvider | LoggerBindingsProvider | undefined | Pulls bindings from async context or another ambient source for every record. |

Async Context Propagation

Use the built-in async context store when you want request and correlation IDs to flow through asynchronous work automatically.

import {
  createAsyncContextBindingsProvider,
  createLogger,
  runWithLogContext
} from "@orionaisystems/betterlogs";

const log = createLogger({
  scope: "worker",
  bindingsProvider: createAsyncContextBindingsProvider()
});

await runWithLogContext(
  {
    requestId: "req_123",
    correlationId: "corr_987",
    context: {
      jobId: "job_42"
    }
  },
  async () => {
    log.info("Processing started");
    await Promise.resolve();
    log.success("Processing finished");
  }
);

If you need more control, BetterLogs also exports createLogContextStore(), bindLogContext(), enterLogContext(), and getLogContext().

Request Timing Helpers

Use time() when you want a structured duration without hand-rolling start and end timestamps.

const timer = log.time("Tool call processed", {
  tool: "search"
});

try {
  timer.finish({
    level: "success",
    meta: {
      tokenCount: 1_234
    }
  });
} catch (error) {
  timer.fail(error as Error, {
    message: "Tool call failed"
  });
}

Redaction Helpers

Use key or path based rules to protect secrets and PII before records reach hooks, transports, or formatters.

import {
  createDefaultRedactionRules,
  createKeyRedactionRule,
  createPartialKeyRedactionRule,
  createPathRedactionRule,
  createLogger
} from "@orionaisystems/betterlogs";

const log = createLogger({
  redact: [
    ...createDefaultRedactionRules(),
    createKeyRedactionRule(["authorization", "cookie"]),
    createPartialKeyRedactionRule(["email"], {
      keepStart: 2,
      keepEnd: 10
    }),
    createPathRedactionRule("meta.user.ssn")
  ]
});

Durable Batching With Acknowledgement-Aware Retries

Use durable batching when you want a transport to persist records locally before attempting delivery.

import {
  createDurableBatchingTransport,
  createKafkaTransport,
  createLogger
} from "@orionaisystems/betterlogs";

const kafkaTransport = createKafkaTransport({
  producer: {
    async send(input) {
      void input;
    }
  },
  topic: "app.logs",
  key: (record) => record.requestId
});

const durableTransport = createDurableBatchingTransport({
  filePath: "./.betterlogs/spool.jsonl",
  maxBatchSize: 50,
  retry: {
    retries: 5,
    baseDelayMs: 250,
    maxDelayMs: 2_000
  },
  async sink(records) {
    for (const record of records) {
      await kafkaTransport.write(record);
    }

    return {
      acknowledgedCount: records.length
    };
  }
});

const log = createLogger({
  scope: "pipeline",
  transports: [durableTransport]
});

If the sink only acknowledges part of a batch, BetterLogs keeps the remaining records in the spool file for the next flush cycle.

Durable Spool Inspection CLI

BetterLogs ships a small Node CLI for inspecting JSONL spool files, rotated log files, and archive directories without writing a one-off script.

npx @orionaisystems/betterlogs inspect ./.betterlogs/spool.jsonl
npx @orionaisystems/betterlogs inspect ./logs/archive --limit 5 --json

The human output summarizes total records, invalid JSONL lines, timestamp windows, levels, scopes, request IDs, and recent records per file. --json prints the same inspection result as structured data for operational scripts.

The same inspection logic is available from the root package when an application wants to build its own operator surface:

import { inspectDurableLogPaths } from "@orionaisystems/betterlogs";

const inspection = await inspectDurableLogPaths(["./.betterlogs/spool.jsonl"], {
  limit: 20
});

console.log(inspection.totalRecordCount);

Transport Health And Circuit Breakers

Wrap transports when you want delivery diagnostics or a guardrail around unstable downstream systems.

import {
  createCircuitBreakerTransport,
  createHealthTrackedTransport,
  createHttpTransport,
  createLogger,
  createTransportDiagnosticsSnapshot,
  formatTransportDiagnosticsAsPrometheus,
  getTransportHealth
} from "@orionaisystems/betterlogs";

const delivery = createCircuitBreakerTransport({
  name: "log-ingest",
  transport: createHealthTrackedTransport({
    name: "log-ingest",
    transport: createHttpTransport({
      url: "https://logs.internal.example/ingest"
    })
  }),
  failureThreshold: 3,
  resetTimeoutMs: 5_000,
  onStateChange(transition) {
    console.warn("Log delivery state changed", {
      from: transition.previousState,
      to: transition.currentState,
      reason: transition.reason
    });
  }
});

const log = createLogger({ transports: [delivery] });

log.info("Queued for delivery");
await log.flush();

const health = getTransportHealth(delivery);
const diagnostics = createTransportDiagnosticsSnapshot([delivery], {
  labels: {
    service: "api"
  }
});

console.log(formatTransportDiagnosticsAsPrometheus(diagnostics));

Health-aware transports expose state such as healthy, degraded, unhealthy, open, and half-open alongside counters and timestamps.

createTransportDiagnosticsSnapshot() turns one or more health-aware transports into JSON-safe diagnostics with ISO timestamps, Unix millisecond fields, success and failure ratios, writability, open-circuit timing, aggregate status, and labels. formatTransportDiagnosticsAsPrometheus() renders the same snapshot as text metrics for lightweight scrape endpoints without adding a metrics SDK dependency. The Prometheus formatter includes aggregate snapshot metrics plus per-transport metrics.

Diagnostics Endpoint Adapters

Use the endpoint helpers when a service wants to expose JSON diagnostics or Prometheus metrics without adding a framework package to BetterLogs.

import {
  createExpressTransportDiagnosticsHandler,
  createFetchTransportDiagnosticsHandler
} from "@orionaisystems/betterlogs";

export const fetchMetrics = createFetchTransportDiagnosticsHandler([delivery], {
  format: "prometheus",
  statusCode: "from-health"
});

app.get(
  "/internal/logging/diagnostics",
  createExpressTransportDiagnosticsHandler([delivery], {
    format: "json",
    statusCode: "from-health"
  })
);

The same adapter family includes createFastifyTransportDiagnosticsHandler() and createKoaTransportDiagnosticsMiddleware(). By default, diagnostics endpoints return HTTP 200 so scrapers can collect degraded-state payloads; set statusCode: "from-health" when an unhealthy logging pipeline should return 503.

Production Logging Preset

Services can use the production preset when they want the common BetterLogs stack without redoing the transport wiring in every gateway or worker.

import {
  createFetchTransportDiagnosticsHandler,
  createProductionLoggingPreset,
  formatTransportDiagnosticsAsPrometheus
} from "@orionaisystems/betterlogs";

const logging = createProductionLoggingPreset({
  scope: "api-gateway",
  serviceName: "api-gateway",
  serviceVersion: process.env.APP_VERSION,
  environment: process.env.NODE_ENV,
  http: {
    url: process.env.LOG_INGEST_URL!,
    headers: {
      "x-log-source": "api-gateway"
    }
  },
  durable: {
    filePath: "./.betterlogs/api-gateway-spool.jsonl",
    maxBatchSize: 25,
    flushIntervalMs: 1_000
  },
  circuitBreaker: {
    onStateChange(transition) {
      console.warn("Log delivery state changed", {
        currentState: transition.currentState,
        previousState: transition.previousState,
        reason: transition.reason,
        transport: transition.name
      });
    }
  },
  debugBurstLimit: {
    maxRecords: 100,
    intervalMs: 60_000
  }
});

logging.logger.info("Task accepted", {
  taskId: "task_001"
});

await logging.flush();

const metrics = formatTransportDiagnosticsAsPrometheus(logging.getDiagnostics());
export const metricsHandler = createFetchTransportDiagnosticsHandler(
  logging.healthTransports,
  {
    format: "prometheus",
    statusCode: "from-health"
  }
);

The preset configures JSON output, async request/correlation context, default redaction, optional durable HTTP delivery, health tracking, circuit breaking, debug burst control, and reusable delivery diagnostics. Pass console: false when the service should only emit through the configured production transport.

Consumer Wrapper Pattern

Public packages and applications should usually expose their own logging boundary instead of importing BetterLogs everywhere. Put app-specific scopes, labels, environment variable names, diagnostics routes, and additional redaction rules in that local wrapper. Keep BetterLogs as the generic transport, formatting, context, diagnostics, and redaction foundation underneath.

Queue And Broker Helpers

BetterLogs ships lightweight helpers for common broker-style destinations without taking hard dependencies on their SDKs.

Generic Queue Transport

import { createLogger, createQueueTransport } from "@orionaisystems/betterlogs";

const queueLog = createLogger({
  transports: [
    createQueueTransport({
      async send(payload) {
        void payload;
      }
    })
  ]
});

SQS Helper

import { createLogger, createSqsTransport } from "@orionaisystems/betterlogs";

const sqsLog = createLogger({
  transports: [
    createSqsTransport({
      client: {
        async sendMessage(input) {
          void input;
        }
      },
      queueUrl: "https://sqs.us-east-1.amazonaws.com/123456789012/app-logs",
      messageGroupId: (record) => record.requestId ?? "default"
    })
  ]
});

Kafka Helper

import { createKafkaTransport, createLogger } from "@orionaisystems/betterlogs";

const kafkaLog = createLogger({
  transports: [
    createKafkaTransport({
      producer: {
        async send(input) {
          void input;
        }
      },
      topic: "app.logs",
      key: (record) => record.requestId
    })
  ]
});

BullMQ Helper

import { createBullMqTransport, createLogger } from "@orionaisystems/betterlogs";

const workerLog = createLogger({
  transports: [
    createBullMqTransport({
      queue: {
        async add(name, data, opts) {
          void name;
          void data;
          void opts;
        }
      },
      mode: "record",
      name: (record) => `${record.level}.log`
    })
  ]
});

JSON Flattening

When logs are headed to ingestion pipelines, queue consumers, or an MCP layer, flattened JSON can be easier to query and route.

import { createJsonFormatter } from "@orionaisystems/betterlogs";

const formatter = createJsonFormatter({
  flatten: {
    enabled: true,
    include: ["context", "meta", "error"],
    delimiter: "."
  },
  prettyPrintObjects: false
});

This converts nested sections like context.user.id and meta.durationMs into top-level JSON keys while keeping the rest of the record shape stable.

Buffered Transports And Flush Support

import { createBufferedTransport, createLogger } from "@orionaisystems/betterlogs";

const records: string[] = [];

const transport = createBufferedTransport({
  maxBufferSize: 10,
  flushIntervalMs: 1_000,
  sink: async (batch) => {
    records.push(...batch.map((record) => record.message));
  }
});

const log = createLogger({ transports: [transport] });

log.info("queued");
await log.flush();

Buffered transports are useful when you want to batch network, file, or analytics writes without making the logging callsite async.

Sampling And Burst Rate Limiting

Use samplers when you want to reduce log volume before hooks, formatters, or transports do any downstream work. Samplers run after level filtering, record creation, serialization, and redaction, so custom decisions can inspect the structured LogRecord without exposing raw sensitive input.

import {
  createBurstRateLimitSampler,
  createLogger,
  createPercentageSampler
} from "@orionaisystems/betterlogs";

const log = createLogger({
  scope: "worker",
  minLevel: "debug",
  sample: [
    createPercentageSampler({
      rate: 0.1,
      levels: ["debug", "trace"]
    }),
    createBurstRateLimitSampler({
      maxRecords: 100,
      intervalMs: 60_000,
      levels: ["debug", "trace", "info"]
    })
  ]
});

Percentage sampling is useful for noisy low-severity records. Burst rate limiting is useful when a hot loop, retry storm, or repeated integration failure could otherwise multiply transport cost. If multiple samplers are configured, a record must pass every sampler before hooks and transports receive it.

Retry And Backoff Policies

Wrap a transport when you want retry behavior without baking retry logic into every destination.

import {
  createHttpTransport,
  createRetryingTransport,
  createLogger
} from "@orionaisystems/betterlogs";

const transport = createRetryingTransport({
  transport: createHttpTransport({
    url: "https://logs.internal.example/ingest"
  }),
  retry: {
    retries: 5,
    baseDelayMs: 250,
    maxDelayMs: 2_000
  }
});

const log = createLogger({ transports: [transport] });

File Transport, Rotation, And Retention

import { createFileTransport, createLogger } from "@orionaisystems/betterlogs";

const fileLog = createLogger({
  scope: "audit",
  format: "json",
  transports: [
    createFileTransport({
      filePath: "./logs/audit.log",
      rotate: {
        maxBytes: 1_000_000,
        maxFiles: 5
      },
      retention: {
        maxAgeMs: 7 * 24 * 60 * 60 * 1_000,
        archiveDirectory: "./logs/archive"
      }
    })
  ]
});

fileLog.info("Audit event recorded", {
  actorId: "u_123",
  event: "user.updated"
});

await fileLog.flush();

The file transport writes asynchronously, rotates files once a size threshold is exceeded, and can archive or prune old log segments on a retention schedule.

Formatter Variants

Pretty Formatter

The default formatter for local development and service logs.

import { createPrettyFormatter } from "@orionaisystems/betterlogs";

JSON Formatter

Best for pipelines, ingestion, or machine processing.

import { createJsonFormatter } from "@orionaisystems/betterlogs";

Browser Formatter And Browser Entry

For browser-targeted imports, use the dedicated subpath:

import {
  createBrowserConsoleTransport,
  createBrowserLogger
} from "@orionaisystems/betterlogs/browser";

const log = createBrowserLogger({ scope: "ui" });
log.info("Mounted application shell");

The browser subpath is built separately from the Node entry point and should remain free of Node.js builtin imports. CI and the publish workflow run a dedicated browser bundle smoke gate before packaging. That gate scans the ESM and CommonJS browser artifacts plus sourcemaps for Node builtin imports and accidental Node-only source inclusion.

Framework Adapters

BetterLogs stays dependency-light, so the framework adapters are opt-in helpers rather than hard integrations.

Express-Style Middleware

import { createExpressLoggingMiddleware, createLogger } from "@orionaisystems/betterlogs";

const baseLogger = createLogger({ scope: "http" });

export const requestLogger = createExpressLoggingMiddleware(baseLogger, {
  includeHeaders: ["user-agent"],
  successLevel: "info"
});

Fastify-Style Hooks

import { createFastifyLoggingHooks, createLogger } from "@orionaisystems/betterlogs";

const baseLogger = createLogger({ scope: "http" });

export const hooks = createFastifyLoggingHooks(baseLogger, {
  includeHeaders: ["user-agent"]
});

Koa-Style Middleware

import { createKoaLoggingMiddleware, createLogger } from "@orionaisystems/betterlogs";

const baseLogger = createLogger({ scope: "http" });

export const koaMiddleware = createKoaLoggingMiddleware(baseLogger, {
  includeHeaders: ["user-agent"]
});

Fetch-Style Request Wrapper

import { createLogger, withFetchRequestLogging } from "@orionaisystems/betterlogs";

const baseLogger = createLogger({ scope: "http" });

export async function handle(request: Request) {
  return withFetchRequestLogging(
    baseLogger,
    request,
    async (logger) => {
      logger.info("Handling request");
      return new Response("ok", { status: 200 });
    }
  );
}

includeHeaders works with both plain object header maps and Headers-style request objects, which keeps Edge and Fetch runtimes aligned with the Node adapters.

OpenTelemetry Bridge Utilities

@orionaisystems/betterlogs stays dependency-light, so the OpenTelemetry helpers work through small compatible interfaces rather than taking a hard dependency on the OTel SDK.

import {
  createLogger,
  createOpenTelemetryLogHook,
  createOpenTelemetrySpanHook
} from "@orionaisystems/betterlogs";

const emitted: unknown[] = [];

const log = createLogger({
  hooks: [
    createOpenTelemetryLogHook({
      emit(record) {
        emitted.push(record);
      }
    }),
    createOpenTelemetrySpanHook({
      addEvent(name, attributes) {
        emitted.push({ name, attributes });
      }
    })
  ]
});

Testing Utilities And Snapshots

import {
  createTestLogger,
  snapshotRecords
} from "@orionaisystems/betterlogs";

const { logger, transport } = createTestLogger({
  timestamps: false,
  colors: false
});

logger.info("Captured in tests", { id: "u_123" });
await logger.flush();

const snapshot = snapshotRecords(transport.records);

The memory transport and snapshot helpers make it easy to assert on structured log records in unit tests without stubbing global console methods.

Custom Serializers

Serializers let you normalize domain objects before they reach formatters or transports.

import { createLogger, type LogSerializer } from "@orionaisystems/betterlogs";

class Money {
  constructor(
    public readonly amount: number,
    public readonly currency: string
  ) {}
}

const moneySerializer: LogSerializer<Money> = {
  name: "money",
  test(value): value is Money {
    return value instanceof Money;
  },
  serialize(value) {
    return `${value.currency} ${value.amount.toFixed(2)}`;
  }
};

const log = createLogger({
  serializers: [moneySerializer]
});

log.info("Invoice settled", {
  amount: new Money(129.99, "USD")
});

Error Logging

const log = createLogger({ scope: "worker", showStackTrace: true });

try {
  throw new Error("Queue processing failed");
} catch (error) {
  log.error("Unhandled exception", error);
}

Errors are routed to console.error by the console transport, formatted clearly, and keep stack traces unless you disable them.

Security

If you discover a vulnerability, please use the reporting guidance in SECURITY.md rather than opening a public issue with exploit details.

Default redaction rules are case-insensitive and cover common credential, cookie, session, API key, token, connection string, and private-key field variants. Production preset users get those defaults unless they explicitly set includeDefaultRedaction: false.

Example Project Files

The repository includes:

  • examples/basic.ts for async context propagation, scoped logging, timing, redaction, and structured output
  • examples/advanced.ts for durable batching, Kafka-style delivery, health reporting, and circuit breaker wrapping
  • examples/server.ts for Express-style, Fastify-style, Koa-style, and fetch-style runtime adapters
  • examples/production-preset.ts for the generic production preset, durable HTTP delivery, and metrics diagnostics

Development Scripts

npm run build
npm run dev
npm run typecheck
npm run typecheck:api
npm run smoke:browser
npm run smoke:exports
npm run smoke:runtime
npm run clean

smoke:browser, smoke:exports, smoke:runtime, and typecheck:api expect dist/ to exist, so run them after npm run build when working locally. The browser smoke gate checks browser bundle artifacts and sourcemaps for Node-only leaks. The API type tests compile small consumer-style imports from the root package and browser subpath to catch accidental public surface regressions. The runtime smoke test exercises the Fetch/Fastify adapter behavior, transport diagnostics endpoints, health transition hooks, Prometheus metric formatting, and the durable inspection CLI against the built package.

Release Flow

BetterLogs now includes GitHub Actions for validation and publishing:

  • CI runs on pushes and pull requests and verifies install, typecheck, build, browser bundle smoke checks, export smoke checks, runtime smoke checks, public API type checks, and publish contents
  • Publish runs when a GitHub release is published, uses npm trusted publishing, runs the same package browser/export/runtime smoke checks, and publishes with provenance
  • prepublishOnly runs typecheck, build, browser bundle smoke checks, export smoke checks, runtime adapter/CLI smoke checks, and public API type checks for local npm publishes

Recommended release flow:

npm version patch # or npm version minor for new public API
git push origin main --follow-tags

Then publish a GitHub release for the new tag from the repository UI to trigger npm publication automatically.

Design Notes

@orionaisystems/betterlogs v0.12.0 keeps the runtime small while separating:

  • record creation and ambient binding resolution
  • redaction and serialization
  • formatter selection and JSON shaping
  • sampling and burst rate limiting
  • transport delivery, batching, retry, health tracking, health transition hooks, and flushing
  • exporter-friendly transport diagnostics, endpoint adapters, and metrics formatting
  • generic production logging presets from lower-level transport primitives
  • hook observation
  • Node and browser entry points
  • runtime-specific adapter helpers
  • CLI/programmatic inspection for durable JSONL spool and archive files
  • browser bundle leak checks, compile-only public API type tests, and runtime adapter/diagnostics smoke tests for the root and browser package exports

That separation makes it practical to add more outputs and integrations later without disturbing the logger API most callers use every day.

Roadmap

Future ideas for the package:

  • schema-driven structured event helpers for shared internal log contracts
  • additional metrics exporter adapters for services that already run a metrics SDK
  • additional production presets for OTLP and vendor-specific delivery patterns
  • consumer adoption guides for application-owned logging boundaries
  • worker-thread and multi-process relay transports

License

MIT