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

pm2-log-server

v0.1.1

Published

PM2 plugin for streaming application logs via WS

Readme

PM2 Log Server Plugin

A PM2 plugin that streams application logs via WebSocket using the native PM2 bus API.

Features

  • 🔄 Real-time log streaming via WebSocket
  • 🔐 Built-in authentication support
  • 📊 Dynamic process subscriptions
  • 🎯 Advanced filtering options:
    • Filter by log level (all/out/error)
    • Strip ANSI color codes
    • JSON or plain text output
  • 💾 Configurable log buffering
  • 🌐 CORS support for REST endpoints
  • 📝 Both stdout and stderr streaming
  • ⚡ Zero file I/O - uses PM2's in-memory event bus
  • 🔌 Automatic process detection and registration

Advantages of Using PM2 Bus API + WebSocket

PM2 Bus Benefits:

  • Real-time performance: Direct event stream from PM2, no file I/O latency
  • Lower resource usage: No file watchers, no disk reads
  • Reliable: Works even if log files are rotated, deleted, or disabled
  • Simpler: No need to handle file paths, permissions, or rotation
  • Consistent: Gets logs exactly as PM2 processes them

WebSocket Benefits:

  • Bidirectional: Clients can send filtering options and receive logs
  • Authentication: Built-in token authentication in the protocol
  • Dynamic filtering: Change filters without reconnecting
  • Efficient: Lower overhead than SSE for high-frequency updates
  • Flexible: Subscribe/unsubscribe to specific processes on demand

Installation

pm2 install pm2-log-server

Configuration

This plugin has several configurable values that you must set using PM2's build in commands.

  pm2 set pm2-log-server:authToken some-secure-token
  pm2 set pm2-log-server:corsEnabled false
  pm2 set pm2-log-server:host 0.0.0.0
  pm2 set pm2-log-server:logBufferSize 100
  pm2 set pm2-log-server:port 9615

WebSocket Protocol

Connect to: ws://localhost:9615/ws

Message Types

1. Authentication (if token is configured)

{
  "type": "auth",
  "token": "your-secret-token"
}

Response:

{
  "type": "authenticated",
  "message": "Authentication successful"
}

2. Subscribe to Process

{
  "type": "subscribe",
  "process": "my-app"
}

Subscribe to all processes:

{
  "type": "subscribe",
  "process": "*"
}

Response:

{
  "type": "subscribed",
  "process": "my-app",
  "subscriptions": ["my-app"]
}

3. Set Format

{
  "type": "format",
  "format": {
    "clean": true,        // Strip ANSI color codes (default: false)
    "json": false,        // Return logs as JSON objects (default: true)
    "timestamps": false,  // Prefixes the raw log message with a timestamp
    "log_type": false     // Prefixes the raw log with the log type (`error` | `out`)
  }
}

Response:

{
  "type": "format_updated",
  "format": {
    "clean": true,
    "json": false,
    "timestamps": false,
    "log_type": false
  }
}

4. Set Filter

{
  "type": "filter",
  "filter": {
    "text": "",       // simple text filter
    "regex": "",      // custom regex sequence
    "log_type": "all" // "all" | "out" | "error"
  }
}

Response:

{
  "type": "filter_updated",
  "filter": {
    "text": "",
    "regex": "",
    "log_type": "all"
  }
}

5. Unsubscribe from Process

{
  "type": "unsubscribe",
  "process": "my-app"
}

Unsubscribe from all:

{
  "type": "unsubscribe"
}

6. Ping/Pong

{
  "type": "ping"
}

Response:

{
  "type": "pong"
}

Receiving Logs

When json: true (default):

{
  "timestamp": "2025-01-15T10:30:00.000Z",
  "type": "out",
  "message": "Server started on port 3000",
  "process": "my-app"
}

When json: false:

Server started on port 3000

Or with timestamp (if includeTimestamp is enabled):

[2025-01-15T10:30:00.000Z] [out] Server started on port 3000

REST API Endpoints

GET /health

Health check endpoint

curl http://localhost:9615/health

Response:

{
  "status": "ok",
  "timestamp": "2025-01-15T10:30:00.000Z",
  "clients": 3
}

GET /processes

List all watched PM2 processes

curl -H "Authorization: Bearer your-token" http://localhost:9615/processes

Response:

{
  "processes": ["my-app", "worker", "api"],
  "count": 3
}

GET /logs/:name/recent

Get recent logs as JSON

curl -H "Authorization: Bearer your-token" \
  "http://localhost:9615/logs/my-app/recent?count=10&filter=error&clean=true"

Query parameters:

  • count: Number of recent logs to retrieve
  • filter: all | out | error
  • clean: true | false - Strip ANSI codes

Response:

{
  "process": "my-app",
  "logs": [
    {
      "timestamp": "2025-01-15T10:30:00.000Z",
      "type": "error",
      "message": "Connection failed",
      "process": "my-app"
    }
  ],
  "count": 1
}

Client Examples

Node.js Client

const WebSocket = require('ws');

const ws = new WebSocket('ws://localhost:9615/ws');

ws.on('open', () => {
  console.log('Connected to PM2 Log Server');
  
  // Authenticate (if required)
  ws.send(JSON.stringify({
    type: 'auth',
    token: 'your-secret-token'
  }));
  
  // Set filter
  ws.send(
    JSON.stringify({
      type: 'filter',
      filter: { log_type: 'all',  text: '', regex: '/abc/gi' },
    })
  )

  // Set format
  ws.send(
    JSON.stringify({
      type: 'format',
      format: { clean: false, json: false, timestamps: false, log_type: false },
    })
  )
  
  // Subscribe to a process
  ws.send(JSON.stringify({
    type: 'subscribe',
    process: 'my-app'
  }));
});

ws.on('message', (data) => {
  try {
    const message = JSON.parse(data);
    
    if (message.type) {
      // Control message
      console.log('Control:', message);
    } else {
      // Log entry
      console.log('Log:', message);
    }
  } catch (e) {
    // Plain text log
    console.log('Log:', data.toString());
  }
});

ws.on('error', (error) => {
  console.error('WebSocket error:', error);
});

ws.on('close', () => {
  console.log('Disconnected from PM2 Log Server');
});

Browser Client

const ws = new WebSocket('ws://localhost:9615/ws');

ws.addEventListener('open', () => {
  console.log('Connected');
  
  // Authenticate
  ws.send(JSON.stringify({
    type: 'auth',
    token: 'your-secret-token'
  }));
  
  // Subscribe to all processes
  ws.send(JSON.stringify({
    type: 'subscribe',
    process: '*'
  }));
});

ws.addEventListener('message', (event) => {
  try {
    const message = JSON.parse(event.data);
    console.log('Message:', message);
  } catch (e) {
    // Plain text log
    console.log('Log:', event.data);
  }
});

ws.addEventListener('error', (error) => {
  console.error('Error:', error);
});

ws.addEventListener('close', () => {
  console.log('Disconnected');
});

License

MIT