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

strogger

v4.0.2

Published

A structured logging library for Node.js that makes request tracing easy

Readme

Strogger

A structured logging library for Node.js that makes request tracing easy.

npm version License: MIT TypeScript

Installation

npm install strogger

Quick Start

import { logger } from 'strogger';

const log = logger({ serviceName: 'my-app' });

log.info('Application started');
log.info('User logged in', { userId: 'user-123' });
log.error('Something failed', { orderId: 'order-456', err: new Error('Database timeout') });

// Before exit, flush pending logs
await log.flush();

Or use the pre-configured default logger:

import { strogger } from 'strogger';

strogger.info('Quick logging without setup');

Output (development - pretty printed):

12:34:56.789 INFO  Application started
12:34:56.790 INFO  User logged in  userId=user-123
12:34:56.791 ERROR Something failed  orderId=order-456
  Error: Database timeout
    at processOrder (/app/orders.ts:42:11)

Output (production - JSON):

{"timestamp":"2024-01-15T12:34:56.789Z","level":"INFO","message":"User logged in","userId":"user-123","serviceName":"my-app"}

Request Tracing

The most common need for structured logging is tracing requests through your application. Strogger makes this easy with child loggers:

import { logger } from 'strogger';

const log = logger({ serviceName: 'api-server' });

app.use((req, res, next) => {
  // Create a child logger with request context
  req.log = log.child({
    requestId: req.headers['x-request-id'] || crypto.randomUUID(),
    userId: req.user?.id,
    path: req.path,
  });
  next();
});

// In your route handlers:
app.get('/orders/:id', async (req, res) => {
  req.log.info('Fetching order');           // Has requestId, userId, path
  const order = await getOrder(req.params.id);
  req.log.info('Order found', { orderId: order.id });
  res.json(order);
});

Every log from req.log automatically includes requestId, userId, and path - no need to pass them to each call.

Express/Fastify Middleware

For even easier setup, use the built-in middleware:

import { logger, createRequestLogger, attachLogger } from 'strogger';

const log = logger({ serviceName: 'api' });

// Option 1: Full request logging with timing
app.use(createRequestLogger({
  logger: log,
  timing: true,
  skip: (req) => req.path === '/health',
  getContext: (req) => ({ userId: req.user?.id }),
}));

// Option 2: Just attach a logger to each request
app.use(attachLogger({
  logger: log,
  property: 'log',
  getContext: (req) => ({ userId: req.user?.id }),
}));

Automatic Context Propagation

For complex async flows, use runWithContext to automatically propagate context:

import { logger, runWithContext, generateRequestContext } from 'strogger';

const log = logger({ serviceName: 'worker' });

// Context flows through all async calls automatically
await runWithContext(
  { ...generateRequestContext(), jobId: 'job-123' },
  async () => {
    log.info('Starting job');      // Has correlationId, traceId, jobId
    await step1();
    await step2();
    log.info('Job complete');      // Same context
  }
);

async function step1() {
  log.info('Processing step 1');   // Still has all context!
}

Sensitive Data Redaction

Automatically redact sensitive data from logs:

import { logger, createLogger, createRedactor } from 'strogger';

const log = createLogger({
  config: {
    serviceName: 'secure-api',
    redact: createRedactor(), // Uses sensible defaults
  },
});

// Sensitive data is automatically redacted:
log.info('User email: [email protected]');        // Logs: "User email: [EMAIL]"
log.info('Login', { password: 'secret123' });    // password becomes "[REDACTED]"
log.info('Token: Bearer eyJhbGci...');           // Logs: "Token: Bearer [REDACTED]"

The default redactor handles: emails, credit cards, SSNs, API keys, Bearer/Basic tokens, JWTs, AWS keys, and password/secret fields.

Log Levels

log.debug('Detailed debugging info');
log.info('General information');
log.warn('Warning message');
log.error('Error occurred', { err: new Error('Something broke') });
log.fatal('Critical failure');

Set the level via environment variable or config:

LOG_LEVEL=debug  # Show all logs
LOG_LEVEL=info   # Default in production
import { logger, LogLevel } from 'strogger';

const log = logger({ level: 'debug' });      // String (recommended)
const log2 = logger({ level: LogLevel.DEBUG }); // Enum also works

Configuration

Simple (recommended)

import { logger, LogLevel } from 'strogger';

const log = logger({
  serviceName: 'my-app',     // Identifies your service
  stage: 'production',       // 'dev' | 'staging' | 'prod'
  level: LogLevel.INFO,      // Minimum level to log
  pretty: false,             // Use JSON formatting
});

Environment Variables

SERVICE_NAME=my-app
STAGE=prod
LOG_LEVEL=info

Advanced

For custom transports and advanced features:

import {
  createLogger,
  createConsoleTransport,
  createJsonFormatter,
  createRedactor,
  LogLevel,
} from 'strogger';

const log = createLogger({
  config: {
    serviceName: 'my-app',
    level: LogLevel.DEBUG,
    redact: createRedactor(),
    samplingRate: 0.1, // Sample 10% of logs
  },
  transports: [
    createConsoleTransport({
      formatter: createJsonFormatter(),
      level: LogLevel.DEBUG,
    }),
  ],
});

Transports

Send logs to multiple destinations using shorthand configs:

import { logger } from 'strogger';

// Simple - use environment variables for credentials
const log = logger({
  serviceName: 'my-app',
  datadog: true,                              // Uses DATADOG_API_KEY env var
  cloudwatch: { logGroupName: '/app/logs' },  // CloudWatch requires logGroupName
  file: true,                                 // Local file backup
});

