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

@eventuras/logger

v0.8.1

Published

Structured logging with Pino and optional OpenTelemetry integration

Readme

@eventuras/logger

A structured logging library with pluggable transports and optional OpenTelemetry integration. Works in Node.js and browser environments.

Features

  • 🎯 Scoped loggers — Create instances with persistent context and namespace
  • 🔌 Pluggable transports — Use Pino (default), console, or bring your own
  • 🔒 Auto-redaction — Protect sensitive fields in log output
  • 📊 Log levels — Control verbosity per logger or globally
  • 🔍 Correlation IDs — Track requests across services
  • 🌐 OpenTelemetry — Optional integration for any OTel-compatible backend
  • 🌍 Cross-runtime — Works in Node.js, browsers, and edge runtimes

Installation

pnpm add @eventuras/logger

Quick Start

Scoped Logger (preferred)

Create a Logger instance per module so every log entry carries the module's namespace and any persistent context — it's the pattern we use everywhere in Eventuras, and it makes filtering by module in Loki / Grafana trivial.

import { Logger } from "@eventuras/logger";

const logger = Logger.create({
  namespace: "web:admin:events",
  context: { module: "EventEditor" },
});

logger.info({ eventId: 42 }, "Event updated");
logger.error({ error }, "Failed to save event");

Static Methods (one-off logs)

The static methods are fine for bootstrap code that runs before any scoped logger exists (server startup, top-level error handlers, scripts). For anything inside a module or request path, prefer Logger.create() so the output stays namespaced.

import { Logger } from "@eventuras/logger";

Logger.info("Server started");
Logger.warn({ memoryUsage: "85%" }, "Memory usage high");
Logger.error({ error }, "Unhandled exception");

Log Levels

From most to least verbose:

| Level | Value | Use case | | ------- | ----- | ---------------------------- | | trace | 10 | Fine-grained debugging | | debug | 20 | Development debugging | | info | 30 | General events (default) | | warn | 40 | Warnings | | error | 50 | Errors | | fatal | 60 | Critical/shutdown errors |

Configuration

Configure the logger once at application startup:

import { Logger } from "@eventuras/logger";

Logger.configure({
  level: "debug",
  redact: ["password", "token", "apiKey", "authorization", "secret"],
  destination: "/var/log/app.log", // Optional file output
});

Pretty dev output (Node only)

Pretty-printing depends on node:stream, so it lives in the /node subpath to keep the main entry browser/edge-safe. Call it from your server bootstrap:

import { configureNodeLogger } from "@eventuras/logger/node";

configureNodeLogger({
  level: "debug",
  prettyPrint: process.env.NODE_ENV === "development",
});

Environment Variables

LOG_LEVEL=debug       # Set global log level (picked up by the default PinoTransport)

Transports

The library uses a pluggable transport system. A transport implements the LogTransport interface:

interface LogTransport {
  log(level: LogLevel, data: Record<string, unknown>, msg?: string): void;
  child(bindings: Record<string, unknown>): LogTransport;
  flush?(): Promise<void>;
  shutdown?(): Promise<void>;
}

PinoTransport (default in Node.js)

Pino is included as a dependency and used automatically in Node.js environments. In browser/edge runtimes, ConsoleTransport is used as the default instead.

import { Logger, PinoTransport } from "@eventuras/logger";

// Explicit Pino configuration (JSON output to stdout)
Logger.configure({
  transport: new PinoTransport({
    level: "debug",
    redact: ["password", "secret"],
  }),
});

For pretty-printed dev output, use configureNodeLogger from @eventuras/logger/node (see Pretty dev output) — the prettyPrint option lives there to keep node:stream out of the universal main entry.

ConsoleTransport

A lightweight transport using native console methods. Automatically selected as the default in browser and edge runtimes. Also useful for testing:

import { Logger, ConsoleTransport } from "@eventuras/logger";

Logger.configure({
  transport: new ConsoleTransport(),
});

Custom Transport

Implement the LogTransport interface for any logging backend:

import type { LogTransport, LogLevel } from "@eventuras/logger";

class DatadogTransport implements LogTransport {
  log(level: LogLevel, data: Record<string, unknown>, msg?: string) {
    // Send to Datadog, Winston, Bunyan, or any backend
  }

  child(bindings: Record<string, unknown>): LogTransport {
    // Return a new transport with merged bindings
    return new DatadogTransport({ ...this.config, bindings });
  }
}

Logger.configure({ transport: new DatadogTransport() });

Auto-Redaction

Sensitive fields are automatically redacted:

import { Logger } from "@eventuras/logger";
const logger = Logger.create({ namespace: "auth" });

