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

@wiolett/gateway-logger

v1.1.2

Published

`@wiolett/gateway-logger` is a Node-first TypeScript SDK for sending application logs into Wiolett Industries Gateway. It handles Gateway ingest tokens, context merging, batching, retries, rate-limit backoff, trace/span identifiers, and graceful shutdown

Readme

Gateway Logging SDK

@wiolett/gateway-logger is a Node-first TypeScript SDK for sending application logs into Wiolett Industries Gateway. It handles Gateway ingest tokens, context merging, batching, retries, rate-limit backoff, trace/span identifiers, and graceful shutdown flushing.

Use it when an external service, worker, script, or backend needs to write structured logs into Gateway without calling the ingest API manually.

Installation

pnpm add @wiolett/gateway-logger
npm install @wiolett/gateway-logger

The package is ESM-only and ships TypeScript declarations. It expects a runtime with fetch; modern Node.js versions provide it globally. For older runtimes, pass a custom fetch implementation in the logger options.

Gateway Setup

  1. Open Gateway.
  2. Create a logging ingest token for the target schema/source.
  3. Store the token as a server-side secret, for example GATEWAY_LOGGING_TOKEN.
  4. Configure the SDK with your Gateway URL and token.

Logging ingest tokens use the gwl_ prefix. Keep them server-side. Do not put them in browser bundles, public config, mobile apps, or any client-controlled code.

Quick Start

import { GatewayLogger } from "@wiolett/gateway-logger";

const logger = new GatewayLogger({
  endpoint: "https://gateway.example.com",
  token: process.env.GATEWAY_LOGGING_TOKEN!,
  service: "billing-api",
  source: "api",
  labels: {
    app: "billing",
    environment: process.env.NODE_ENV ?? "development",
  },
  fields: {
    version: process.env.npm_package_version,
  },
});

logger.info("Service started");

logger.error("Payment capture failed", {
  requestId: "req_123",
  labels: { provider: "stripe" },
  fields: {
    statusCode: 502,
    durationMs: 1834,
  },
});

await logger.flush();
await logger.close();

Configuration

const logger = new GatewayLogger({
  endpoint: "https://gateway.example.com",
  token: process.env.GATEWAY_LOGGING_TOKEN!,
  service: "billing-api",
  source: "api",
  traceId: "trace_123",
  spanId: "span_456",
  requestId: "req_123",
  labels: { app: "billing" },
  fields: { version: "1.4.0" },
  batching: {
    enabled: true,
    maxBatchSize: 100,
    flushIntervalMs: 5000,
    flushDebounceMs: 250,
    maxQueueSize: 10000,
    overflow: "drop-oldest",
  },
  retry: {
    maxAttempts: 5,
    minDelayMs: 500,
    maxDelayMs: 30000,
    jitter: true,
  },
  onError: (error, logs, failure) => {
    console.error("Gateway logging delivery failed", {
      reason: failure?.reason,
      status: failure?.status,
      count: logs?.length ?? 0,
      error,
    });
  },
  onDrop: (log, reason) => {
    console.warn("Gateway log dropped", reason, log.message);
  },
  onFallback: async (logs, failure) => {
    await persistFailedGatewayLogs(logs, failure);
  },
});

Logger Options

| Option | Type | Description | | --- | --- | --- | | endpoint | string | Base Gateway URL, for example https://gateway.example.com. Trailing slashes are removed automatically. | | token | string | Gateway logging ingest token. | | service | string | Logical service name attached to every log from this logger. | | source | string | Source/channel name, such as api, worker, cron, or queue. | | traceId | string | Default trace identifier attached to every log. | | spanId | string | Default span identifier attached to every log. | | requestId | string | Default request/correlation identifier attached to every log. | | labels | Record<string, string \| number \| boolean \| null \| undefined> | Indexed dimensions. Values are normalized to strings; null and undefined values are dropped. | | fields | Record<string, GatewayLogValue \| undefined> | Structured event data. Date values are serialized to ISO strings; undefined values are dropped. | | batching | GatewayBatchingOptions | Queue and flush behavior. | | retry | GatewayRetryOptions | Retry and backoff behavior for transient delivery failures. | | fetch | typeof fetch | Optional fetch implementation for custom runtimes or tests. | | onError | (error, logs?, failure?) => void | Called when delivery fails or a fallback handler throws. Failed logs and failure metadata are included when available. | | onDrop | (log, reason) => void | Called for each dropped log. | | onFallback | (logs, failure) => void \| Promise<void> | Optional final handler for logs that could not be delivered after retries or were rejected permanently. Use this to write logs to disk, another queue, or another transport. |

Writing Logs

The logger provides severity helpers for all Gateway severities:

logger.trace("Cache lookup", { fields: { key: "user:123" } });
logger.debug("Computed quote", { fields: { amount: 42.13 } });
logger.info("Checkout started");
logger.warn("Provider latency is high", { fields: { durationMs: 2900 } });
logger.error("Capture failed", { fields: { statusCode: 502 } });
logger.fatal("Worker cannot start", { fields: { reason: "missing_config" } });

