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

@outfitter/logging

v0.1.0-rc.3

Published

Structured logging via logtape with redaction support for Outfitter

Readme

@outfitter/logging

Structured logging via logtape with automatic sensitive data redaction. Provides consistent log formatting across CLI, MCP, and server contexts.

Installation

bun add @outfitter/logging

Quick Start

import {
  createLogger,
  createConsoleSink,
  configureRedaction,
} from "@outfitter/logging";

// Configure global redaction (optional - defaults already cover common sensitive keys)
configureRedaction({
  keys: ["apiKey", "accessToken"],
  patterns: [/sk-[a-zA-Z0-9]+/g],
});

// Create a logger
const logger = createLogger({
  name: "my-service",
  level: "debug",
  sinks: [createConsoleSink()],
  redaction: { enabled: true },
});

// Log with metadata
logger.info("Request received", {
  path: "/api/users",
  apiKey: "secret-key-123", // Will be redacted to "[REDACTED]"
});

Log Levels

| Level | Priority | Use For | | -------- | -------- | ------------------------------------------ | | trace | 0 | Very detailed debugging (loops, internals) | | debug | 1 | Development debugging | | info | 2 | Normal operations | | warn | 3 | Unexpected but handled situations | | error | 4 | Failures requiring attention | | fatal | 5 | Unrecoverable failures | | silent | 6 | Disable all logging |

Messages are filtered by minimum level. Setting level: "warn" filters out trace, debug, and info.

const logger = createLogger({
  name: "app",
  level: "warn", // Only warn, error, fatal will be logged
  sinks: [createConsoleSink()],
});

logger.debug("Filtered out");
logger.warn("This appears");

Changing Level at Runtime

logger.setLevel("debug"); // Enable debug logging
logger.setLevel("silent"); // Disable all logging

Redaction

Automatic redaction protects sensitive data from appearing in logs.

Default Sensitive Keys

These keys are redacted by default (case-insensitive matching):

  • password
  • secret
  • token
  • apikey

Custom Redaction Patterns

configureRedaction({
  patterns: [
    /Bearer [a-zA-Z0-9._-]+/g, // Bearer tokens
    /sk-[a-zA-Z0-9]{20,}/g, // OpenAI keys
    /ghp_[a-zA-Z0-9]{36}/g, // GitHub PATs
  ],
  keys: ["credentials", "privateKey"],
});

Per-Logger Redaction

const logger = createLogger({
  name: "auth",
  redaction: {
    enabled: true,
    patterns: [/custom-secret-\d+/g],
    keys: ["myCustomKey"],
    replacement: "***", // Custom replacement (default: "[REDACTED]")
  },
});

Nested Object Redaction

Redaction is recursive and applies to nested objects:

logger.info("Config loaded", {
  database: {
    host: "localhost",
    password: "super-secret", // Redacted
  },
  api: {
    url: "https://api.example.com",
    token: "jwt-token", // Redacted
  },
});
// Output: { database: { host: "localhost", password: "[REDACTED]" }, ... }

Child Loggers

Create scoped loggers that inherit parent configuration and merge context:

const parent = createLogger({
  name: "app",
  context: { service: "api" },
  sinks: [createConsoleSink()],
  redaction: { enabled: true },
});

const child = createChildLogger(parent, { handler: "getUser" });

child.info("Processing request");
// Output includes merged context: { service: "api", handler: "getUser" }

Child loggers:

  • Inherit parent's sinks, level, and redaction config
  • Merge context (child overrides parent for conflicting keys)
  • Share the same setLevel() and addSink() behavior

Formatters

JSON Formatter

Machine-readable output for log aggregation:

import { createJsonFormatter } from "@outfitter/logging";

const formatter = createJsonFormatter();
// Output: {"timestamp":1705936800000,"level":"info","category":"app","message":"Hello","userId":"123"}

Pretty Formatter

Human-readable output with optional ANSI colors:

import { createPrettyFormatter } from "@outfitter/logging";

const formatter = createPrettyFormatter({ colors: true, timestamp: true });
// Output: 2024-01-22T12:00:00.000Z [INFO] app: Hello {"userId":"123"}

Sinks

Console Sink

Routes logs to stdout/stderr based on level:

  • trace, debug, info -> stdout
  • warn, error, fatal -> stderr
import { createConsoleSink } from "@outfitter/logging";

const logger = createLogger({
  name: "app",
  sinks: [createConsoleSink()],
});

File Sink

Buffered writes to a file path:

import { createFileSink, flush } from "@outfitter/logging";

const logger = createLogger({
  name: "app",
  sinks: [createFileSink({ path: "/var/log/app.log" })],
});

logger.info("Application started");

// Call flush() before exit to ensure all logs are written
await flush();

Custom Sinks

Implement the Sink interface for custom destinations:

import type { Sink, LogRecord, Formatter } from "@outfitter/logging";

const customSink: Sink = {
  formatter: createJsonFormatter(), // Optional
  write(record: LogRecord, formatted?: string): void {
    // Send to your destination
    sendToRemote(formatted ?? JSON.stringify(record));
  },
  async flush(): Promise<void> {
    // Optional: ensure pending writes complete
    await flushPendingWrites();
  },
};

Multiple Sinks

Logs can be sent to multiple destinations:

const logger = createLogger({
  name: "app",
  sinks: [
    createConsoleSink(),
    createFileSink({ path: "/var/log/app.log" }),
    customRemoteSink,
  ],
});

Structured Metadata

Basic Metadata

logger.info("User logged in", {
  userId: "u123",
  email: "[email protected]",
});

Error Serialization

Error objects are automatically serialized with name, message, and stack:

try {
  await riskyOperation();
} catch (error) {
  logger.error("Operation failed", { error });
  // error is serialized as: { name: "Error", message: "...", stack: "..." }
}

Context Inheritance

Logger context is merged with per-call metadata:

const logger = createLogger({
  name: "api",
  context: { requestId: "abc123" },
  sinks: [createConsoleSink()],
});

logger.info("Processing", { step: 1 });
// Metadata: { requestId: "abc123", step: 1 }

Flushing

Call flush() before process exit to ensure buffered logs are written:

import { flush } from "@outfitter/logging";

process.on("beforeExit", async () => {
  await flush();
});

// Or before explicit exit
logger.info("Shutting down");
await flush();
process.exit(0);

API Reference

Functions

| Function | Description | | ----------------------- | --------------------------------------------------- | | createLogger | Create a configured logger instance | | createChildLogger | Create a child logger with merged context | | configureRedaction | Configure global redaction patterns and keys | | flush | Flush all pending log writes across all sinks | | createJsonFormatter | Create a JSON formatter for structured output | | createPrettyFormatter | Create a human-readable formatter with colors | | createConsoleSink | Create a console sink (stdout/stderr routing) | | createFileSink | Create a file sink with buffered writes |

Types

| Type | Description | | ------------------------ | ----------------------------------------------- | | LogLevel | Union of log level strings | | LogRecord | Structured log record with timestamp/metadata | | LoggerConfig | Configuration options for createLogger | | LoggerInstance | Logger interface with level methods | | RedactionConfig | Per-logger redaction configuration | | GlobalRedactionConfig | Global redaction patterns and keys | | Formatter | Interface for log record formatting | | Sink | Interface for log output destinations | | PrettyFormatterOptions | Options for human-readable formatter | | FileSinkOptions | Options for file sink configuration |

License

MIT