Features

Germain APM can monitor availability, performance and usage of any Node.js application. The main features of the Germain APM Node.js monitoring are:

  • Error and Exception monitoring

  • Warning monitoring

  • Exit monitoring

  • HTTP monitoring

  • Express monitoring

  • Event loop latency monitoring

  • Startup duration monitoring

  • Transaction monitoring

  • Process resources monitoring

  • OS resources monitoring

  • Disk monitoring

  • Multiple promise resolve/rejection monitoring

Requirements

  • npm package manager integration

  • Node.js v14 or higher

Installation

npm Installation

Germain APM monitoring package is available here: https://www.npmjs.com/package/@germainapm/nodejs-monitoring

Run the following command to add our monitoring into your Node.js application:

npm i --save @germainapm/nodejs-monitoring
JS

Yarn Installation

Germain APM monitoring package is available here: https://yarnpkg.com/package/@germainapm/nodejs-monitoring

Run the following command to add our monitoring into your Node.js application:

yarn add @germainapm/nodejs-monitoring
JS

Initialization

Initialize germain APM monitoring in your main application javascript file (e.g. server.js). In the simplest integration only the Germain APM server URL (where the monitoring data will be sent - contact the germain Team if you don't know it) must be provided and the rest will be autoconfigured.

Integration example using RequireJS:

const GermainAPM = require('@germainapm/nodejs-monitoring');

const config = GermainAPM.getDefaultConfiguration('http://GERMAIN_APM_SERVER/');
const apm = new GermainAPM(config);
apm.start();
JS

Integration example using ES6:

import GermainAPM from '@germainapm/nodejs-monitoring';

const config = GermainAPM.getDefaultConfiguration('http://GERMAIN_APM_SERVER/');
const apm = new GermainAPM(config);
apm.start();
JS

Germain APM UX with Incoming HTTP

Germain APM allows you to tag UX monitoring data and correlate it with incoming http traffic in Node.js. To allow this correlation make sure UX is configured to tag data and Node.js is configured to allow custom http headers.

Germain APM UX configuration (taggingEnabled flag must be set to true):

const settings = germainApm.getDefaultSettings(loaderArgs, agentConfig);
settings.plugins.network.trackingEnabled = true;
JS

Node.js configuration to allow germain-apm-sequence custom http header:

app.use(function(req, res, next) {
  	res.header("Access-Control-Allow-Origin", "*");
  	res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, germain-apm-sequence");
  	next();
});
JS

Configuration

In addition to the initialization and auto-configuration we allow customers to define custom configuration and enable/disable monitoring components. Please review GermainAPMConfiguration documentation for the complete list of options and setters.

Type definition for GermainAPMConfiguration object:

type GermainAPMConfiguration = {
    /** Germain APM environment URL (e.g. http://localhost:8080). */
    servicesUrl: string;
    /** Config to enable/disable/customize what is monitored */
    monitoring: {
        /** How often periodic monitoring should be collected. Default: 60 */
        frequencySeconds: number;
        /** Whether to enable correlation of data across async callbacks. Default: true */
        asyncCorrelation: boolean;
        /** Unhandled Exception monitoring */
        unhandledExceptions: {
            /** Enable monitoring. Default: true */
            enabled: boolean;
            /** Whether to exit the Node process on error. Default: true */
            exitOnError: boolean;
            /** Callback to be fired when an error occurs. */
            callbackOnError?: (error: Error) => void;
        },
        /** Incoming HTTP monitoring */
        httpIncoming: {
            /** Enable monitoring. Default: true */
            enabled: boolean;
            /** Function to extract a sessionId from an incoming request */
            sessionExtractor?: (request: IncomingMessage) => string;
        },
        /** Outgoing HTTP monitoring */
        httpOutgoing: {
            /** Enable monitoring. Default: true */
            enabled: boolean;
            /** Function to extract a sessionId from an incoming request */
            sessionExtractor?: (response: ClientRequest) => string;
        },
        /** Node Startup time monitoring. Default: true */
        startup: true,
        /** Node Process monitoring. Default: true */
        pid: boolean;
        /** OS monitoring (CPU, Memory). Default: true */
        os: boolean;
        /** Disk monitoring. Default: true */
        disk: boolean;
        /** Node Warning monitoring. Default: true */
        warning: boolean;
        /** Unhandled Promise Rejection count monitoring. Default: true */
        unhandledPromiseRejection: boolean;
        /** Event Loop Latency monitoring. Default: true */
        eventLoopLatency: boolean;
        /** Exit monitoring - when Node shuts down. Default: true */
        exit: boolean;
        /** Multiple Promise Resolve monitoring. Default: true */
        multiplePromiseResolves: boolean;
    },
    /** Logging configuration */
    logging: {
        /** Level of logging to enable. Default: 'info' */
        level: 'error' | 'warn' | 'info' | 'verbose' | 'debug',
        /** Filename to log to. Default: 'GermainAPM.log' */
        filename: string;
        /** Max number of rolling files to keep. Default: 10 */
        maxFiles: number;
        /** Max size of a single rolling log file. Default: 10Mb */
        maxSize: number;
    },
    /** Data sending settings */
    datapoints: {
        /** Whether to send data to the server or just discard. This is a debug setting as should always be set true. Default: true */
        sendData: boolean;
        /** Whether outgoing data should be compressed to reduce network overhead. Default: true */
        compress: boolean;
        /** Max number of datapoints to keep in-memory before sending to the server. Default: 250 */
        maxNumInMemory: number;
        /** Max time to wait between sending data to server. Default: 60 */
        maxFlushTimeSeconds: number;
    },
    /** Default data to be added to datapoints */
    defaults: {
        /** Default user */
        user?: UserDimension;
        /** Default system */
        system?: SystemDimension;
        /** Default application */
        application?: ApplicationDimension;
        /** Default pid */
        pid?: string;
    }
    /** Exclusions to be applied to collected datapoints */
    exclusions: FieldExclusionConfig[]
}
TYPESCRIPT

Monitoring Frequency

Some monitoring components such as Node.js process resource monitoring run periodically. By default they run every 60 seconds but you can be customized as follows

const config = GermainAPM.getDefaultConfiguration('http://GERMAIN_APM_SERVER/');
config.monitoring.frequencySeconds = 60;
JS

Asynchronous Correlation

By default Germain APM performs correlation between incoming HTTP requests and asynchronous outgoing HTTP requests. This feature has a minor performance overhead and can be disabled as follows. 

const config = GermainAPM.getDefaultConfiguration('http://GERMAIN_APM_SERVER/');
config.monitoring.asyncCorrelation = false;
JS

Distribution Frequency

By default Germain APM monitoring sends data back to the Germain APM Server every 30 seconds. You can update this frequency by setting maxFlushTimeSeconds:

const config = GermainAPM.getDefaultConfiguration('http://GERMAIN_APM_SERVER/');
config.datapoints.maxFlushTimeSeconds = 60;
JS

Application 

You can associate an application name with all collected data. The simplest way to provide an application name is by passing it as the second (optional) argument, when creating the default settings:

const config = GermainAPM.getDefaultConfiguration('http://GERMAIN_APM_SERVER/', 'APP_NAME');
JS

Alternatively you can provide the application name and application component by providing a configuration object:

const config = GermainAPM.getDefaultConfiguration('http://GERMAIN_APM_SERVER/');
config.defaults.application = { name: 'APP_NAME', component: 'APP_COMPONENT' };
JS

Logging output

By default, logging (info level and above) is enabled when Germain APM monitoring gets initiated. You can change this configuration through GermainAPMConfiguration:

const config = GermainAPM.getDefaultConfiguration('http://GERMAIN_APM_SERVER/');
config.logging.level = 'debug';
config.logging.filename = 'GermainAPM_custom.log';
config.logging.maxFiles = 20;
config.logging.maxSize = 20000000; // in bytes
JS

Data Exclusion

By default all captured data is sent to the APM server, however it may be necessary to exclude or anonymize some data due to security concerns. The exclusions section of the GermainAPMConfiguration allows to configure which fields (per fact type or globally) should be excluded or anonymized.

  • Masking - The string will be replaced with * characters.

  • Anonymization - The string will be hashed so the original string cannot be read. This allows the same value to be hashed globally so it can be used to anonymously associate data.

  • Exclusion - The string will be replaced with an empty string.

const config = GermainAPM.getDefaultConfiguration('http://GERMAIN_APM_SERVER/');
config.exclusions = [{
  fieldName: 'sessionId',				// The field that will be anonymized
  factType: '',							// The fact type that this exclusion applies to, if empty it will apply to all facts
  type: 'MASK',                         // The type of exclusion to apply, one of 'MASK', 'ANONYMIZE' or 'EXCLUDE'
  name: 'SessionId Anonymization',		// A readable name for this exclusion
  pattern: '',							// A regexp to allow anonymization/exclusion of matching groups within a string
  preserveLength: true,					// If true (and type is 'MASK') the length of the excluded string will be preserved
  preserveWhitespace: true,				// If true (and type is 'MASK') the whitespaces within the excluded string will be preserved
  enabled: true							// Allows to enable/disable this exclusion
}, {
  fieldName: 'requestBody',
  factType: 'NodeJS:Outbound',
  type: 'ANONYMIZE',
  name: 'Request Body Exclusion',
  pattern: '',
  preserveLength: true,
  preserveWhitespace: false,
  enabled: true
}];
JS

Integration

Error and Exception Monitoring

This monitoring can collect all types of custom and standard errors (AssertionError, EvalError, SyntaxError, RangeError, ReferenceError, TypeError, URIError) and all system errors including:

  • Permission denied

  • Address already in use

  • Connection refused

  • Connection reset by peer

  • File exists

  • Is a directory

  • Too many open files in system

  • No such file or directory

  • Not a directory

  • Directory not empty

  • Operation not permitted

  • Broken pipe

  • Operation timed out

Configuration

const config = GermainAPM.getDefaultConfiguration('http://GERMAIN_APM_SERVER/');
config.monitoring.unhandledExceptions.enabled = true;           // Enable/Disable automatic unhandled exception monitoring. Default: true.
config.monitoring.unhandledExceptions.exitOnError = true;       // Process exit on unhandled exception (with exit code 1). Default: true.
config.monitoring.unhandledExceptions.callbackOnError = null;   // Callback on unhandled exception provided as a function if exitOnError=false. Callback function will receive Error instance as the only parameter. Default: empty / no callback.
JS

Unhandled exceptions

Caught by process

If you already catching all unhandled exceptions by listening to the uncaughtException event on the process then you should explicitly collect the exception in your exception handler like this and disable config.monitoring.unhandledExceptions:

process.on('uncaughtException', function (err) {
    GermainAPM.collectError(err);
});
JS
Uncaught by process

If you don't catch unhandled exceptions yet, you have 2 options to use our monitoring to catch them:

Exit on Error

If you set the exitOnError:true then, once the exception caught, we will exit from the process with id 1 (process.exit(1)). Node.js does recommend to exit from the process following an unhandled exception.

Correct configuration for this scenario:

const config = GermainAPM.getDefaultConfiguration('http://GERMAIN_APM_SERVER/');
config.monitoring.unhandledExceptions.enabled = true;
config.monitoring.unhandledExceptions.exitOnError = true;
config.monitoring.unhandledExceptions.callbackOnError = null;
JS
Callback on Error

If you set the exitOnError:false but provide a function callback to the callbackOnError then, once the exception is caught, we will execute your callback by passing the Error instance as the only parameter. Node.js doesn't recommend to proceed with any logic if an unhandled exception occurs.

Correct configuration for this scenario:

const config = GermainAPM.getDefaultConfiguration('http://GERMAIN_APM_SERVER/');
config.monitoring.unhandledExceptions.enabled = true;
config.monitoring.unhandledExceptions.exitOnError = false;
config.monitoring.unhandledExceptions.callbackOnError = function(error){
  // your logic to process the exception
};
JS
Disable automatic unhandled exceptions monitoring 

You can disable this monitoring through GermainAPMConfiguration:

const config = GermainAPM.getDefaultConfiguration('http://GERMAIN_APM_SERVER/');
config.monitoring.unhandledExceptions.enabled = false;
JS

Handled exceptions

You can collect an exception / an error explicitly by adding the following code to your handled exception logic:

try {
   // ...
} catch(err){
   GermainAPM.collectError(err);
   // your logic here ...
}
JS

Warning Monitoring

By default Germain APM monitors Node.js warnings (bad coding practices, bugs, or security vulnerabilities). You can disable this monitoring through GermainAPMConfiguration:

const config = GermainAPM.getDefaultConfiguration('http://GERMAIN_APM_SERVER/');
config.monitoring.warning = false;
JS

Exit Monitoring

By default Germain APM monitors Node.js process exit events. You can disable this monitoring through GermainAPMConfiguration:

const config = GermainAPM.getDefaultConfiguration('http://GERMAIN_APM_SERVER/');
config.monitoring.exit = false;
JS

HTTP Monitoring

Incoming HTTP Monitoring

By default Germain APM monitors incoming http traffic. You can disable this monitoring through GermainAPMConfiguration:

const config = GermainAPM.getDefaultConfiguration('http://GERMAIN_APM_SERVER/');
config.monitoring.httpIncoming.enabled = false;
JS
Session extraction

In addition you can provide a session extractor function to extract session ids from the incoming http traffic. By default, no session extractor is provided. 

Example below shows how you can extract an existing session id from incoming http requests' headers and pass it on to the germain APM monitoring :

const config = GermainAPM.getDefaultConfiguration('http://GERMAIN_APM_SERVER/');
config.monitoring.httpIncoming.sessionExtractor = function(incomingMessage) { // you receive an instance of the <http.incomingMessage>
  return incomingMessage.headers.sessionId;
};
JS

Outgoing HTTP Monitoring

By default Germain APM monitors outgoing http traffic. You can disable this monitoring through GermainAPMConfiguration:

const config = GermainAPM.getDefaultConfiguration('http://GERMAIN_APM_SERVER/');
config.monitoring.httpOutgoing.enabled = false;
JS
Session extraction

In addition you can provide a session extractor function to extract session ids from the outgoing http traffic. By default, no session extractor is provided. 

Example below shows how you can extract an existing session id from outgoing http requests' headers and pass it on to the germain APM monitoring :

const config = GermainAPM.getDefaultConfiguration('http://GERMAIN_APM_SERVER/');
config.monitoring.httpOutgoing.sessionExtractor = function(clientRequest) { // you receive an instance of the <http.clientRequest>
  return clientRequest.getHeader('sessionId');
};
JS

Event Loop Latency Monitoring

By default Germain APM monitors Node.js event loop latency. You can disable this monitoring through GermainAPMConfiguration:

const config = GermainAPM.getDefaultConfiguration('http://GERMAIN_APM_SERVER/');
config.monitoring.eventLoopLatency = false;
JS

Startup Monitoring

By default Germain APM monitors Node.js startup duration as time between the current Node.js process startup and Germain APM initialization. You can disable this automatic feature and call it explicitly when you think the startup has been fully completed:

GermainAPM.collectStartup();
JS

You can disable this monitoring through GermainAPMConfiguration:

const config = GermainAPM.getDefaultConfiguration('http://GERMAIN_APM_SERVER/');
config.monitoring.startup = false;
JS

Transaction Monitoring

Germain APM allows you to monitor all kinds of transactions in your Node.js application. To enable this monitoring you need to programmatically set the start of a transaction (GermainAPM.startTransaction()) and the end of a transaction (GermainAPM.endTransaction()).

Start a transaction:

GermainAPM.startTransaction('UNIQUE_TRANSACTION_NAME', 'AdditionalInformation'); // AdditionalInformation is optional, you can pass in null value
JS

End started transaction:

GermainAPM.endTransaction('UNIQUE_TRANSACTION_NAME');
JS

Process resources Monitoring

By default Germain APM monitors CPU, Memory Usage, Heap Size and Heap Usage of your Node.js process. You can disable this monitoring through GermainAPMConfiguration:

const config = GermainAPM.getDefaultConfiguration('http://GERMAIN_APM_SERVER/');
config.monitoring.pid = false;
JS

OS resources Monitoring

By default Germain APM monitors CPU and Memory Usage of the server on which your Node.js application runs. You can disable this monitoring through GermainAPMConfiguration:

const config = GermainAPM.getDefaultConfiguration('http://GERMAIN_APM_SERVER/');
config.monitoring.os = false;
JS

Disk Monitoring

By default Germain APM monitors Disk Usage of the server on which your Node.js application runs. You can disable this monitoring through GermainAPMConfiguration:

const config = GermainAPM.getDefaultConfiguration('http://GERMAIN_APM_SERVER/');
config.monitoring.disk = false;
JS

Multiple Promise Resolve/Rejection Monitoring

By default Germain APM monitors when a promise has been resolved or rejected multiple times. You can disable this monitoring through GermainAPMConfiguration:

const config = GermainAPM.getDefaultConfiguration('http://GERMAIN_APM_SERVER/');
config.monitoring.multiplePromiseResolves = false;
JS