JSPM

  • Created
  • Published
  • Downloads 1988949
  • Score
    100M100P100Q276334F
  • License Apache-2

PM2.io NodeJS APM

Package Exports

  • @pm2/io

This package does not declare an exports field, so the exports above have been automatically detected and optimized by JSPM instead. If any package subpath is missing, it is recommended to post an issue to the original package (@pm2/io) to support the "exports" field. If that is not possible, create a JSPM override to customize the exports field for this package.

Readme

The @pm2/io module comes along with PM2. It is the PM2 library responsible for gathering the metrics, reporting exceptions, exposing remote actions and every interaction with your application.

You can also use it as a standalone agent, if you want to connect your nodejs process to PM2 Enterprise but without having to launch your application with PM2.

Table of Contents

Installation

With npm:

npm install @pm2/io --save

With yarn:

yarn add @pm2/io

Expose Custom Metrics

@pm2/io allows you to gather metrics from your code to be reported in the PM2 Plus/Enterprise dashboard.

Create a custom metrics

You can create a new custom metrics with the method metric() of @pm2/io.

const io = require('@pm2/io');

const users = io.metric({
  name: 'Realtime user',
});
users.set(10)

This arguments are available:

  • name: The metric name (required; string)
  • id: The type of metric (default 'metric', string)
  • unit: unit of the measure (default ''; string)
  • historic: keep the history in PM2 Plus (default: true; boolean)

There are 4 different types of metrics:

  • gauge: To expose a variable's value
  • counter: A discrete counter to be triggered manually to count a number of occurrence
  • meter: To measure a frequency, a number of occurrences of a repeating event per unit of time
  • histogram: To measure a statistic, a statistic on a metric over the last 5 minutes

Metric: Variable Exposition

The first type of metric, called metric, allows to expose a variable's value. The variable can be exposed passively, with a function that gets called every second, or actively, with a method that you use to update the value.

Active Mode

In active mode, you need to create a probe and call the method set() to update the value.

const myMetric = io.metric({
  name: 'Realtime Value'
});

myMetric.set(23);

Counter: Discrete Counter

The second type of metric, called counter, is a discrete counter that helps you count the number of occurrence of a particular event. The counter starts at 0 and can be incremented or decremented.

const io = require('@pm2/io');

const currentReq = io.counter({
  name: 'Current req processed',
  type: 'counter',
});

http.createServer((req, res) => {
  // Increment the counter, counter will eq 1
  currentReq.inc();
  req.on('end', () => {
    // Decrement the counter, counter will eq 0
    currentReq.dec();
  });
});

Meter: Frequency

The third type of metric, called meter, compute the frequency of an event. Each time the event happens, you need to call the mark() method. By default, the frequency is the number of events per second over the last minute.

const io = require('@pm2/io');

const reqsec = io.meter({
  name: 'req/sec',
  type: 'meter',
});

http.createServer((req, res) => {
  reqsec.mark();
  res.end({ success: true });
});

Additional options:

  • samples: (optional)(default: 1) Rate unit. Defaults to 1 sec.
  • timeframe: (optional)(default: 60) Timeframe over which the events will be analyzed. Defaults to 60 sec.

Histogram: Statistics

Collect values and provide statistic tools to explore their distribution over the last 5 minutes.

const io = require('@pm2/io');

const latency = io.histogram({
  name: 'latency',
  measurement: 'mean'
});

const latencyValue = 0;

setInterval(() => {
  latencyValue = Math.round(Math.random() * 100);
  latency.update(latencyValue);
}, 100);

Options are:

  • measurement : default: mean; min, max, sum, count, variance, mean, stddev, median, p75, p95, p99, p99.

Expose Remote Actions: Trigger Functions remotely

Remotely trigger functions from PM2 Plus or Enterprise.

Simple actions

The function takes a function as a parameter (cb here) and need to be called once the job is finished.

Example:

const io = require('@pm2/io');

io.action('db:clean', (cb) => {
  clean.db(() => {
    // cb must be called at the end of the action
    return cb({ success: true });
  });
});

Report user error

By default, in the Issue tab, you are only alerted for uncaught exceptions. Any exception that you catch is not reported. You can manually report them with the notifyError() method.

const io = require('@pm2/io');

io.notifyError(new Error('This is an error'), {
  // you can some http context that will be reported in the UI
  http: {
    url: req.url
  },
  // or anything that you can like an user id
  custom: {
    user: req.user.id
  }
});

Express error reporting

If you want you can configure your express middleware to automatically send you an error with the error middleware of express :

const io = require('@pm2/io')
const express = require('express')
const app = express()

// add the routes that you want
app.use('/toto', () => {
  throw new Error('ajdoijerr')
})

// always add the middleware as the last one
app.use(io.expressErrorHandler())

Koa error reporting

We also expose a custom koa middleware to report error with a specific koa middleware :

const io = require('@pm2/io')
const Koa = require('koa')
const app = new Koa()