// With explicit options
const log2 = logger({
  serviceName: 'my-app',
  datadog: { region: 'eu', tags: ['env:prod'] },
  newrelic: { region: 'eu' },
  elasticsearch: { url: 'https://es.example.com:9200' },
});

Shorthand options:

| Transport | Shorthand | Environment Variables | |-----------|-----------|----------------------| | DataDog | datadog: true | DATADOG_API_KEY, DD_SERVICE | | CloudWatch | cloudwatch: { logGroupName } | AWS_REGION | | Splunk | splunk: true | SPLUNK_HEC_URL, SPLUNK_HEC_TOKEN | | Elasticsearch | elasticsearch: true | ELASTICSEARCH_URL, ELASTICSEARCH_API_KEY | | New Relic | newrelic: true | NEW_RELIC_LICENSE_KEY, NEW_RELIC_ACCOUNT_ID | | File | file: true | - |

Advanced Transport Setup

For full control, use createLogger with explicit transports:

import {
  createLogger,
  createConsoleTransport,
  createCloudWatchTransport,
  createDataDogTransport,
  createJsonFormatter,
} from 'strogger';

const log = createLogger({
  config: { serviceName: 'my-app' },
  transports: [
    createConsoleTransport({ formatter: createJsonFormatter() }),
    createCloudWatchTransport({
      logGroupName: '/aws/lambda/my-function',
      region: 'us-east-1',
    }),
    createDataDogTransport({
      apiKey: process.env.DATADOG_API_KEY,
    }),
  ],
});

Available transports:

  • createConsoleTransport - Console output (included by default)
  • createFileTransport - Local file with rotation
  • createCloudWatchTransport - AWS CloudWatch Logs
  • createDataDogTransport - DataDog
  • createSplunkTransport - Splunk HEC
  • createElasticsearchTransport - Elasticsearch
  • createNewRelicTransport - New Relic

Graceful Shutdown

Strogger automatically registers exit handlers to flush logs, but for explicit control:

// Flush pending logs (waits for completion)
await log.flush();

// Full shutdown (flush + close transports)
await log.shutdown();

TypeScript

Full TypeScript support with comprehensive types:

import type { Logger, LogContext, LogLevel } from 'strogger';

function processWithLogging(log: Logger, data: unknown): void {
  log.info('Processing', { dataSize: JSON.stringify(data).length });
}

Best Practices

Use Structured Data

// Good - structured and searchable
log.info('Order created', { orderId: 'order-123', amount: 99.99, currency: 'USD' });

// Avoid - hard to parse and search
log.info('Created order order-123 for $99.99 USD');

Use Child Loggers for Request Context

// Good - context is automatic
const requestLog = log.child({ requestId, userId });
requestLog.info('Step 1');
requestLog.info('Step 2');

// Avoid - repetitive and error-prone
log.info('Step 1', { requestId, userId });
log.info('Step 2', { requestId, userId });

Use Appropriate Log Levels

log.debug('Query executed', { sql, params });         // Development debugging
log.info('User registered', { userId });              // Normal operations
log.warn('Rate limit approaching', { current });      // Potential issues
log.error('Payment failed', { orderId, err: error }); // Errors that need attention
log.fatal('Database connection lost');                // Critical failures

Platform Compatibility

Node.js (Full Support)

Strogger is designed for Node.js 18+ and uses AsyncLocalStorage for automatic context propagation through async operations.

Browser/Edge (Limited Support)

When AsyncLocalStorage is not available (browsers, some edge runtimes), Strogger gracefully falls back to a synchronous context store. This means:

  • All core logging features work normally
  • child() loggers work as expected
  • runWithContext() works for synchronous code
  • Limitation: Context may not propagate correctly through some async patterns

To check availability:

import { hasAsyncLocalStorage } from 'strogger';

if (!hasAsyncLocalStorage()) {
  console.warn('Context propagation is sync-only in this environment');
}

Performance

Log calls are synchronous and non-blocking - they return immediately while logs are processed in the background via microtasks. This means:

  • Logging never blocks your application
  • Logs are batched for efficient transport writes
  • ~1.4 million logs/second throughput (see benchmarks/throughput.ts)

Migration Guide (v3 → v4)

Error Logging

// v3 - error as separate parameter
log.error('failed', { requestId }, error);

// v4 - error in data object as 'err'
log.error('failed', { requestId, err: error });

String Log Levels

// v3 - enum only
log.setLevel(LogLevel.DEBUG);

// v4 - string or enum
log.setLevel('debug');
log.setLevel(LogLevel.DEBUG);  // still works

Async Handling

// v3 - await each log (optional)
await log.info('message');

// v4 - fire and forget, flush when needed
log.info('message');
await log.flush();  // only when you need to wait

Convenience Methods Removed

The built-in convenience methods (logFunctionStart, logFunctionEnd, logDatabaseOperation, logApiRequest) have been removed. Use standard logging with descriptive messages instead:

// v3
log.logFunctionStart('processOrder');
log.logFunctionEnd('processOrder', 150);

// v4
log.info('Function processOrder started', { functionName: 'processOrder' });
log.info('Function processOrder completed', { functionName: 'processOrder', duration: 150 });

Error Handling

Transport errors (network failures, missing credentials) are handled gracefully - they won't crash your application. By default, errors are logged to console. Customize with:

const log = logger({
  serviceName: 'my-app',
  datadog: true,
  onError: (error, transportName) => {
    // Custom error handling (e.g., send to error tracking service)
    console.error(`[${transportName}] ${error.message}`);
  },
});

If a transport's required credentials are missing (e.g., datadog: true without DATADOG_API_KEY), the logger will throw at creation time with a helpful error message.

License

MIT