fix(cluster): add flag to turn off the cluster support

This commit is contained in:
Gareth Jones 2017-09-25 09:47:03 +10:00
parent 9c19fd5e8e
commit f266757fcf
6 changed files with 110 additions and 20 deletions

View File

@ -19,6 +19,7 @@ Properties:
* `level` (string, case insensitive) - the minimum log level that this category will send to the appenders. For example, if set to 'error' then the appenders will only receive log events of level 'error', 'fatal', 'mark' - log events of 'info', 'warn', 'debug', or 'trace' will be ignored.
* `pm2` (boolean) (optional) - set this to true if you're running your app using [pm2](http://pm2.keymetrics.io), otherwise logs will not work (you'll also need to install pm2-intercom)
* `pm2InstanceVar` (string) (optional, defaults to 'NODE_APP_INSTANCE') - set this if you're using pm2 and have changed the default name of the NODE_APP_INSTANCE variable.
* `disableClustering` (boolean) (optional) - set this to true if you liked the way log4js used to just ignore clustered environments, or you're having trouble with PM2 logging. Each worker process will do its own logging. Be careful with this if you're logging to files, weirdness can occur.
## Loggers - `log4js.getLogger([category])`

View File

@ -50,6 +50,9 @@ log4js.configure({
});
```
## FFS, why did you mess with the PM2 stuff? It was working fine for me!
You can turn off the clustering support, with the `disableClustering: true` option in your config. This will make log4js behave more like it did before version 2.x. Each worker process will log its own output, instead of sending it all to the master process. Be careful if you're logging to files though, this could result in weird behaviour.
## NPM complains about nodemailer being deprecated, what should I do?
Nodemailer version 4.0.1 (the not-deprecated version) requires a node version >= 6, but log4js supports node versions >= 4. So until I stop supporting node versions less than 6 I can't update the dependency. It's only an optional dependency anyway, so you're free to install nodemailer@4.0.1 if you want - as far as I know it should work, the API looks the same to me. If you know that the smtp appender definitely doesn't work with nodemailer v4, then please create an issue with some details about the problem.

View File

@ -2,11 +2,17 @@
const util = require('util');
const path = require('path');
const cluster = require('cluster');
const levels = require('./levels');
const layouts = require('./layouts');
const debug = require('debug')('log4js:configuration');
let cluster;
try {
cluster = require('cluster'); // eslint-disable-line global-require
} catch (e) {
debug('Clustering support disabled because require(cluster) threw an error: ', e);
}
const validColours = [
'white', 'grey', 'black',
'blue', 'cyan', 'green',
@ -76,11 +82,11 @@ class Configuration {
debug(`DEPRECATION: Appender ${config.type} exports a shutdown function.`);
}
debug(`cluster.isMaster ? ${cluster.isMaster}`);
debug(`pm2 enabled ? ${this.pm2}`);
debug(`pm2InstanceVar = ${this.pm2InstanceVar}`);
debug(`process.env[${this.pm2InstanceVar}] = ${process.env[this.pm2InstanceVar]}`);
if (cluster.isMaster || (this.pm2 && process.env[this.pm2InstanceVar] === '0')) {
if (this.disableClustering || cluster.isMaster || (this.pm2 && process.env[this.pm2InstanceVar] === '0')) {
debug(`cluster.isMaster ? ${cluster.isMaster}`);
debug(`pm2 enabled ? ${this.pm2}`);
debug(`pm2InstanceVar = ${this.pm2InstanceVar}`);
debug(`process.env[${this.pm2InstanceVar}] = ${process.env[this.pm2InstanceVar]}`);
return appenderModule.configure(
config,
layouts,
@ -199,6 +205,8 @@ class Configuration {
this.throwExceptionIf(not(anObject(candidate.appenders)), 'must have a property "appenders" of type object.');
this.throwExceptionIf(not(anObject(candidate.categories)), 'must have a property "categories" of type object.');
this.disableClustering = this.candidate.disableClustering || !cluster;
this.pm2 = this.candidate.pm2;
this.pm2InstanceVar = this.candidate.pm2InstanceVar || 'NODE_APP_INSTANCE';

View File

@ -24,12 +24,18 @@
*/
const debug = require('debug')('log4js:main');
const fs = require('fs');
const cluster = require('cluster');
const Configuration = require('./configuration');
const connectModule = require('./connect-logger');
const logger = require('./logger');
const layouts = require('./layouts');
let cluster;
try {
cluster = require('cluster'); // eslint-disable-line global-require
} catch (e) {
debug('Clustering support disabled because require(cluster) threw an error: ', e);
}
const defaultConfig = {
appenders: {
stdout: { type: 'stdout' }
@ -147,7 +153,7 @@ function isPM2Master() {
}
function isMaster() {
return cluster.isMaster || isPM2Master();
return config.disableClustering || cluster.isMaster || isPM2Master();
}
/**
@ -199,16 +205,23 @@ function configure(configurationFileOrObject) {
LoggingEvent = loggerModule.LoggingEvent;
module.exports.connectLogger = connectModule(config.levels).connectLogger;
// PM2 cluster support
// PM2 runs everything as workers - install pm2-intercom for this to work.
// we only want one of the app instances to write logs
if (isPM2Master()) {
debug('listening for PM2 broadcast messages');
process.removeListener('message', receiver);
process.on('message', receiver);
} else if (cluster.isMaster) {
cluster.removeListener('message', receiver);
cluster.on('message', receiver);
if (config.disableClustering) {
debug('Not listening for cluster messages, because clustering disabled.');
} else {
// PM2 cluster support
// PM2 runs everything as workers - install pm2-intercom for this to work.
// we only want one of the app instances to write logs
if (isPM2Master()) {
debug('listening for PM2 broadcast messages');
process.removeListener('message', receiver);
process.on('message', receiver);
} else if (cluster.isMaster) {
debug('listening for cluster messages');
cluster.removeListener('message', receiver);
cluster.on('message', receiver);
} else {
debug('not listening for messages, because we are not a master process');
}
}
enabled = true;

View File

@ -3,7 +3,13 @@
'use strict';
const debug = require('debug')('log4js:logger');
const cluster = require('cluster');
let cluster;
try {
cluster = require('cluster'); // eslint-disable-line global-require
} catch (e) {
debug('Clustering support disabled because require(cluster) threw an error: ', e);
}
/**
* @name LoggingEvent
@ -25,7 +31,7 @@ class LoggingEvent {
this.level = level;
this.context = Object.assign({}, context);
this.pid = process.pid;
if (cluster.isWorker) {
if (cluster && cluster.isWorker) {
this.cluster = {
workerId: cluster.worker.id,
worker: process.pid

View File

@ -0,0 +1,59 @@
'use strict';
const test = require('tap').test;
const cluster = require('cluster');
const log4js = require('../../lib/log4js');
const recorder = require('../../lib/appenders/recording');
cluster.removeAllListeners();
log4js.configure({
appenders: {
vcr: { type: 'recording' }
},
categories: { default: { appenders: ['vcr'], level: 'debug' } },
disableClustering: true
});
if (cluster.isMaster) {
cluster.fork();
const masterLogger = log4js.getLogger('master');
const masterPid = process.pid;
masterLogger.info('this is master');
cluster.on('exit', () => {
const logEvents = recorder.replay();
test('cluster master', (batch) => {
batch.test('only master events should be logged', (t) => {
t.equal(logEvents.length, 1);
t.equal(logEvents[0].categoryName, 'master');
t.equal(logEvents[0].pid, masterPid);
t.equal(logEvents[0].data[0], 'this is master');
t.end();
});
batch.end();
});
});
} else {
const workerLogger = log4js.getLogger('worker');
workerLogger.info('this is worker', new Error('oh dear'));
const workerEvents = recorder.replay();
test('cluster worker', (batch) => {
batch.test('should send events to its own appender', (t) => {
t.equal(workerEvents.length, 1);
t.equal(workerEvents[0].categoryName, 'worker');
t.equal(workerEvents[0].data[0], 'this is worker');
t.type(workerEvents[0].data[1], 'Error');
t.contains(workerEvents[0].data[1].stack, 'Error: oh dear');
t.end();
});
batch.end();
});
// test sending a cluster-style log message
process.send({ topic: 'log4js:message', data: { cheese: 'gouda' } });
cluster.worker.disconnect();
}