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

@contract-kit/provider-logger-pino

v0.1.1

Published

Pino logger provider for contract-kit - adds logger port using pino

Readme

@contract-kit/provider-logger-pino

Pino logger provider for contract-kit that extends your application ports with structured logging capabilities using Pino.

Installation

npm install @contract-kit/provider-logger-pino pino
# or
bun add @contract-kit/provider-logger-pino pino

For pretty-printed logs in development (optional):

npm install --save-dev pino-pretty
# or
bun add -d pino-pretty

TypeScript Requirements

This package requires TypeScript 5.0 or higher for proper type inference.

Usage

Basic Setup

import { createServer } from "@contract-kit/server";
import { loggerPinoProvider } from "@contract-kit/provider-logger-pino";

// Set environment variables:
// LOG_LEVEL=info
// LOG_FORMAT=json
// LOG_SERVICE=my-app (optional)
// LOG_TIMESTAMP=true

const app = createServer({
  ports: basePorts,
  providers: [loggerPinoProvider],
  createContext: ({ ports }) => ({
    ports,
    // ... other context
  }),
  routes: [
    // ... your routes
  ],
});

Using the Logger in Use Cases

Once the provider is registered, your ports will include a logger property:

// Log informational messages
async function createUser(ctx: AppCtx, email: string) {
  ctx.ports.logger.info("Creating user", { email });
  
  const user = await ctx.ports.db.users.create({ email });
  
  ctx.ports.logger.info("User created successfully", { 
    userId: user.id,
    email: user.email 
  });
  
  return user;
}

// Log errors with context
async function processPayment(ctx: AppCtx, amount: number) {
  try {
    await ctx.ports.payment.charge(amount);
    ctx.ports.logger.info("Payment processed", { amount });
  } catch (error) {
    ctx.ports.logger.error("Payment failed", { 
      amount,
      error: error instanceof Error ? error.message : String(error)
    });
    throw error;
  }
}

// Log warnings
async function checkQuota(ctx: AppCtx, userId: string) {
  const usage = await ctx.ports.db.usage.getByUser(userId);
  
  if (usage.remaining < 10) {
    ctx.ports.logger.warn("User approaching quota limit", {
      userId,
      remaining: usage.remaining,
      total: usage.total
    });
  }
}

Request-Scoped Child Loggers

Create child loggers with bound fields for request-scoped logging:

// In your request handler or middleware
async function handleRequest(ctx: AppCtx, requestId: string) {
  // Create child logger with request context
  const log = ctx.ports.logger.child({ 
    requestId,
    userId: ctx.userId 
  });
  
  log.info("Processing request");
  
  try {
    const result = await processRequest(ctx);
    log.info("Request completed", { duration: Date.now() - start });
    return result;
  } catch (error) {
    log.error("Request failed", { 
      error: error instanceof Error ? error.message : String(error)
    });
    throw error;
  }
}

// All logs from the child logger will include requestId and userId

All Log Levels

The logger supports six log levels, from most to least verbose:

ctx.ports.logger.trace("Very detailed diagnostic info", { data });
ctx.ports.logger.debug("Debugging information", { state });
ctx.ports.logger.info("General informational messages", { event });
ctx.ports.logger.warn("Warning conditions", { issue });
ctx.ports.logger.error("Error conditions", { error });
ctx.ports.logger.fatal("Fatal errors that will terminate the app", { reason });

Configuration

The logger provider reads configuration from environment variables with the LOG_ prefix:

| Variable | Required | Default | Description | |----------|----------|---------|-------------| | LOG_LEVEL | No | info | Minimum log level: trace, debug, info, warn, error, fatal | | LOG_FORMAT | No | json | Log format: json (structured) or pretty (human-readable) | | LOG_SERVICE | No | - | Service name to add to all log entries | | LOG_TIMESTAMP | No | true | Whether to include timestamps (true or false) |

Log Levels

Set LOG_LEVEL to control which messages are logged:

  • trace: Most verbose, logs everything including trace messages
  • debug: Logs debug and above (excludes trace)
  • info: Logs info and above (default, good for production)
  • warn: Logs warnings, errors, and fatal only
  • error: Logs errors and fatal only
  • fatal: Logs only fatal messages

Log Formats

JSON Format (Production)

LOG_FORMAT=json

Outputs newline-delimited JSON, perfect for log aggregation systems:

{"level":30,"time":"2024-01-15T10:30:00.000Z","service":"my-app","msg":"User logged in","userId":123}
{"level":40,"time":"2024-01-15T10:30:05.000Z","service":"my-app","msg":"Rate limit approaching","remaining":10}

Pretty Format (Development)

LOG_FORMAT=pretty

Requires pino-pretty to be installed. Outputs human-readable logs:

[10:30:00.000] INFO: User logged in
    userId: 123
[10:30:05.000] WARN: Rate limit approaching
    remaining: 10

If pino-pretty is not installed, the logger will automatically fall back to JSON format and log a warning.

Service Name

Add a service name to all log entries for easier filtering in multi-service environments:

LOG_SERVICE=api-gateway

All logs will include "service":"api-gateway":

{"level":30,"time":"2024-01-15T10:30:00.000Z","service":"api-gateway","msg":"Request received"}

Timestamps

Control whether timestamps are included in logs:

LOG_TIMESTAMP=false  # Disable timestamps
LOG_TIMESTAMP=true   # Enable timestamps (default)

Logger Port API

The provider extends your ports with the following logger interface:

trace(message: string, meta?: Record<string, unknown>): void