logger.info(
  {
    username: "john",
    password: "secret123", // → '[REDACTED]'
    apiKey: "key_123", // → '[REDACTED]'
  },
  "User login",
);

Default redacted paths: password, token, apiKey, authorization, secret.

Configure additional paths via Logger.configure({ redact: [...] }).

Nested fields

Redaction uses Pino's redact option, which is powered by fast-redact. Paths are exact field paths — they don't match nested occurrences by default:

// Only redacts top-level `password`
Logger.configure({ redact: ["password"] });

const logger = Logger.create({ namespace: "auth" });
logger.info({ password: "x" });           // → [REDACTED]
logger.info({ user: { password: "x" } }); // → NOT redacted

To redact nested fields, spell out the path or use wildcards:

Logger.configure({
  redact: [
    "password",
    "user.password",
    "request.headers.authorization",
    "*.token", // any key named `token` one level deep
  ],
});

For HTTP headers specifically, prefer redactHeaders — it normalizes the object and handles Headers instances in addition to plain objects.

HTTP Header Redaction

Utility for redacting sensitive HTTP headers:

import { redactHeaders } from "@eventuras/logger";

const safe = redactHeaders(request.headers);
logger.info({ headers: safe }, "Incoming request");

Redacts authorization, cookie, set-cookie, x-api-key, x-auth-token, and proxy-authorization.

OpenTelemetry Integration

Send logs to any OTel-compatible backend (Sentry, Grafana, Jaeger, etc.) without vendor lock-in.

Install OTel Packages

pnpm add @opentelemetry/api @opentelemetry/api-logs @opentelemetry/sdk-logs \
  @opentelemetry/instrumentation-pino @opentelemetry/exporter-logs-otlp-http

These are optional peer dependencies — the library works fine without them.

Setup

import { setupOpenTelemetryLogger } from "@eventuras/logger/opentelemetry";
import { OTLPLogExporter } from "@opentelemetry/exporter-logs-otlp-http";
import { BatchLogRecordProcessor } from "@opentelemetry/sdk-logs";

setupOpenTelemetryLogger({
  serviceName: "my-app",
  logRecordProcessor: new BatchLogRecordProcessor(
    new OTLPLogExporter({
      url: process.env.OTEL_EXPORTER_OTLP_LOGS_ENDPOINT,
    }),
  ),
});

Shutdown

import { shutdownOpenTelemetryLogger } from "@eventuras/logger/opentelemetry";

process.on("SIGTERM", async () => {
  await shutdownOpenTelemetryLogger();
  process.exit(0);
});

OTel Environment Variables

OTEL_EXPORTER_OTLP_LOGS_ENDPOINT=https://...
OTEL_EXPORTER_OTLP_LOGS_HEADERS=x-api-key=YOUR_KEY
OTEL_SERVICE_NAME=my-app

Examples

API Route Handler

import { Logger } from "@eventuras/logger";

export async function POST(req: Request) {
  const logger = Logger.create({
    namespace: "events-api",
    correlationId: req.headers.get("x-correlation-id") || crypto.randomUUID(),
  });

  try {
    const data = await req.json();
    logger.info("Creating event");

    const result = await createEvent(data);
    logger.info({ eventId: result.id }, "Event created");

    return Response.json(result);
  } catch (error) {
    logger.error({ error }, "Failed to create event");
    return Response.json({ error: "Internal server error" }, { status: 500 });
  }
}

Server Action

"use server";

import { Logger } from "@eventuras/logger";

const logger = Logger.create({ namespace: "web:admin:collections" });

export async function updateCollection(data: CollectionDto) {
  logger.info({ collectionId: data.id }, "Updating collection");

  try {
    const result = await api.put(data);
    logger.info({ collectionId: data.id }, "Collection updated");
    return { success: true, data: result };
  } catch (error) {
    logger.error({ error, collectionId: data.id }, "Failed to update");
    return { success: false, error: "Update failed" };
  }
}

TypeScript

All types are exported:

import type {
  LogTransport,
  LogLevel,
  LoggerOptions,
  LoggerConfig,
  ErrorLoggerOptions,
} from "@eventuras/logger";

Subpath Exports

| Import path | Contents | Environment | | --- | --- | --- | | @eventuras/logger | Logger, types, PinoTransport, ConsoleTransport, redactHeaders | Universal | | @eventuras/logger/node | configureNodeLogger, createPrettyStream, formatLogLine | Node.js | | @eventuras/logger/opentelemetry | setupOpenTelemetryLogger, shutdownOpenTelemetryLogger | Node.js |

Node-only Pretty-print Utilities

The pretty-print formatter and stream are available via a separate entry point to avoid pulling node:stream into browser bundles:

import { createPrettyStream, formatLogLine } from "@eventuras/logger/node";

License

Apache-2.0