// the order isn't important with koa
app.use(pmx.koaErrorHandler())

// add the routes that you want
app.use(async ctx => {
  ctx.throw(new Error('toto'))
})

Configuration

Global configuration object

export class IOConfig {
  /**
   * Automatically catch unhandled errors
   */
  catchExceptions?: boolean = true
  /**
   * Configure the metrics to add automatically to your process
   */
  metrics?: {
    eventLoop: boolean = true,
    network: boolean = false,
    http: boolean = true,
    gc: boolean = true,
    v8: boolean = true
  }
  /**
   * Configure the default actions that you can run
   */
  actions?: {
    eventLoopDump?: boolean = true
  }
  /**
   * Configure availables profilers that will be exposed
   */
  profiling?: {
    /**
     * Toggle the CPU profiling actions
     */
    cpuJS: boolean = true
    /**
     * Toggle the heap snapshot actions
     */
    heapSnapshot: boolean = true
    /**
     * Toggle the heap sampling actions
     */
    heapSampling: boolean = true
    /**
     * Force a specific implementation of profiler
     * 
     * available: 
     *  - 'addon' (using the v8-profiler-node8 addon)
     *  - 'inspector' (using the "inspector" api from node core)
     *  - 'none' (disable the profilers)
     *  - 'both' (will try to use inspector and fallback on addon if available)
     */
    implementation: string = 'both'
  }
  /**
   * Configure the transaction tracing options
   */
  tracing?: {
    /**
     * Choose to enable the HTTP tracing system
     * 
     * default is false
     */
    enabled: boolean = false
    /**
     * Specify specific urls to ignore
     */
    ignoreFilter?: {
      url?: string[]
      method?: string[]
    }
    // Log levels: 0-disabled,1-error,2-warn,3-info,4-debug
    logLevel?: number
    /**
     * To disable a plugin in this list, you may override its path with a falsy
     * value. Disabling any of the default plugins may cause unwanted behavior,
     * so use caution.
     */
    plugins?: {
      connect?: boolean
      express?: boolean
      'generic-pool'?: boolean
      hapi?: boolean
      http?: boolean
      knex?: boolean
      koa?: boolean
      'mongodb-core'?: boolean
      mysql?: boolean
      pg?: boolean
      redis?: boolean
      restify?: boolean
    }
    /**
     * An upper bound on the number of traces to gather each second. If set to 0,
     * sampling is disabled and all traces are recorded. Sampling rates greater
     * than 1000 are not supported and will result in at most 1000 samples per
     * second.
     */
    samplingRate?: number
  }
  /**
   * If you want to connect to PM2 Enterprise without using PM2, you should enable
   * the standalone mode
   * 
   * default is false
   */
  standalone?: boolean = false
  /**
   * Define custom options for the standalone mode
   */
  apmOptions?: {
    /**
     * public key of the bucket to which the agent need to connect
     */
    publicKey: string
    /**
     * Secret key of the bucket to which the agent need to connect
     */
    secretKey: string
    /**
     * The name of the application/service that will be reported to PM2 Enterprise
     */
    appName: string
    /**
     * The name of the server as reported in PM2 Enterprise
     *
     * default is os.hostname()
     */
    serverName?: string
    /**
     * Broadcast all the logs from your application to our backend
     */
    sendLogs?: Boolean
    /**
     * Since logs can be forwared to our backend you may want to ignore specific
     * logs (containing sensitive data for example)
     */
    logFilter?: string | RegExp
    /**
     * Proxy URI to use when reaching internet
     * Supporting socks5,http,https,pac,socks4
     * see https://github.com/TooTallNate/node-proxy-agent
     *
     * example: socks5://username:password@some-socks-proxy.com:9050
     */
    proxy?: string
  }
}

You can pass whatever options you want to io.init, it will automatically update its configuration.

Migration Guides

2.x to 3.x

Here the list of breaking changes :

  • Removed io.scopedAction because of low user adoption
  • Removed io.notify in favor of io.notifyError (droppin replacement)
  • Removed support for gc-stats module
  • Removed Heap profiling support when using the profiler addon (which wasn't possible at all)
  • Removed deep-metrics support (the module that allowed to get metrics about websocket/mongo out of the box), we are working on a better solution.
  • Removed io.transpose
  • Changed the configuration structure

High chance that if you used a custom configuration for io.init, you need to change it to reflect the new configuration. Apart from that and the io.notify removal, it shouldn't break the way you instanciated metrics. If you find something else that breaks please report it to us (tech@keymetrics.io).

Development

To auto rebuild on file change:

$ npm install
$ npm run watch

To test only one file:

$ npm run unit <typescript-file-to-test.ts>

Run transpilation + test + coverage:

$ npm run test

Run transpilation + test only:

$ npm run unit <test>

Notes

Curently this package isn't compatible with amqp if you use the network metrics. We recommend to disable the metrics with the following configuration in this case :

io.init({
  metrics: {
    network: false
  }
})