Log trace-level messages (most verbose). Use for very detailed diagnostic information.

ctx.ports.logger.trace("Function entered", { args: [1, 2, 3] });

debug(message: string, meta?: Record<string, unknown>): void

Log debug-level messages. Use for diagnostic information useful during development.

ctx.ports.logger.debug("Cache miss", { key: "user:123" });

info(message: string, meta?: Record<string, unknown>): void

Log info-level messages. Use for general informational messages about application flow.

ctx.ports.logger.info("User logged in", { userId: 123 });

warn(message: string, meta?: Record<string, unknown>): void

Log warning-level messages. Use for warning conditions that should be reviewed.

ctx.ports.logger.warn("API rate limit approaching", { remaining: 10 });

error(message: string, meta?: Record<string, unknown>): void

Log error-level messages. Use for error conditions that need attention.

ctx.ports.logger.error("Database connection failed", { 
  error: error.message,
  retryAttempt: 3 
});

fatal(message: string, meta?: Record<string, unknown>): void

Log fatal-level messages (most severe). Use for errors that will cause the application to terminate.

ctx.ports.logger.fatal("Out of memory", { 
  heapUsed: process.memoryUsage().heapUsed 
});

child(bindings: Record<string, unknown>): LoggerPort

Create a child logger with bound fields. All messages logged via the child will include these fields.

const requestLogger = ctx.ports.logger.child({ 
  requestId: "abc123",
  userId: ctx.userId 
});

requestLogger.info("Processing request"); 
// Logs: { requestId: "abc123", userId: 456, msg: "Processing request", ... }

TypeScript Support

To get proper type inference for the logger port, extend your ports type:

import type { LoggerPort } from "@contract-kit/provider-logger-pino";

// Your base ports
const basePorts = definePorts({
  db: dbAdapter,
});

// After using loggerPinoProvider, your ports will have this shape:
type AppPorts = typeof basePorts & {
  logger: LoggerPort;
};

Example: Complete Setup

import { createServer } from "@contract-kit/server";
import { loggerPinoProvider } from "@contract-kit/provider-logger-pino";
import { definePorts } from "@contract-kit/ports";

// Define base ports
const basePorts = definePorts({
  db: myDatabaseAdapter,
});

// Create app with logger provider
const app = createServer({
  ports: basePorts,
  providers: [loggerPinoProvider],
  createContext: ({ ports, req }) => ({
    ports,
    requestId: req.headers["x-request-id"] || crypto.randomUUID(),
  }),
  routes: [
    // ... your routes
  ],
});

// Use in your application
async function myUseCase(ctx: AppCtx) {
  // Create request-scoped logger
  const log = ctx.ports.logger.child({ requestId: ctx.requestId });
  
  log.info("Starting operation");
  
  try {
    const result = await ctx.ports.db.query("...");
    log.info("Operation completed", { resultCount: result.length });
    return result;
  } catch (error) {
    log.error("Operation failed", { 
      error: error instanceof Error ? error.message : String(error)
    });
    throw error;
  }
}

Lifecycle

The Pino logger provider:

  1. During register:
    • Validates configuration from environment variables
    • Creates Pino logger instance with specified options
    • Adds the logger port to your application

The provider does not implement onAppStop as Pino automatically flushes logs and does not require explicit cleanup.

Best Practices

1. Use Structured Logging

Always pass structured metadata instead of interpolating values into messages:

// ✅ Good: Structured metadata
ctx.ports.logger.info("User login attempt", { 
  email: user.email,
  success: true,
  ip: req.ip 
});

// ❌ Bad: String interpolation
ctx.ports.logger.info(`User ${user.email} logged in from ${req.ip}`);

2. Use Appropriate Log Levels

  • trace/debug: Development and troubleshooting only
  • info: Normal application flow (e.g., user actions, important state changes)
  • warn: Unusual but handled conditions (e.g., rate limits, retries)
  • error: Errors that need attention but are handled
  • fatal: Unrecoverable errors (e.g., out of memory, critical dependency failure)

3. Use Child Loggers for Context

Create child loggers with request/operation context:

const log = ctx.ports.logger.child({ 
  requestId: ctx.requestId,
  userId: ctx.userId,
  operation: "checkout"
});

// All subsequent logs include this context automatically
log.info("Starting checkout");
log.info("Payment processed");

4. Log Errors Properly

Include error details and context:

try {
  await riskyOperation();
} catch (error) {
  ctx.ports.logger.error("Operation failed", {
    operation: "riskyOperation",
    error: error instanceof Error ? error.message : String(error),
    stack: error instanceof Error ? error.stack : undefined,
    // Add relevant context
    userId: ctx.userId,
    timestamp: Date.now()
  });
}

Production Configuration

Recommended settings for production:

LOG_LEVEL=info          # Balance between detail and noise
LOG_FORMAT=json         # Machine-readable, works with log aggregators
LOG_SERVICE=my-api      # Identify your service in logs
LOG_TIMESTAMP=true      # Essential for production debugging

Development Configuration

Recommended settings for local development:

LOG_LEVEL=debug         # More verbose for debugging
LOG_FORMAT=pretty       # Human-readable output
LOG_SERVICE=my-api-dev  # Identify as dev environment
LOG_TIMESTAMP=true      # Helpful for timing issues

Error Handling

The provider will throw errors in these cases:

  • Missing required configuration (though all config has defaults)
  • Invalid configuration values (validated by Zod schema)

Make sure to handle these during application startup.

License

MIT