You can also submit a complete event with log:

logger.log({
  timestamp: new Date(),
  severity: "info",
  message: "Invoice generated",
  service: "billing-worker",
  source: "invoice",
  labels: { tenant: "acme" },
  fields: { invoiceId: "inv_123", total: 149.99 },
});

Context Merging

Context is inherited and merged from parent loggers, child loggers, and per-event options.

const root = new GatewayLogger({
  endpoint: "https://gateway.example.com",
  token: process.env.GATEWAY_LOGGING_TOKEN!,
  service: "api",
  labels: { app: "billing", region: "eu" },
});

const payments = root.child({
  service: "billing-payments",
  labels: { module: "payments" },
  fields: { provider: "stripe" },
});

payments.error("Payment capture failed", {
  labels: { region: "us" },
  fields: { statusCode: 502 },
});

The emitted event uses:

  • service: "billing-payments" because later scalar context overrides earlier scalar context.
  • labels: { app: "billing", region: "us", module: "payments" } because labels are merged by key.
  • fields: { provider: "stripe", statusCode: 502 } because fields are merged by key.

Request Scoped Logging

Use withContext to create a temporary derived logger for a request, job, or operation.

await logger.withContext(
  {
    requestId: "req_123",
    labels: { route: "POST /payments" },
  },
  async (requestLogger) => {
    requestLogger.info("Request started");
    requestLogger.info("Request completed", { fields: { durationMs: 148 } });
  }
);

withContext returns the value returned by your callback and supports async callbacks.

Traces And Spans

The SDK can create lightweight trace and span contexts. This does not require a separate tracing backend; it only attaches traceId, spanId, and optional names to Gateway log events.

const trace = logger.createTrace({
  name: "checkout",
  requestId: "req_123",
  labels: { flow: "checkout" },
});

trace.info("Checkout started");

const span = trace.createSpan("stripe.capture");
span.info("Capture requested");
span.error("Capture failed", {
  fields: { statusCode: 502, durationMs: 1834 },
});

await span.end();
await trace.end();

createTrace generates a UUID trace ID when one is not provided. createSpan keeps the trace ID and generates a UUID span ID when one is not provided. Passing a trace or span name stores it in fields.traceName or fields.spanName.

Batching And Delivery

Logs are queued in memory and delivered to Gateway over HTTP:

  • One event is sent to /api/logging/ingest.
  • Multiple events are sent to /api/logging/ingest/batch.
  • Authorization: Bearer <token> is used for every request.
  • Content-Type: application/json is set automatically.

Default batching options:

| Option | Default | Description | | --- | ---: | --- | | enabled | true | Enables queued batching. When set to false, the SDK flushes one event at a time. | | maxBatchSize | 100 | Maximum events sent in one batch. | | flushIntervalMs | 5000 | Periodic flush interval. | | flushDebounceMs | 250 | Delay after enqueue before a flush is scheduled. | | maxQueueSize | 10000 | Maximum in-memory queued events. | | overflow | "drop-oldest" | Queue overflow strategy: "drop-oldest" or "drop-newest". |

Call flush() when you need to wait until currently queued logs are delivered:

logger.info("Job completed");
await logger.flush();

Call close() during shutdown. It stops timers, marks the logger closed, and flushes queued events:

await logger.close();

Events logged after close() are dropped with reason "closed".

Retries And Failures

The SDK retries transient delivery failures:

  • Network errors.
  • HTTP 408.
  • HTTP 425.
  • HTTP 429.
  • HTTP 5xx.

Default retry options:

| Option | Default | Description | | --- | ---: | --- | | maxAttempts | 5 | Total send attempts for a batch. | | minDelayMs | 500 | Initial backoff delay. | | maxDelayMs | 30000 | Maximum backoff delay. | | jitter | true | Adds random jitter to retry delays. |

For 429 responses, the SDK prefers the HTTP Retry-After header. If the header is absent, it also understands Gateway's details.retryAfterSeconds response shape.

Permanent validation or authorization failures are not retried. Those batches are dropped with reason "permanent_failure". Retryable batches that still fail after maxAttempts are dropped with reason "retry_exhausted".

When a batch cannot be delivered, the SDK calls callbacks in this order:

  1. onDrop(log, reason) once for each failed log.
  2. onError(error, logs, failure) with the failed log array and metadata.
  3. onFallback(logs, failure) if configured.

onFallback is intended for recovery paths where logs should not disappear after Gateway delivery fails:

const logger = new GatewayLogger({
  endpoint: "https://gateway.example.com",
  token: process.env.GATEWAY_LOGGING_TOKEN!,
  async onFallback(logs, failure) {
    await backupLogSink.write({
      reason: failure.reason,
      status: failure.status,
      logs,
    });
  },
});

If onFallback throws, the SDK calls onError again with the fallback error.

Drop Reasons

