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

@ubercode/winston-cloudwatch

v1.3.1

Published

A TypeScript Winston transport for Amazon CloudWatch.

Downloads

966

Readme

@ubercode/winston-cloudwatch

npm version npm downloads CI TypeScript Node.js License: MIT

A modern TypeScript Winston transport for Amazon CloudWatch using AWS SDK v3.

Features

  • TypeScript - Full TypeScript support with complete type definitions
  • AWS SDK v3 - Uses the modern modular AWS SDK v3
  • Rate Limiting - Built-in throttling to respect CloudWatch API limits
  • Bounded Memory - Delivery is decoupled from Winston's stream; a CloudWatch outage can never stall the logger or leak memory
  • Automatic Retries - Handles sequence token errors automatically
  • Customizable Formatting - Flexible log formatting options
  • JSON Formatting - Optional structured JSON log output
  • Retention Policies - Automatic log group retention configuration
  • Byte-Aware Batching - Respects the 1 MB PutLogEvents payload limit
  • Graceful Shutdown - Flush pending logs before process exit
  • Client Injection - Bring your own CloudWatchLogsClient
  • Well Tested - 100% test coverage with Jest

Installation

npm install @ubercode/winston-cloudwatch winston
# or
yarn add @ubercode/winston-cloudwatch winston
# or
pnpm add @ubercode/winston-cloudwatch winston

Usage

JavaScript (CommonJS)

const winston = require('winston')
const CloudWatchTransport = require('@ubercode/winston-cloudwatch').default

const logger = winston.createLogger({
  transports: [
    new CloudWatchTransport({
      logGroupName: 'my-app-logs',        // REQUIRED
      logStreamName: 'my-app-stream',     // REQUIRED
      createLogGroup: true,
      createLogStream: true,
      submissionInterval: 2000,
      batchSize: 20,
      awsConfig: {
        region: 'us-east-1',
        credentials: {
          accessKeyId: process.env.AWS_ACCESS_KEY_ID,
          secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY
        }
      }
    })
  ]
})

logger.info('Hello CloudWatch!', { userId: 123, action: 'login' })

TypeScript

import winston from 'winston'
import CloudWatchTransport from '@ubercode/winston-cloudwatch'

const logger = winston.createLogger({
  transports: [
    new CloudWatchTransport({
      logGroupName: 'my-app-logs',
      logStreamName: 'my-app-stream',
      createLogGroup: true,
      createLogStream: true,
      awsConfig: {
        region: 'us-east-1'
      }
    })
  ]
})

logger.info('Hello CloudWatch!', { userId: 123, action: 'login' })

Configuration Options

| Option | Type | Required | Default | Description | |----------------------|------------------------------|----------|-------------|--------------------------------------------------------------------------------------------------| | name | string | No | cloudwatch| Transport name used by Winston to identify this transport | | logGroupName | string | Yes | - | CloudWatch log group name (1-512 characters) | | logStreamName | string | Yes | - | CloudWatch log stream name (1-512 characters) | | awsConfig | CloudWatchLogsClientConfig | No | {} | AWS SDK v3 client configuration. Ignored when cloudWatchLogs is provided | | cloudWatchLogs | CloudWatchLogsClient | No | - | Pre-built AWS SDK client. When provided, awsConfig is ignored and the client is not destroyed on close | | createLogGroup | boolean | No | false | Automatically create log group if it doesn't exist | | createLogStream | boolean | No | false | Automatically create log stream if it doesn't exist | | retentionInDays | RetentionInDays | No | - | Set the retention policy on the log group (e.g. 1, 7, 30, 90, 365). Works on pre-existing groups | | timeout | number | No | 10000 | Timeout in ms for each AWS SDK call | | maxEventSize | number | No | 1048576 | Max event size in bytes (including 26-byte overhead). Messages exceeding the limit are truncated | | jsonMessage | boolean | No | false | Format log messages as JSON objects. Ignored if formatLog or formatLogItem is provided | | formatLog | function | No | - | Custom function to format log messages. Takes precedence over formatLogItem | | formatLogItem | function | No | - | Custom function to format log items (message + timestamp). Ignored if formatLog is provided | | submissionInterval | number | No | 2000 | Milliseconds between batch submissions | | batchSize | number | No | 20 | Maximum number of logs per batch | | maxQueueSize | number | No | 10000 | Maximum queued log items (oldest dropped when full) | | maxRetries | number | No | 10 | Consecutive failed delivery attempts of the head batch before it is dropped (frees head-of-line) | | retryBackoffCap | number | No | 30000 | Upper bound (ms) on the exponential backoff between failed retries. 0 disables backoff | | level | string | No | - | Minimum log level for this transport (inherited from Winston) | | silent | boolean | No | false | Suppress all output (inherited from Winston) | | handleExceptions | boolean | No | false | Handle uncaught exceptions (inherited from Winston) |

Custom Formatting