onDrop receives the dropped log and one of these reasons:

| Reason | Meaning | | --- | --- | | queue_full | The in-memory queue reached maxQueueSize. | | closed | Code attempted to log after close(). | | invalid_event | Reserved for invalid event handling. | | permanent_failure | Gateway rejected the batch with a non-retryable response. | | retry_exhausted | Retry attempts were exhausted. |

Example:

const logger = new GatewayLogger({
  endpoint: "https://gateway.example.com",
  token: process.env.GATEWAY_LOGGING_TOKEN!,
  onDrop(log, reason) {
    console.warn(`Dropped Gateway log: ${reason}`, log);
  },
});

Shutdown Hooks

For long-running Node processes, install the shutdown hook so queued logs are flushed on common process shutdown signals:

import { GatewayLoggerHook } from "@wiolett/gateway-logger";

const uninstallHook = logger.hooks.install(GatewayLoggerHook.SHUTDOWN);

logger.hooks.uninstall(GatewayLoggerHook.SHUTDOWN);

// Later, if the logger lifecycle ends before the process:
uninstallHook();
await logger.close();

The shutdown hook listens for SIGINT, SIGTERM, and beforeExit. Hook installation is idempotent: calling install(GatewayLoggerHook.SHUTDOWN) more than once does not register duplicate process listeners. The hook registry is intentionally exposed as logger.hooks so future SDK versions can add more hook types without adding more top-level logger methods.

Framework Examples

Express-style request middleware

app.use(async (req, res, next) => {
  const startedAt = Date.now();

  await logger.withContext(
    {
      requestId: req.headers["x-request-id"]?.toString(),
      labels: {
        method: req.method,
        route: req.path,
      },
    },
    async (requestLogger) => {
      requestLogger.info("Request started");

      res.once("finish", () => {
        requestLogger.info("Request completed", {
          fields: {
            statusCode: res.statusCode,
            durationMs: Date.now() - startedAt,
          },
        });
      });

      next();
    }
  );
});

Worker job

export async function runJob(job: { id: string; tenantId: string }) {
  await logger.withContext(
    {
      requestId: job.id,
      labels: { worker: "invoice-sync", tenant: job.tenantId },
    },
    async (jobLogger) => {
      jobLogger.info("Job started");
      try {
        await syncInvoices(job);
        jobLogger.info("Job completed");
      } catch (error) {
        jobLogger.error("Job failed", {
          fields: { error: error instanceof Error ? error.message : String(error) },
        });
        throw error;
      } finally {
        await jobLogger.flush();
      }
    }
  );
}

Data Modeling Guidance

Use labels for low-cardinality dimensions you want to filter or group by, such as app, environment, region, tenant, module, route, or provider.

Use fields for event-specific structured data, such as durations, status codes, entity IDs, error messages, payload sizes, or version information.

Avoid placing secrets, passwords, API keys, tokens, raw authorization headers, private keys, or credential material in either labels or fields. Gateway can store and search logs, so treat log content as durable operational data.

API Reference

new GatewayLogger(options)

Creates a Gateway logger instance.

import { GatewayLogger } from "@wiolett/gateway-logger";

GatewayLogger class

class GatewayLogger {
  readonly hooks: GatewayLoggerHooks;
  constructor(options: GatewayLoggerOptions);
  trace(message: string, options?: GatewayLogOptions): void;
  debug(message: string, options?: GatewayLogOptions): void;
  info(message: string, options?: GatewayLogOptions): void;
  warn(message: string, options?: GatewayLogOptions): void;
  error(message: string, options?: GatewayLogOptions): void;
  fatal(message: string, options?: GatewayLogOptions): void;
  log(event: GatewayLogEvent): void;
  child(context: GatewayLogContext): GatewayLogger;
  createTrace(context?: GatewayTraceContext): GatewayTraceLogger;
  withContext<T>(context: GatewayLogContext, fn: (logger: GatewayLogger) => T | Promise<T>): Promise<T>;
  flush(): Promise<void>;
  close(): Promise<void>;
}

Runtime Exports

  • GatewayLogger
  • GatewayLoggerHook

Exported Types

The package exports these public types:

  • GatewayBatchingOptions
  • GatewayLoggerMethods
  • GatewayLoggerHooks
  • GatewayLog
  • GatewayLogContext
  • GatewayLogDropReason
  • GatewayLogEvent
  • GatewayLogFailureInfo
  • GatewayLogFailureReason
  • GatewayLoggerOptions
  • GatewayLogOptions
  • GatewayLogSeverity
  • GatewayLogValue
  • GatewayRetryOptions
  • GatewaySpanContext
  • GatewaySpanLogger
  • GatewayTraceContext
  • GatewayTraceLogger

Development

From the repository root:

pnpm --filter @wiolett/gateway-logger test
pnpm --filter @wiolett/gateway-logger lint
pnpm --filter @wiolett/gateway-logger typecheck
pnpm --filter @wiolett/gateway-logger build

The package publishes dist only.