new CloudWatchTransport({
  logGroupName: 'my-app',
  logStreamName: 'my-stream',
  formatLog: (item) => {
    const meta = item.meta ? ` ${JSON.stringify(item.meta)}` : ''
    return `[${item.level}] ${item.message}${meta}`
  }
})

Graceful Shutdown

close() is asynchronous: it performs a best-effort flush of any queued logs (bounded by the flush timeout) before stopping the relay. For a graceful shutdown, await it so pending logs get a chance to ship:

await transport.close()

Winston also calls close() automatically when the logger ends, but it does not await it. If you exit the process immediately (e.g. process.exit()), await the flush yourself first:

await transport.flush()      // drain the queue (default timeout: 10 seconds)
await transport.close()

You can also specify a custom flush timeout in milliseconds:

await transport.flush(5000)  // wait up to 5 seconds
await transport.close()

Logs that cannot be delivered before shutdown (or that are evicted when the queue is full) are reported to Winston as not delivered and silently dropped. They are not raised as error events, so a missed log on shutdown will never crash your process.

Backpressure & Delivery Semantics

CloudWatch delivery is decoupled from Winston's writable stream. When you log, the entry is accepted into a bounded in-memory queue and Winston's write callback is resolved immediately — delivery to CloudWatch then happens asynchronously in rate-limited batches.

This is deliberate. If the write callback were deferred until CloudWatch confirmed delivery, any persistent delivery failure (throttling, request timeouts, missing IAM permissions, a CloudWatch outage) would stall Winston's serialized objectMode stream at the head-of-line: every subsequent log would accumulate, unbounded, in the stream's internal buffer until the process ran out of memory. (This is the long-standing leak inherited from the original winston-cloudwatch / winston-aws-cloudwatch lineage — see #9.)

Practical implications:

  • Memory is strictly bounded by maxQueueSize (default 10000), regardless of CloudWatch availability. When the queue is full the oldest queued log is dropped (reported to Winston as not delivered, never as an error).
  • logger.info(...) returning does not mean the log reached CloudWatch — only that it was accepted into the queue. Genuine delivery failures are surfaced via the transport's error event, not via the logging call.
  • For maximum delivery on shutdown, await transport.flush() / await transport.close() (see Graceful Shutdown).
  • During a persistent outage a failing batch is retried with exponential backoff (capped by retryBackoffCap) and, after maxRetries consecutive failures, dropped — so an undeliverable head batch never head-of-line blocks newer logs. Each failed attempt is surfaced as an error event.

Tune the buffer with maxQueueSize, batchSize, submissionInterval, maxRetries, and retryBackoffCap.

Migration Guides

Coming from another CloudWatch Winston transport? See our migration guides:

Error Handling

The transport emits an error event only when a CloudWatch submission fails with an unrecoverable error. Dropped logs (queue overflow or shutdown) are not emitted as errors, so they cannot crash a process that has no error listener. It's still recommended to subscribe to this event so genuine CloudWatch failures are surfaced:

const transport = new CloudWatchTransport({
  logGroupName: 'my-app',
  logStreamName: 'my-stream'
})

transport.on('error', (error) => {
  console.error('CloudWatch logging error:', error)
})

const logger = winston.createLogger({ transports: [transport] })

AWS Credentials

This library uses AWS SDK v3, which supports multiple authentication methods:

  1. Environment Variables: AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_REGION
  2. AWS Config Files: ~/.aws/credentials and ~/.aws/config
  3. IAM Roles: Automatic in EC2, ECS, Lambda
  4. Explicit Config: Pass credentials in awsConfig
// Option 1: Use environment variables (recommended)
new CloudWatchTransport({
  logGroupName: 'my-app',
  logStreamName: 'my-stream',
  awsConfig: { region: 'us-east-1' }
})

// Option 2: Explicit credentials (not recommended for production)
new CloudWatchTransport({
  logGroupName: 'my-app',
  logStreamName: 'my-stream',
  awsConfig: {
    region: 'us-east-1',
    credentials: {
      accessKeyId: 'YOUR_ACCESS_KEY',
      secretAccessKey: 'YOUR_SECRET_KEY'
    }
  }
})

Why This Fork?

This is a modernized TypeScript fork of winston-aws-cloudwatch with the following improvements:

  • Full TypeScript rewrite with proper types
  • Updated to AWS SDK v3 (modular, tree-shakeable)
  • Modern testing with Jest (replacing Mocha)
  • Updated dependencies and security fixes
  • Better error handling and type safety

Requirements

  • Node.js >= 20.9.0
  • Winston ^3.0.0

Development

# Install dependencies
pnpm install

# Run tests
pnpm test

# Run tests with coverage
pnpm test:cover

# Sustained memory soak (node --expose-gc; not part of `pnpm test` or CI)
pnpm test:stress

# Build
pnpm build

# Lint
pnpm lint

# Format
pnpm format

This project follows the mission-critical TypeScript standard documented in docs/CodingStandards.md.

License

MIT

Original Author

Original package by Tim De Pauw

Contributors

TypeScript modernization and AWS SDK v3 migration by Michael Lee Hobbs