mirror of
https://github.com/log4js-node/log4js-node.git
synced 2025-12-08 19:26:01 +00:00
Merge pull request #660 from log4js-node/tcp-refactor
TCP Appender Refactor
This commit is contained in:
commit
387d8ba84b
@ -5,7 +5,5 @@ node_js:
|
||||
- "8"
|
||||
- "7"
|
||||
- "6"
|
||||
- "5"
|
||||
- "4"
|
||||
after_success:
|
||||
- npm run codecov
|
||||
|
||||
@ -35,13 +35,15 @@ The following appenders are included with log4js. Some require extra dependencie
|
||||
* [mailgun](mailgun.md)
|
||||
* [multiFile](multiFile.md)
|
||||
* [multiprocess](multiprocess.md)
|
||||
* [rabbitmq](rabbitmq.md)
|
||||
* [recording](recording.md)
|
||||
* [redis](redis.md)
|
||||
* [slack](slack.md)
|
||||
* [smtp](smtp.md)
|
||||
* [stderr](stderr.md)
|
||||
* [stdout](stdout.md)
|
||||
* [rabbitmq](rabbitmq.md)
|
||||
* [tcp](tcp.md)
|
||||
* [tcp-server](tcp-server.md)
|
||||
|
||||
## Other Appenders
|
||||
|
||||
|
||||
28
docs/clustering.md
Normal file
28
docs/clustering.md
Normal file
@ -0,0 +1,28 @@
|
||||
# Clustering / Multi-process Logging
|
||||
|
||||
If you're running log4js in an application that uses [node's core cluster](https://nodejs.org/dist/latest-v8.x/docs/api/cluster.html) then log4js will transparently handle making sure the processes don't try to log at the same time. All logging is done on the master process, with the worker processes sending their log messages to the master via `process.send`. This ensures that you don't get multiple processes trying to write to the same file (or rotate the log files) at the same time.
|
||||
|
||||
This can cause problems in some rare circumstances, if you're experiencing weird logging problems, then use the `disableClustering: true` option in your log4js configuration to have every process behave as if it were the master process. Be careful if you're logging to files.
|
||||
|
||||
## I'm using PM2, but I'm not getting any logs!
|
||||
To get log4js working with [PM2](http://pm2.keymetrics.io), you'll need to install the [pm2-intercom](https://www.npmjs.com/package/pm2-intercom) module.
|
||||
```bash
|
||||
pm2 install pm2-intercom
|
||||
```
|
||||
Then add the value `pm2: true` to your log4js configuration. If you're also using `node-config`, then you'll probably have renamed your `NODE_APP_INSTANCE` environment variable. If so, you'll also need to add `pm2InstanceVar: '<NEW_APP_INSTANCE_ID>'` where `<NEW_APP_INSTANCE_ID>` should be replaced with the new name you gave the instance environment variable.
|
||||
```javascript
|
||||
log4js.configure({
|
||||
appenders: { out: { type: 'stdout'}},
|
||||
categories: { default: { appenders: ['out'], level: 'info'}},
|
||||
pm2: true,
|
||||
pm2InstanceVar: 'INSTANCE_ID'
|
||||
});
|
||||
```
|
||||
|
||||
## I'm using Passenger, but I'm not getting any logs!
|
||||
|
||||
[Passenger](https://www.phusionpassenger.com/library/) replaces the node.js core cluster module with a non-functional stub, so you won't see any output using log4js. To fix this, add `disableClustering: true` to your configuration. Again, be careful if you're logging to files.
|
||||
|
||||
## I'm not using clustering/pm2/passenger but I do have multiple processes that I'd like to all log to the same place
|
||||
|
||||
Ok, you probably want to look at the [tcp-server](tcp-server.md) and [tcp appender](tcp.md) documentation.
|
||||
18
docs/faq.md
18
docs/faq.md
@ -35,23 +35,9 @@ const logger = log4js.getLogger('console');
|
||||
console.log = logger.info.bind(logger); // do the same for others - console.debug, etc.
|
||||
```
|
||||
|
||||
## I'm using PM2, but I'm not getting any logs!
|
||||
To get log4js working with PM2, you'll need to install the [pm2-intercom](https://www.npmjs.com/package/pm2-intercom) module.
|
||||
```bash
|
||||
pm2 install pm2-intercom
|
||||
```
|
||||
Then add the value `pm2: true` to your log4js configuration. If you're also using `node-config`, then you'll probably have renamed your `NODE_APP_INSTANCE` environment variable. If so, you'll also need to add `pm2InstanceVar: '<NEW_APP_INSTANCE_ID>'` where `<NEW_APP_INSTANCE_ID>` should be replaced with the new name you gave the instance environment variable.
|
||||
```javascript
|
||||
log4js.configure({
|
||||
appenders: { out: { type: 'stdout'}},
|
||||
categories: { default: { appenders: ['out'], level: 'info'}},
|
||||
pm2: true,
|
||||
pm2InstanceVar: 'INSTANCE_ID'
|
||||
});
|
||||
```
|
||||
## I'm using pm2/passenger/some other third thing and I'm not getting any logs!
|
||||
|
||||
## 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.
|
||||
Take a look at the [clustering](clustering.md) docs, they should help you out.
|
||||
|
||||
## NPM complains about nodemailer being deprecated, what should I do?
|
||||
|
||||
|
||||
@ -16,7 +16,7 @@ There have been a few changes between log4js 1.x and 2.x (and 0.x too). You shou
|
||||
* [Loggly appender](loggly.md)
|
||||
* [Logstash UDP appender](logstashUDP.md)
|
||||
* logFaces ([UDP](logFaces-UDP.md) and [HTTP](logFaces-HTTP.md)) appender
|
||||
* [multiprocess appender](multiprocess.md) (useful when you've got multiple servers but want to centralise logging)
|
||||
* [TCP appender](tcp.md) (useful when you've got multiple servers but want to centralise logging)
|
||||
* a [logger for connect/express](connect-logger.md) servers
|
||||
* configurable log message [layout/patterns](layouts.md)
|
||||
* different log levels for different log categories (make some parts of your app log as DEBUG, others only ERRORS, etc.)
|
||||
@ -38,6 +38,9 @@ logger.level = 'debug'; // default level is OFF - which means no logs at all.
|
||||
logger.debug("Some debug messages");
|
||||
```
|
||||
|
||||
## Clustering
|
||||
If you use node's cluster, or passenger, or pm2, then you should read this [clustering guide](clustering.md)
|
||||
|
||||
## Note for library makers
|
||||
|
||||
If you're writing a library and would like to include support for log4js, without introducing a dependency headache for your users, take a look at [log4js-api](https://github.com/log4js-node/log4js-api).
|
||||
|
||||
@ -1,8 +1,11 @@
|
||||
# Multiprocess Appender
|
||||
|
||||
*You probably want to use the [tcp server](tcp-server.md) or [tcp appender](tcp.md) instead of this - they are more flexible*
|
||||
|
||||
*Note that if you're just using node core's `cluster` module then you don't need to use this appender - log4js will handle logging within the cluster transparently.*
|
||||
|
||||
The multiprocess appender sends log events to a master server over TCP sockets. It can be used as a simple way to centralise logging when you have multiple servers or processes. It uses the node.js core networking modules, and so does not require any extra dependencies. Remember to call `log4js.shutdown` when your application terminates, so that the sockets get closed cleanly.
|
||||
|
||||
Note that if you're just using node core's `cluster` module then you don't need to use this appender - log4js will handle logging within the cluster transparently.
|
||||
|
||||
## Configuration
|
||||
|
||||
|
||||
23
docs/tcp-server.md
Normal file
23
docs/tcp-server.md
Normal file
@ -0,0 +1,23 @@
|
||||
# TCP Server Appender
|
||||
|
||||
Strictly speaking, this is not an appender - but it is configured as one. The TCP server listens for log messages on a port, taking JSON-encoded log events and then forwarding them to the other appenders. It can be used as a simple way to centralise logging when you have multiple servers or processes. It uses the node.js core networking modules, and so does not require any extra dependencies. Remember to call `log4js.shutdown` when your application terminates, so that the sockets get closed cleanly. It is designed to work with the [tcp appender](tcp.md), but could work with anything that sends correctly formatted JSON log events.
|
||||
|
||||
## Configuration
|
||||
|
||||
* `type` - `tcp-server`
|
||||
* `port` - `integer` (optional, defaults to `5000`) - the port to listen on
|
||||
* `host` - `string` (optional, defaults to `localhost`) - the host/IP address to listen on
|
||||
|
||||
## Example (master)
|
||||
```javascript
|
||||
log4js.configure({
|
||||
appenders: {
|
||||
file: { type: 'file', filename: 'all-the-logs.log' },
|
||||
server: { type: 'tcp-server', host: '0.0.0.0' }
|
||||
},
|
||||
categories: {
|
||||
default: { appenders: ['file'], level: 'info' }
|
||||
}
|
||||
});
|
||||
```
|
||||
This creates a log server listening on port 5000, on all IP addresses the host has assigned to it. Note that the appender is not included in the appenders listed for the categories. All events received on the socket will be forwarded to the other appenders, as if they had originated on the same server.
|
||||
22
docs/tcp.md
Normal file
22
docs/tcp.md
Normal file
@ -0,0 +1,22 @@
|
||||
# TCP Appender
|
||||
|
||||
The TCP appender sends log events to a master server over TCP sockets. It can be used as a simple way to centralise logging when you have multiple servers or processes. It uses the node.js core networking modules, and so does not require any extra dependencies. Remember to call `log4js.shutdown` when your application terminates, so that the sockets get closed cleanly. It's designed to work with the [tcp-server](tcp-server.md), but it doesn't necessarily have to, just make sure whatever is listening at the other end is expecting JSON objects as strings.
|
||||
|
||||
## Configuration
|
||||
|
||||
* `type` - `tcp`
|
||||
* `port` - `integer` (optional, defaults to `5000`) - the port to send to
|
||||
* `host` - `string` (optional, defaults to `localhost`) - the host/IP address to send to
|
||||
|
||||
## Example
|
||||
```javascript
|
||||
log4js.configure({
|
||||
appenders: {
|
||||
network: { type: 'tcp', host: 'log.server' }
|
||||
},
|
||||
categories: {
|
||||
default: { appenders: ['network'], level: 'error' }
|
||||
}
|
||||
});
|
||||
```
|
||||
This will send all error messages to `log.server:5000`.
|
||||
13
examples/layouts.js
Normal file
13
examples/layouts.js
Normal file
@ -0,0 +1,13 @@
|
||||
const log4js = require('../lib/log4js');
|
||||
|
||||
log4js.configure({
|
||||
appenders: {
|
||||
out: { type: 'stdout', layout: { type: 'messagePassThrough' } }
|
||||
},
|
||||
categories: {
|
||||
default: { appenders: ['out'], level: 'info' }
|
||||
}
|
||||
});
|
||||
|
||||
const logger = log4js.getLogger('thing');
|
||||
logger.info('This should not have a timestamp');
|
||||
72
lib/LoggingEvent.js
Normal file
72
lib/LoggingEvent.js
Normal file
@ -0,0 +1,72 @@
|
||||
const CircularJSON = require('circular-json');
|
||||
const levels = require('./levels');
|
||||
|
||||
/**
|
||||
* @name LoggingEvent
|
||||
* @namespace Log4js
|
||||
*/
|
||||
class LoggingEvent {
|
||||
/**
|
||||
* Models a logging event.
|
||||
* @constructor
|
||||
* @param {String} categoryName name of category
|
||||
* @param {Log4js.Level} level level of message
|
||||
* @param {Array} data objects to log
|
||||
* @author Seth Chisamore
|
||||
*/
|
||||
constructor(categoryName, level, data, context) {
|
||||
this.startTime = new Date();
|
||||
this.categoryName = categoryName;
|
||||
this.data = data;
|
||||
this.level = level;
|
||||
this.context = Object.assign({}, context);
|
||||
this.pid = process.pid;
|
||||
}
|
||||
|
||||
serialise() {
|
||||
const logData = this.data.map((e) => {
|
||||
// JSON.stringify(new Error('test')) returns {}, which is not really useful for us.
|
||||
// The following allows us to serialize errors correctly.
|
||||
if (e && e.stack && CircularJSON.stringify(e) === '{}') {
|
||||
e = { message: e.message, stack: e.stack };
|
||||
}
|
||||
return e;
|
||||
});
|
||||
this.data = logData;
|
||||
return CircularJSON.stringify(this);
|
||||
}
|
||||
|
||||
static deserialise(serialised) {
|
||||
let event;
|
||||
try {
|
||||
const rehydratedEvent = CircularJSON.parse(serialised);
|
||||
rehydratedEvent.data = rehydratedEvent.data.map((e) => {
|
||||
if (e && e.stack) {
|
||||
const fakeError = new Error(e.message);
|
||||
fakeError.stack = e.stack;
|
||||
e = fakeError;
|
||||
}
|
||||
return e;
|
||||
});
|
||||
event = new LoggingEvent(
|
||||
rehydratedEvent.categoryName,
|
||||
levels.getLevel(rehydratedEvent.level.levelStr),
|
||||
rehydratedEvent.data,
|
||||
rehydratedEvent.context
|
||||
);
|
||||
event.startTime = new Date(rehydratedEvent.startTime);
|
||||
event.pid = rehydratedEvent.pid;
|
||||
event.cluster = rehydratedEvent.cluster;
|
||||
} catch (e) {
|
||||
event = new LoggingEvent(
|
||||
'log4js',
|
||||
levels.ERROR,
|
||||
['Unable to parse log:', serialised, 'because: ', e]
|
||||
);
|
||||
}
|
||||
|
||||
return event;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = LoggingEvent;
|
||||
@ -1,9 +1,13 @@
|
||||
'use strict';
|
||||
|
||||
const debug = require('debug')('log4js:categoryFilter');
|
||||
|
||||
function categoryFilter(excludes, appender) {
|
||||
if (typeof excludes === 'string') excludes = [excludes];
|
||||
return (logEvent) => {
|
||||
debug(`Checking ${logEvent.categoryName} against ${excludes}`);
|
||||
if (excludes.indexOf(logEvent.categoryName) === -1) {
|
||||
debug('Not excluded, sending to appender');
|
||||
appender(logEvent);
|
||||
}
|
||||
};
|
||||
|
||||
102
lib/appenders/index.js
Normal file
102
lib/appenders/index.js
Normal file
@ -0,0 +1,102 @@
|
||||
const path = require('path');
|
||||
const debug = require('debug')('log4js:appenders');
|
||||
const configuration = require('../configuration');
|
||||
const clustering = require('../clustering');
|
||||
const levels = require('../levels');
|
||||
const layouts = require('../layouts');
|
||||
|
||||
// pre-load the core appenders so that webpack can find them
|
||||
const coreAppenders = new Map();
|
||||
coreAppenders.set('console', require('./console'));
|
||||
coreAppenders.set('stdout', require('./stdout'));
|
||||
coreAppenders.set('stderr', require('./stderr'));
|
||||
coreAppenders.set('file', require('./file'));
|
||||
coreAppenders.set('dateFile', require('./dateFile'));
|
||||
|
||||
const appenders = new Map();
|
||||
|
||||
const tryLoading = (modulePath, config) => {
|
||||
debug('Loading module from ', modulePath);
|
||||
try {
|
||||
return require(modulePath); //eslint-disable-line
|
||||
} catch (e) {
|
||||
// if the module was found, and we still got an error, then raise it
|
||||
configuration.throwExceptionIf(
|
||||
config,
|
||||
e.code !== 'MODULE_NOT_FOUND',
|
||||
`appender "${modulePath}" could not be loaded (error was: ${e})`
|
||||
);
|
||||
return undefined;
|
||||
}
|
||||
};
|
||||
|
||||
const loadAppenderModule = (type, config) => coreAppenders.get(type) ||
|
||||
tryLoading(`./${type}`, config) ||
|
||||
tryLoading(type, config) ||
|
||||
tryLoading(path.join(path.dirname(require.main.filename), type), config) ||
|
||||
tryLoading(path.join(process.cwd(), type), config);
|
||||
|
||||
const createAppender = (name, config) => {
|
||||
const appenderConfig = config.appenders[name];
|
||||
const appenderModule = loadAppenderModule(appenderConfig.type, config);
|
||||
configuration.throwExceptionIf(
|
||||
config,
|
||||
configuration.not(appenderModule),
|
||||
`appender "${name}" is not valid (type "${appenderConfig.type}" could not be found)`
|
||||
);
|
||||
if (appenderModule.appender) {
|
||||
debug(`DEPRECATION: Appender ${appenderConfig.type} exports an appender function.`);
|
||||
}
|
||||
if (appenderModule.shutdown) {
|
||||
debug(`DEPRECATION: Appender ${appenderConfig.type} exports a shutdown function.`);
|
||||
}
|
||||
|
||||
debug(`${name}: clustering.isMaster ? ${clustering.isMaster()}`);
|
||||
debug(`${name}: appenderModule is ${require('util').inspect(appenderModule)}`); // eslint-disable-line
|
||||
return clustering.onlyOnMaster(() => {
|
||||
debug(`calling appenderModule.configure for ${name} / ${appenderConfig.type}`);
|
||||
return appenderModule.configure(
|
||||
appenderConfig,
|
||||
layouts,
|
||||
appender => appenders.get(appender),
|
||||
levels
|
||||
);
|
||||
}, () => {});
|
||||
};
|
||||
|
||||
const setup = (config) => {
|
||||
appenders.clear();
|
||||
|
||||
Object.keys(config.appenders).forEach((name) => {
|
||||
debug(`Creating appender ${name}`);
|
||||
appenders.set(name, createAppender(name, config));
|
||||
});
|
||||
};
|
||||
|
||||
setup({ appenders: { out: { type: 'stdout' } } });
|
||||
|
||||
configuration.addListener((config) => {
|
||||
configuration.throwExceptionIf(
|
||||
config,
|
||||
configuration.not(configuration.anObject(config.appenders)),
|
||||
'must have a property "appenders" of type object.'
|
||||
);
|
||||
const appenderNames = Object.keys(config.appenders);
|
||||
configuration.throwExceptionIf(
|
||||
config,
|
||||
configuration.not(appenderNames.length),
|
||||
'must define at least one appender.'
|
||||
);
|
||||
|
||||
appenderNames.forEach((name) => {
|
||||
configuration.throwExceptionIf(
|
||||
config,
|
||||
configuration.not(config.appenders[name].type),
|
||||
`appender "${name}" is not valid (must be an object with property "type")`
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
configuration.addListener(setup);
|
||||
|
||||
module.exports = appenders;
|
||||
@ -7,6 +7,7 @@ const recordedEvents = [];
|
||||
function configure() {
|
||||
return function (logEvent) {
|
||||
debug(`received logEvent, number of events now ${recordedEvents.length + 1}`);
|
||||
debug('log event was ', logEvent);
|
||||
recordedEvents.push(logEvent);
|
||||
};
|
||||
}
|
||||
|
||||
39
lib/appenders/tcp-server.js
Normal file
39
lib/appenders/tcp-server.js
Normal file
@ -0,0 +1,39 @@
|
||||
const debug = require('debug')('log4js:tcp-server');
|
||||
const net = require('net');
|
||||
const clustering = require('../clustering');
|
||||
const LoggingEvent = require('../LoggingEvent');
|
||||
|
||||
const send = (data) => {
|
||||
if (data) {
|
||||
const event = LoggingEvent.deserialise(data);
|
||||
clustering.send(event);
|
||||
}
|
||||
};
|
||||
|
||||
exports.configure = (config) => {
|
||||
debug('configure called with ', config);
|
||||
// dummy shutdown if we're not master
|
||||
let shutdown = (cb) => { cb(); };
|
||||
|
||||
clustering.onlyOnMaster(() => {
|
||||
const server = net.createServer((socket) => {
|
||||
socket.setEncoding('utf8');
|
||||
socket.on('data', send);
|
||||
socket.on('end', send);
|
||||
});
|
||||
|
||||
server.listen(config.port || 5000, config.host || 'localhost', () => {
|
||||
debug(`listening on ${config.host || 'localhost'}:${config.port || 5000}`);
|
||||
server.unref();
|
||||
});
|
||||
|
||||
shutdown = (cb) => {
|
||||
debug('shutdown called.');
|
||||
server.close(cb);
|
||||
};
|
||||
});
|
||||
|
||||
return {
|
||||
shutdown
|
||||
};
|
||||
};
|
||||
76
lib/appenders/tcp.js
Normal file
76
lib/appenders/tcp.js
Normal file
@ -0,0 +1,76 @@
|
||||
'use strict';
|
||||
|
||||
const debug = require('debug')('log4js:tcp');
|
||||
const net = require('net');
|
||||
|
||||
function appender(config) {
|
||||
let canWrite = false;
|
||||
const buffer = [];
|
||||
let socket;
|
||||
let shutdownAttempts = 3;
|
||||
|
||||
function write(loggingEvent) {
|
||||
debug('Writing log event to socket');
|
||||
canWrite = socket.write(loggingEvent.serialise(), 'utf8');
|
||||
}
|
||||
|
||||
function emptyBuffer() {
|
||||
let evt;
|
||||
debug('emptying buffer');
|
||||
/* eslint no-cond-assign:0 */
|
||||
while ((evt = buffer.shift())) {
|
||||
write(evt);
|
||||
}
|
||||
}
|
||||
|
||||
function createSocket() {
|
||||
debug(`appender creating socket to ${config.host || 'localhost'}:${config.port || 5000}`);
|
||||
socket = net.createConnection(config.port || 5000, config.host || 'localhost');
|
||||
socket.on('connect', () => {
|
||||
debug('socket connected');
|
||||
emptyBuffer();
|
||||
canWrite = true;
|
||||
});
|
||||
socket.on('drain', () => {
|
||||
debug('drain event received, emptying buffer');
|
||||
canWrite = true;
|
||||
emptyBuffer();
|
||||
});
|
||||
socket.on('timeout', socket.end.bind(socket));
|
||||
// don't bother listening for 'error', 'close' gets called after that anyway
|
||||
socket.on('close', createSocket);
|
||||
}
|
||||
|
||||
createSocket();
|
||||
|
||||
function log(loggingEvent) {
|
||||
if (canWrite) {
|
||||
write(loggingEvent);
|
||||
} else {
|
||||
debug('buffering log event because it cannot write at the moment');
|
||||
buffer.push(loggingEvent);
|
||||
}
|
||||
}
|
||||
|
||||
log.shutdown = function (cb) {
|
||||
debug('shutdown called');
|
||||
if (buffer.length && shutdownAttempts) {
|
||||
debug('buffer has items, waiting 100ms to empty');
|
||||
shutdownAttempts -= 1;
|
||||
setTimeout(() => {
|
||||
log.shutdown(cb);
|
||||
}, 100);
|
||||
} else {
|
||||
socket.removeAllListeners('close');
|
||||
socket.end(cb);
|
||||
}
|
||||
};
|
||||
return log;
|
||||
}
|
||||
|
||||
function configure(config) {
|
||||
debug(`configure with config = ${config}`);
|
||||
return appender(config);
|
||||
}
|
||||
|
||||
module.exports.configure = configure;
|
||||
123
lib/categories.js
Normal file
123
lib/categories.js
Normal file
@ -0,0 +1,123 @@
|
||||
const configuration = require('./configuration');
|
||||
const levels = require('./levels');
|
||||
const appenders = require('./appenders');
|
||||
const debug = require('debug')('log4js:categories');
|
||||
|
||||
const categories = new Map();
|
||||
|
||||
configuration.addListener((config) => {
|
||||
configuration.throwExceptionIf(
|
||||
config,
|
||||
configuration.not(configuration.anObject(config.categories)),
|
||||
'must have a property "categories" of type object.'
|
||||
);
|
||||
|
||||
const categoryNames = Object.keys(config.categories);
|
||||
configuration.throwExceptionIf(
|
||||
config,
|
||||
configuration.not(categoryNames.length),
|
||||
'must define at least one category.'
|
||||
);
|
||||
|
||||
categoryNames.forEach((name) => {
|
||||
const category = config.categories[name];
|
||||
configuration.throwExceptionIf(
|
||||
config,
|
||||
[
|
||||
configuration.not(category.appenders),
|
||||
configuration.not(category.level)
|
||||
],
|
||||
`category "${name}" is not valid (must be an object with properties "appenders" and "level")`
|
||||
);
|
||||
|
||||
configuration.throwExceptionIf(
|
||||
config,
|
||||
configuration.not(Array.isArray(category.appenders)),
|
||||
`category "${name}" is not valid (appenders must be an array of appender names)`
|
||||
);
|
||||
|
||||
configuration.throwExceptionIf(
|
||||
config,
|
||||
configuration.not(category.appenders.length),
|
||||
`category "${name}" is not valid (appenders must contain at least one appender name)`
|
||||
);
|
||||
|
||||
category.appenders.forEach((appender) => {
|
||||
configuration.throwExceptionIf(
|
||||
config,
|
||||
configuration.not(appenders.get(appender)),
|
||||
`category "${name}" is not valid (appender "${appender}" is not defined)`
|
||||
);
|
||||
});
|
||||
|
||||
configuration.throwExceptionIf(
|
||||
config,
|
||||
configuration.not(levels.getLevel(category.level)),
|
||||
`category "${name}" is not valid (level "${category.level}" not recognised;` +
|
||||
` valid levels are ${levels.levels.join(', ')})`
|
||||
);
|
||||
});
|
||||
|
||||
configuration.throwExceptionIf(
|
||||
config,
|
||||
configuration.not(config.categories.default),
|
||||
'must define a "default" category.'
|
||||
);
|
||||
});
|
||||
|
||||
const setup = (config) => {
|
||||
categories.clear();
|
||||
|
||||
const categoryNames = Object.keys(config.categories);
|
||||
categoryNames.forEach((name) => {
|
||||
const category = config.categories[name];
|
||||
const categoryAppenders = [];
|
||||
category.appenders.forEach((appender) => {
|
||||
categoryAppenders.push(appenders.get(appender));
|
||||
debug(`Creating category ${name}`);
|
||||
categories.set(
|
||||
name,
|
||||
{ appenders: categoryAppenders, level: levels.getLevel(category.level) }
|
||||
);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
setup({ categories: { default: { appenders: ['out'], level: 'OFF' } } });
|
||||
configuration.addListener(setup);
|
||||
|
||||
const configForCategory = (category) => {
|
||||
debug(`configForCategory: searching for config for ${category}`);
|
||||
if (categories.has(category)) {
|
||||
debug(`configForCategory: ${category} exists in config, returning it`);
|
||||
return categories.get(category);
|
||||
}
|
||||
if (category.indexOf('.') > 0) {
|
||||
debug(`configForCategory: ${category} has hierarchy, searching for parents`);
|
||||
return configForCategory(category.substring(0, category.lastIndexOf('.')));
|
||||
}
|
||||
debug('configForCategory: returning config for default category');
|
||||
return configForCategory('default');
|
||||
};
|
||||
|
||||
const appendersForCategory = category => configForCategory(category).appenders;
|
||||
const getLevelForCategory = category => configForCategory(category).level;
|
||||
|
||||
const setLevelForCategory = (category, level) => {
|
||||
let categoryConfig = categories.get(category);
|
||||
debug(`setLevelForCategory: found ${categoryConfig} for ${category}`);
|
||||
if (!categoryConfig) {
|
||||
const sourceCategoryConfig = configForCategory(category);
|
||||
debug('setLevelForCategory: no config found for category, ' +
|
||||
`found ${sourceCategoryConfig} for parents of ${category}`);
|
||||
categoryConfig = { appenders: sourceCategoryConfig.appenders };
|
||||
}
|
||||
categoryConfig.level = level;
|
||||
categories.set(category, categoryConfig);
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
appendersForCategory,
|
||||
getLevelForCategory,
|
||||
setLevelForCategory
|
||||
};
|
||||
92
lib/clustering.js
Normal file
92
lib/clustering.js
Normal file
@ -0,0 +1,92 @@
|
||||
const debug = require('debug')('log4js:clustering');
|
||||
const LoggingEvent = require('./LoggingEvent');
|
||||
const configuration = require('./configuration');
|
||||
const cluster = require('cluster');
|
||||
|
||||
const listeners = [];
|
||||
|
||||
let disabled = false;
|
||||
let pm2 = false;
|
||||
let pm2InstanceVar = 'NODE_APP_INSTANCE';
|
||||
|
||||
const isPM2Master = () => pm2 && process.env[pm2InstanceVar] === '0';
|
||||
const isMaster = () => disabled || cluster.isMaster || isPM2Master();
|
||||
|
||||
const sendToListeners = (logEvent) => {
|
||||
listeners.forEach(l => l(logEvent));
|
||||
};
|
||||
|
||||
// in a multi-process node environment, worker loggers will use
|
||||
// process.send
|
||||
const receiver = (worker, message) => {
|
||||
// prior to node v6, the worker parameter was not passed (args were message, handle)
|
||||
debug('cluster message received from worker ', worker, ': ', message);
|
||||
if (worker.topic && worker.data) {
|
||||
message = worker;
|
||||
worker = undefined;
|
||||
}
|
||||
if (message && message.topic && message.topic === 'log4js:message') {
|
||||
debug('received message: ', message.data);
|
||||
const logEvent = LoggingEvent.deserialise(message.data);
|
||||
sendToListeners(logEvent);
|
||||
}
|
||||
};
|
||||
|
||||
configuration.addListener((config) => {
|
||||
// clear out the listeners, because configure has been called.
|
||||
listeners.length = 0;
|
||||
|
||||
disabled = config.disableClustering;
|
||||
pm2 = config.pm2;
|
||||
pm2InstanceVar = config.pm2InstanceVar || 'NODE_APP_INSTANCE';
|
||||
|
||||
debug(`clustering disabled ? ${disabled}`);
|
||||
debug(`cluster.isMaster ? ${cluster.isMaster}`);
|
||||
debug(`pm2 enabled ? ${pm2}`);
|
||||
debug(`pm2InstanceVar = ${pm2InstanceVar}`);
|
||||
debug(`process.env[${pm2InstanceVar}] = ${process.env[pm2InstanceVar]}`);
|
||||
|
||||
// just in case configure is called after shutdown
|
||||
if (pm2) {
|
||||
process.removeListener('message', receiver);
|
||||
}
|
||||
if (cluster.removeListener) {
|
||||
cluster.removeListener('message', receiver);
|
||||
}
|
||||
|
||||
if (config.disableClustering) {
|
||||
debug('Not listening for cluster messages, because clustering disabled.');
|
||||
} else if (isPM2Master()) {
|
||||
// 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
|
||||
debug('listening for PM2 broadcast messages');
|
||||
process.on('message', receiver);
|
||||
} else if (cluster.isMaster) {
|
||||
debug('listening for cluster messages');
|
||||
cluster.on('message', receiver);
|
||||
} else {
|
||||
debug('not listening for messages, because we are not a master process');
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = {
|
||||
onlyOnMaster: (fn, notMaster) => (isMaster() ? fn() : notMaster),
|
||||
isMaster: isMaster,
|
||||
send: (msg) => {
|
||||
if (isMaster()) {
|
||||
sendToListeners(msg);
|
||||
} else {
|
||||
if (!pm2) {
|
||||
msg.cluster = {
|
||||
workerId: cluster.worker.id,
|
||||
worker: process.pid
|
||||
};
|
||||
}
|
||||
process.send({ topic: 'log4js:message', data: msg.serialise() });
|
||||
}
|
||||
},
|
||||
onMessage: (listener) => {
|
||||
listeners.push(listener);
|
||||
}
|
||||
};
|
||||
@ -1,217 +1,48 @@
|
||||
'use strict';
|
||||
|
||||
const util = require('util');
|
||||
const path = require('path');
|
||||
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 listeners = [];
|
||||
|
||||
const validColours = [
|
||||
'white', 'grey', 'black',
|
||||
'blue', 'cyan', 'green',
|
||||
'magenta', 'red', 'yellow'
|
||||
];
|
||||
const not = thing => !thing;
|
||||
|
||||
function not(thing) {
|
||||
return !thing;
|
||||
}
|
||||
const anObject = thing => thing && typeof thing === 'object' && !Array.isArray(thing);
|
||||
|
||||
function anObject(thing) {
|
||||
return thing && typeof thing === 'object' && !Array.isArray(thing);
|
||||
}
|
||||
const validIdentifier = thing => /^[A-Za-z][A-Za-z0-9_]*$/g.test(thing);
|
||||
|
||||
function validIdentifier(thing) {
|
||||
return /^[A-Za-z][A-Za-z0-9_]*$/g.test(thing);
|
||||
}
|
||||
const anInteger = thing => thing && typeof thing === 'number' && Number.isInteger(thing);
|
||||
|
||||
function anInteger(thing) {
|
||||
return thing && typeof thing === 'number' && Number.isInteger(thing);
|
||||
}
|
||||
const addListener = (fn) => {
|
||||
listeners.push(fn);
|
||||
debug(`Added listener, listeners now ${listeners.length}`);
|
||||
};
|
||||
|
||||
class Configuration {
|
||||
throwExceptionIf(checks, message) {
|
||||
const tests = Array.isArray(checks) ? checks : [checks];
|
||||
tests.forEach((test) => {
|
||||
if (test) {
|
||||
throw new Error(`Problem with log4js configuration: (${util.inspect(this.candidate, { depth: 5 })})` +
|
||||
` - ${message}`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
tryLoading(modulePath) {
|
||||
debug('Loading module from ', modulePath);
|
||||
try {
|
||||
return require(modulePath); //eslint-disable-line
|
||||
} catch (e) {
|
||||
// if the module was found, and we still got an error, then raise it
|
||||
this.throwExceptionIf(
|
||||
e.code !== 'MODULE_NOT_FOUND',
|
||||
`appender "${path}" could not be loaded (error was: ${e})`
|
||||
);
|
||||
return undefined;
|
||||
const throwExceptionIf = (config, checks, message) => {
|
||||
const tests = Array.isArray(checks) ? checks : [checks];
|
||||
tests.forEach((test) => {
|
||||
if (test) {
|
||||
throw new Error(`Problem with log4js configuration: (${util.inspect(config, { depth: 5 })})` +
|
||||
` - ${message}`);
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
loadAppenderModule(type) {
|
||||
return this.tryLoading(`./appenders/${type}`) ||
|
||||
this.tryLoading(type) ||
|
||||
this.tryLoading(path.join(path.dirname(require.main.filename), type)) ||
|
||||
this.tryLoading(path.join(process.cwd(), type));
|
||||
}
|
||||
const configure = (candidate) => {
|
||||
debug('New configuration to be validated: ', candidate);
|
||||
throwExceptionIf(candidate, not(anObject(candidate)), 'must be an object.');
|
||||
|
||||
createAppender(name, config) {
|
||||
const appenderModule = this.loadAppenderModule(config.type);
|
||||
this.throwExceptionIf(
|
||||
not(appenderModule),
|
||||
`appender "${name}" is not valid (type "${config.type}" could not be found)`
|
||||
);
|
||||
if (appenderModule.appender) {
|
||||
debug(`DEPRECATION: Appender ${config.type} exports an appender function.`);
|
||||
}
|
||||
if (appenderModule.shutdown) {
|
||||
debug(`DEPRECATION: Appender ${config.type} exports a shutdown function.`);
|
||||
}
|
||||
debug(`Calling configuration listeners (${listeners.length})`);
|
||||
listeners.forEach(listener => listener(candidate));
|
||||
debug('Configuration finished.');
|
||||
};
|
||||
|
||||
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,
|
||||
this.configuredAppenders.get.bind(this.configuredAppenders),
|
||||
this.configuredLevels
|
||||
);
|
||||
}
|
||||
return () => {};
|
||||
}
|
||||
|
||||
get appenders() {
|
||||
return this.configuredAppenders;
|
||||
}
|
||||
|
||||
set appenders(appenderConfig) {
|
||||
const appenderNames = Object.keys(appenderConfig);
|
||||
this.throwExceptionIf(not(appenderNames.length), 'must define at least one appender.');
|
||||
|
||||
this.configuredAppenders = new Map();
|
||||
appenderNames.forEach((name) => {
|
||||
this.throwExceptionIf(
|
||||
not(appenderConfig[name].type),
|
||||
`appender "${name}" is not valid (must be an object with property "type")`
|
||||
);
|
||||
|
||||
debug(`Creating appender ${name}`);
|
||||
this.configuredAppenders.set(name, this.createAppender(name, appenderConfig[name]));
|
||||
});
|
||||
}
|
||||
|
||||
get categories() {
|
||||
return this.configuredCategories;
|
||||
}
|
||||
|
||||
set categories(categoryConfig) {
|
||||
const categoryNames = Object.keys(categoryConfig);
|
||||
this.throwExceptionIf(not(categoryNames.length), 'must define at least one category.');
|
||||
|
||||
this.configuredCategories = new Map();
|
||||
categoryNames.forEach((name) => {
|
||||
const category = categoryConfig[name];
|
||||
this.throwExceptionIf(
|
||||
[
|
||||
not(category.appenders),
|
||||
not(category.level)
|
||||
],
|
||||
`category "${name}" is not valid (must be an object with properties "appenders" and "level")`
|
||||
);
|
||||
|
||||
this.throwExceptionIf(
|
||||
not(Array.isArray(category.appenders)),
|
||||
`category "${name}" is not valid (appenders must be an array of appender names)`
|
||||
);
|
||||
|
||||
this.throwExceptionIf(
|
||||
not(category.appenders.length),
|
||||
`category "${name}" is not valid (appenders must contain at least one appender name)`
|
||||
);
|
||||
|
||||
const appenders = [];
|
||||
category.appenders.forEach((appender) => {
|
||||
this.throwExceptionIf(
|
||||
not(this.configuredAppenders.get(appender)),
|
||||
`category "${name}" is not valid (appender "${appender}" is not defined)`
|
||||
);
|
||||
appenders.push(this.appenders.get(appender));
|
||||
});
|
||||
|
||||
this.throwExceptionIf(
|
||||
not(this.configuredLevels.getLevel(category.level)),
|
||||
`category "${name}" is not valid (level "${category.level}" not recognised;` +
|
||||
` valid levels are ${this.configuredLevels.levels.join(', ')})`
|
||||
);
|
||||
|
||||
debug(`Creating category ${name}`);
|
||||
this.configuredCategories.set(
|
||||
name,
|
||||
{ appenders: appenders, level: this.configuredLevels.getLevel(category.level) }
|
||||
);
|
||||
});
|
||||
|
||||
this.throwExceptionIf(not(categoryConfig.default), 'must define a "default" category.');
|
||||
}
|
||||
|
||||
get levels() {
|
||||
return this.configuredLevels;
|
||||
}
|
||||
|
||||
set levels(levelConfig) {
|
||||
// levels are optional
|
||||
if (levelConfig) {
|
||||
this.throwExceptionIf(not(anObject(levelConfig)), 'levels must be an object');
|
||||
const newLevels = Object.keys(levelConfig);
|
||||
newLevels.forEach((l) => {
|
||||
this.throwExceptionIf(
|
||||
not(validIdentifier(l)),
|
||||
`level name "${l}" is not a valid identifier (must start with a letter, only contain A-Z,a-z,0-9,_)`
|
||||
);
|
||||
this.throwExceptionIf(not(anObject(levelConfig[l])), `level "${l}" must be an object`);
|
||||
this.throwExceptionIf(not(levelConfig[l].value), `level "${l}" must have a 'value' property`);
|
||||
this.throwExceptionIf(not(anInteger(levelConfig[l].value)), `level "${l}".value must have an integer value`);
|
||||
this.throwExceptionIf(not(levelConfig[l].colour), `level "${l}" must have a 'colour' property`);
|
||||
this.throwExceptionIf(
|
||||
not(validColours.indexOf(levelConfig[l].colour) > -1),
|
||||
`level "${l}".colour must be one of ${validColours.join(', ')}`
|
||||
);
|
||||
});
|
||||
}
|
||||
this.configuredLevels = levels(levelConfig);
|
||||
}
|
||||
|
||||
constructor(candidate) {
|
||||
this.candidate = candidate;
|
||||
|
||||
this.throwExceptionIf(not(anObject(candidate)), 'must be an object.');
|
||||
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';
|
||||
|
||||
this.levels = candidate.levels;
|
||||
this.appenders = candidate.appenders;
|
||||
this.categories = candidate.categories;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Configuration;
|
||||
module.exports = {
|
||||
configure,
|
||||
addListener,
|
||||
throwExceptionIf,
|
||||
anObject,
|
||||
anInteger,
|
||||
validIdentifier,
|
||||
not
|
||||
};
|
||||
|
||||
@ -2,6 +2,8 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
const levels = require('./levels');
|
||||
|
||||
const DEFAULT_FORMAT = ':remote-addr - -' +
|
||||
' ":method :url HTTP/:http-version"' +
|
||||
' :status :content-length ":referrer"' +
|
||||
@ -163,8 +165,7 @@ function createNoLogCondition(nolog) {
|
||||
return regexp;
|
||||
}
|
||||
|
||||
module.exports = function (levels) {
|
||||
/**
|
||||
/**
|
||||
* Log requests with the given `options` or a `format` string.
|
||||
*
|
||||
* Options:
|
||||
@ -192,80 +193,77 @@ module.exports = function (levels) {
|
||||
* @param options
|
||||
* @api public
|
||||
*/
|
||||
function getLogger(logger4js, options) {
|
||||
/* eslint no-underscore-dangle:0 */
|
||||
if (typeof options === 'object') {
|
||||
options = options || {};
|
||||
} else if (options) {
|
||||
options = { format: options };
|
||||
} else {
|
||||
options = {};
|
||||
}
|
||||
|
||||
const thisLogger = logger4js;
|
||||
let level = levels.getLevel(options.level, levels.INFO);
|
||||
const fmt = options.format || DEFAULT_FORMAT;
|
||||
const nolog = options.nolog ? createNoLogCondition(options.nolog) : null;
|
||||
|
||||
return (req, res, next) => {
|
||||
// mount safety
|
||||
if (req._logging) return next();
|
||||
|
||||
// nologs
|
||||
if (nolog && nolog.test(req.originalUrl)) return next();
|
||||
|
||||
if (thisLogger.isLevelEnabled(level) || options.level === 'auto') {
|
||||
const start = new Date();
|
||||
const writeHead = res.writeHead;
|
||||
|
||||
// flag as logging
|
||||
req._logging = true;
|
||||
|
||||
// proxy for statusCode.
|
||||
res.writeHead = (code, headers) => {
|
||||
res.writeHead = writeHead;
|
||||
res.writeHead(code, headers);
|
||||
|
||||
res.__statusCode = code;
|
||||
res.__headers = headers || {};
|
||||
|
||||
// status code response level handling
|
||||
if (options.level === 'auto') {
|
||||
level = levels.INFO;
|
||||
if (code >= 300) level = levels.WARN;
|
||||
if (code >= 400) level = levels.ERROR;
|
||||
} else {
|
||||
level = levels.getLevel(options.level, levels.INFO);
|
||||
}
|
||||
};
|
||||
|
||||
// hook on end request to emit the log entry of the HTTP request.
|
||||
res.on('finish', () => {
|
||||
res.responseTime = new Date() - start;
|
||||
// status code response level handling
|
||||
if (res.statusCode && options.level === 'auto') {
|
||||
level = levels.INFO;
|
||||
if (res.statusCode >= 300) level = levels.WARN;
|
||||
if (res.statusCode >= 400) level = levels.ERROR;
|
||||
}
|
||||
|
||||
if (thisLogger.isLevelEnabled(level)) {
|
||||
const combinedTokens = assembleTokens(req, res, options.tokens || []);
|
||||
|
||||
if (typeof fmt === 'function') {
|
||||
const line = fmt(req, res, str => format(str, combinedTokens));
|
||||
if (line) thisLogger.log(level, line);
|
||||
} else {
|
||||
thisLogger.log(level, format(fmt, combinedTokens));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// ensure next gets always called
|
||||
return next();
|
||||
};
|
||||
module.exports = function getLogger(logger4js, options) {
|
||||
/* eslint no-underscore-dangle:0 */
|
||||
if (typeof options === 'object') {
|
||||
options = options || {};
|
||||
} else if (options) {
|
||||
options = { format: options };
|
||||
} else {
|
||||
options = {};
|
||||
}
|
||||
|
||||
return { connectLogger: getLogger };
|
||||
const thisLogger = logger4js;
|
||||
let level = levels.getLevel(options.level, levels.INFO);
|
||||
const fmt = options.format || DEFAULT_FORMAT;
|
||||
const nolog = options.nolog ? createNoLogCondition(options.nolog) : null;
|
||||
|
||||
return (req, res, next) => {
|
||||
// mount safety
|
||||
if (req._logging) return next();
|
||||
|
||||
// nologs
|
||||
if (nolog && nolog.test(req.originalUrl)) return next();
|
||||
|
||||
if (thisLogger.isLevelEnabled(level) || options.level === 'auto') {
|
||||
const start = new Date();
|
||||
const writeHead = res.writeHead;
|
||||
|
||||
// flag as logging
|
||||
req._logging = true;
|
||||
|
||||
// proxy for statusCode.
|
||||
res.writeHead = (code, headers) => {
|
||||
res.writeHead = writeHead;
|
||||
res.writeHead(code, headers);
|
||||
|
||||
res.__statusCode = code;
|
||||
res.__headers = headers || {};
|
||||
|
||||
// status code response level handling
|
||||
if (options.level === 'auto') {
|
||||
level = levels.INFO;
|
||||
if (code >= 300) level = levels.WARN;
|
||||
if (code >= 400) level = levels.ERROR;
|
||||
} else {
|
||||
level = levels.getLevel(options.level, levels.INFO);
|
||||
}
|
||||
};
|
||||
|
||||
// hook on end request to emit the log entry of the HTTP request.
|
||||
res.on('finish', () => {
|
||||
res.responseTime = new Date() - start;
|
||||
// status code response level handling
|
||||
if (res.statusCode && options.level === 'auto') {
|
||||
level = levels.INFO;
|
||||
if (res.statusCode >= 300) level = levels.WARN;
|
||||
if (res.statusCode >= 400) level = levels.ERROR;
|
||||
}
|
||||
|
||||
if (thisLogger.isLevelEnabled(level)) {
|
||||
const combinedTokens = assembleTokens(req, res, options.tokens || []);
|
||||
|
||||
if (typeof fmt === 'function') {
|
||||
const line = fmt(req, res, str => format(str, combinedTokens));
|
||||
if (line) thisLogger.log(level, line);
|
||||
} else {
|
||||
thisLogger.log(level, format(fmt, combinedTokens));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// ensure next gets always called
|
||||
return next();
|
||||
};
|
||||
};
|
||||
|
||||
174
lib/levels.js
174
lib/levels.js
@ -1,60 +1,22 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = function (customLevels) {
|
||||
/**
|
||||
* @name Level
|
||||
* @namespace Log4js
|
||||
*/
|
||||
class Level {
|
||||
constructor(level, levelStr, colour) {
|
||||
this.level = level;
|
||||
this.levelStr = levelStr;
|
||||
this.colour = colour;
|
||||
}
|
||||
const configuration = require('./configuration');
|
||||
|
||||
toString() {
|
||||
return this.levelStr;
|
||||
}
|
||||
const validColours = [
|
||||
'white', 'grey', 'black',
|
||||
'blue', 'cyan', 'green',
|
||||
'magenta', 'red', 'yellow'
|
||||
];
|
||||
|
||||
isLessThanOrEqualTo(otherLevel) {
|
||||
if (typeof otherLevel === 'string') {
|
||||
otherLevel = getLevel(otherLevel);
|
||||
}
|
||||
return this.level <= otherLevel.level;
|
||||
}
|
||||
|
||||
isGreaterThanOrEqualTo(otherLevel) {
|
||||
if (typeof otherLevel === 'string') {
|
||||
otherLevel = getLevel(otherLevel);
|
||||
}
|
||||
return this.level >= otherLevel.level;
|
||||
}
|
||||
|
||||
isEqualTo(otherLevel) {
|
||||
if (typeof otherLevel === 'string') {
|
||||
otherLevel = getLevel(otherLevel);
|
||||
}
|
||||
return this.level === otherLevel.level;
|
||||
}
|
||||
class Level {
|
||||
constructor(level, levelStr, colour) {
|
||||
this.level = level;
|
||||
this.levelStr = levelStr;
|
||||
this.colour = colour;
|
||||
}
|
||||
|
||||
const defaultLevels = {
|
||||
ALL: new Level(Number.MIN_VALUE, 'ALL', 'grey'),
|
||||
TRACE: new Level(5000, 'TRACE', 'blue'),
|
||||
DEBUG: new Level(10000, 'DEBUG', 'cyan'),
|
||||
INFO: new Level(20000, 'INFO', 'green'),
|
||||
WARN: new Level(30000, 'WARN', 'yellow'),
|
||||
ERROR: new Level(40000, 'ERROR', 'red'),
|
||||
FATAL: new Level(50000, 'FATAL', 'magenta'),
|
||||
MARK: new Level(9007199254740992, 'MARK', 'grey'), // 2^53
|
||||
OFF: new Level(Number.MAX_VALUE, 'OFF', 'grey')
|
||||
};
|
||||
|
||||
if (customLevels) {
|
||||
const levels = Object.keys(customLevels);
|
||||
levels.forEach((l) => {
|
||||
defaultLevels[l.toUpperCase()] = new Level(customLevels[l].value, l.toUpperCase(), customLevels[l].colour);
|
||||
});
|
||||
toString() {
|
||||
return this.levelStr;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -63,7 +25,7 @@ module.exports = function (customLevels) {
|
||||
* @param {Level} [defaultLevel] -- default Level, if no String representation
|
||||
* @return {Level}
|
||||
*/
|
||||
function getLevel(sArg, defaultLevel) {
|
||||
static getLevel(sArg, defaultLevel) {
|
||||
if (!sArg) {
|
||||
return defaultLevel;
|
||||
}
|
||||
@ -73,15 +35,109 @@ module.exports = function (customLevels) {
|
||||
}
|
||||
|
||||
if (typeof sArg === 'string') {
|
||||
return defaultLevels[sArg.toUpperCase()] || defaultLevel;
|
||||
return Level[sArg.toUpperCase()] || defaultLevel;
|
||||
}
|
||||
|
||||
return getLevel(sArg.toString());
|
||||
return Level.getLevel(sArg.toString());
|
||||
}
|
||||
|
||||
const orderedLevels = Object.keys(defaultLevels).sort((a, b) => b.level - a.level);
|
||||
defaultLevels.getLevel = getLevel;
|
||||
defaultLevels.levels = orderedLevels;
|
||||
static addLevels(customLevels) {
|
||||
if (customLevels) {
|
||||
const levels = Object.keys(customLevels);
|
||||
levels.forEach((l) => {
|
||||
Level[l.toUpperCase()] = new Level(
|
||||
customLevels[l].value,
|
||||
l.toUpperCase(),
|
||||
customLevels[l].colour
|
||||
);
|
||||
Level.levels.push(Level[l.toUpperCase()]);
|
||||
});
|
||||
Level.levels.sort((a, b) => a.level - b.level);
|
||||
}
|
||||
}
|
||||
|
||||
return defaultLevels;
|
||||
};
|
||||
|
||||
isLessThanOrEqualTo(otherLevel) {
|
||||
if (typeof otherLevel === 'string') {
|
||||
otherLevel = Level.getLevel(otherLevel);
|
||||
}
|
||||
return this.level <= otherLevel.level;
|
||||
}
|
||||
|
||||
isGreaterThanOrEqualTo(otherLevel) {
|
||||
if (typeof otherLevel === 'string') {
|
||||
otherLevel = Level.getLevel(otherLevel);
|
||||
}
|
||||
return this.level >= otherLevel.level;
|
||||
}
|
||||
|
||||
isEqualTo(otherLevel) {
|
||||
if (typeof otherLevel === 'string') {
|
||||
otherLevel = Level.getLevel(otherLevel);
|
||||
}
|
||||
return this.level === otherLevel.level;
|
||||
}
|
||||
}
|
||||
|
||||
Level.levels = [];
|
||||
Level.addLevels({
|
||||
ALL: { value: Number.MIN_VALUE, colour: 'grey' },
|
||||
TRACE: { value: 5000, colour: 'blue' },
|
||||
DEBUG: { value: 10000, colour: 'cyan' },
|
||||
INFO: { value: 20000, colour: 'green' },
|
||||
WARN: { value: 30000, colour: 'yellow' },
|
||||
ERROR: { value: 40000, colour: 'red' },
|
||||
FATAL: { value: 50000, colour: 'magenta' },
|
||||
MARK: { value: 9007199254740992, colour: 'grey' }, // 2^53
|
||||
OFF: { value: Number.MAX_VALUE, colour: 'grey' }
|
||||
});
|
||||
|
||||
configuration.addListener((config) => {
|
||||
const levelConfig = config.levels;
|
||||
if (levelConfig) {
|
||||
configuration.throwExceptionIf(
|
||||
config,
|
||||
configuration.not(configuration.anObject(levelConfig)),
|
||||
'levels must be an object'
|
||||
);
|
||||
const newLevels = Object.keys(levelConfig);
|
||||
newLevels.forEach((l) => {
|
||||
configuration.throwExceptionIf(
|
||||
config,
|
||||
configuration.not(configuration.validIdentifier(l)),
|
||||
`level name "${l}" is not a valid identifier (must start with a letter, only contain A-Z,a-z,0-9,_)`
|
||||
);
|
||||
configuration.throwExceptionIf(
|
||||
config,
|
||||
configuration.not(configuration.anObject(levelConfig[l])),
|
||||
`level "${l}" must be an object`
|
||||
);
|
||||
configuration.throwExceptionIf(
|
||||
config,
|
||||
configuration.not(levelConfig[l].value),
|
||||
`level "${l}" must have a 'value' property`
|
||||
);
|
||||
configuration.throwExceptionIf(
|
||||
config,
|
||||
configuration.not(configuration.anInteger(levelConfig[l].value)),
|
||||
`level "${l}".value must have an integer value`
|
||||
);
|
||||
configuration.throwExceptionIf(
|
||||
config,
|
||||
configuration.not(levelConfig[l].colour),
|
||||
`level "${l}" must have a 'colour' property`
|
||||
);
|
||||
configuration.throwExceptionIf(
|
||||
config,
|
||||
configuration.not(validColours.indexOf(levelConfig[l].colour) > -1),
|
||||
`level "${l}".colour must be one of ${validColours.join(', ')}`
|
||||
);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
configuration.addListener((config) => {
|
||||
Level.addLevels(config.levels);
|
||||
});
|
||||
|
||||
module.exports = Level;
|
||||
|
||||
215
lib/log4js.js
215
lib/log4js.js
@ -15,163 +15,33 @@
|
||||
*
|
||||
* NOTE: the authors below are the original browser-based log4js authors
|
||||
* don't try to contact them about bugs in this version :)
|
||||
* @version 1.0
|
||||
* @author Stephan Strittmatter - http://jroller.com/page/stritti
|
||||
* @author Seth Chisamore - http://www.chisamore.com
|
||||
* @since 2005-05-20
|
||||
* @static
|
||||
* Website: http://log4js.berlios.de
|
||||
*/
|
||||
const debug = require('debug')('log4js:main');
|
||||
const fs = require('fs');
|
||||
const CircularJSON = require('circular-json');
|
||||
const Configuration = require('./configuration');
|
||||
const connectModule = require('./connect-logger');
|
||||
const logger = require('./logger');
|
||||
const configuration = require('./configuration');
|
||||
const layouts = require('./layouts');
|
||||
const levels = require('./levels');
|
||||
const appenders = require('./appenders');
|
||||
const categories = require('./categories');
|
||||
const Logger = require('./logger');
|
||||
const clustering = require('./clustering');
|
||||
const connectLogger = require('./connect-logger');
|
||||
|
||||
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' }
|
||||
},
|
||||
categories: {
|
||||
default: { appenders: ['stdout'], level: 'OFF' }
|
||||
}
|
||||
};
|
||||
|
||||
let Logger;
|
||||
let LoggingEvent;
|
||||
let config;
|
||||
let enabled = false;
|
||||
|
||||
function configForCategory(category) {
|
||||
debug(`configForCategory: searching for config for ${category}`);
|
||||
if (config.categories.has(category)) {
|
||||
debug(`configForCategory: ${category} exists in config, returning it`);
|
||||
return config.categories.get(category);
|
||||
}
|
||||
if (category.indexOf('.') > 0) {
|
||||
debug(`configForCategory: ${category} has hierarchy, searching for parents`);
|
||||
return configForCategory(category.substring(0, category.lastIndexOf('.')));
|
||||
}
|
||||
debug('configForCategory: returning config for default category');
|
||||
return configForCategory('default');
|
||||
}
|
||||
|
||||
function appendersForCategory(category) {
|
||||
return configForCategory(category).appenders;
|
||||
}
|
||||
|
||||
function levelForCategory(category) {
|
||||
return configForCategory(category).level;
|
||||
}
|
||||
|
||||
function setLevelForCategory(category, level) {
|
||||
let categoryConfig = config.categories.get(category);
|
||||
debug(`setLevelForCategory: found ${categoryConfig} for ${category}`);
|
||||
if (!categoryConfig) {
|
||||
const sourceCategoryConfig = configForCategory(category);
|
||||
debug('setLevelForCategory: no config found for category, ' +
|
||||
`found ${sourceCategoryConfig} for parents of ${category}`);
|
||||
categoryConfig = { appenders: sourceCategoryConfig.appenders };
|
||||
}
|
||||
categoryConfig.level = level;
|
||||
config.categories.set(category, categoryConfig);
|
||||
}
|
||||
|
||||
function serialise(logEvent) {
|
||||
// JSON.stringify(new Error('test')) returns {}, which is not really useful for us.
|
||||
// The following allows us to serialize errors correctly.
|
||||
// Validate that we really are in this case
|
||||
try {
|
||||
const logData = logEvent.data.map((e) => {
|
||||
if (e && e.stack && CircularJSON.stringify(e) === '{}') {
|
||||
e = { message: e.message, stack: e.stack };
|
||||
}
|
||||
return e;
|
||||
});
|
||||
logEvent.data = logData;
|
||||
return CircularJSON.stringify(logEvent);
|
||||
} catch (e) {
|
||||
return serialise(new LoggingEvent(
|
||||
'log4js',
|
||||
config.levels.ERROR,
|
||||
['Unable to serialise log event due to :', e]
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
function deserialise(serialised) {
|
||||
let event;
|
||||
try {
|
||||
event = CircularJSON.parse(serialised);
|
||||
event.startTime = new Date(event.startTime);
|
||||
event.level = config.levels.getLevel(event.level.levelStr);
|
||||
event.data = event.data.map((e) => {
|
||||
if (e && e.stack) {
|
||||
const fakeError = new Error(e.message);
|
||||
fakeError.stack = e.stack;
|
||||
e = fakeError;
|
||||
}
|
||||
return e;
|
||||
});
|
||||
} catch (e) {
|
||||
event = new LoggingEvent(
|
||||
'log4js',
|
||||
config.levels.ERROR,
|
||||
['Unable to parse log:', serialised, 'because: ', e]
|
||||
);
|
||||
}
|
||||
|
||||
return event;
|
||||
}
|
||||
|
||||
function sendLogEventToAppender(logEvent) {
|
||||
if (!enabled) return;
|
||||
debug('Received log event ', logEvent);
|
||||
const appenders = appendersForCategory(logEvent.categoryName);
|
||||
appenders.forEach((appender) => {
|
||||
const categoryAppenders = categories.appendersForCategory(logEvent.categoryName);
|
||||
categoryAppenders.forEach((appender) => {
|
||||
appender(logEvent);
|
||||
});
|
||||
}
|
||||
|
||||
function workerDispatch(logEvent) {
|
||||
debug(`sending message to master from worker ${process.pid}`);
|
||||
process.send({ topic: 'log4js:message', data: serialise(logEvent) });
|
||||
}
|
||||
|
||||
function isPM2Master() {
|
||||
return config.pm2 && process.env[config.pm2InstanceVar] === '0';
|
||||
}
|
||||
|
||||
function isMaster() {
|
||||
return config.disableClustering || cluster.isMaster || isPM2Master();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a logger instance.
|
||||
* @static
|
||||
* @param loggerCategoryName
|
||||
* @return {Logger} instance of logger for the category
|
||||
*/
|
||||
function getLogger(category) {
|
||||
if (!enabled) {
|
||||
configure(process.env.LOG4JS_CONFIG || defaultConfig);
|
||||
}
|
||||
|
||||
const cat = category || 'default';
|
||||
debug(`creating logger as ${isMaster() ? 'master' : 'worker'}`);
|
||||
return new Logger((isMaster() ? sendLogEventToAppender : workerDispatch), cat);
|
||||
}
|
||||
|
||||
function loadConfigurationFile(filename) {
|
||||
if (filename) {
|
||||
debug(`Loading configuration from ${filename}`);
|
||||
@ -180,21 +50,6 @@ function loadConfigurationFile(filename) {
|
||||
return filename;
|
||||
}
|
||||
|
||||
// in a multi-process node environment, worker loggers will use
|
||||
// process.send
|
||||
const receiver = (worker, message) => {
|
||||
// prior to node v6, the worker parameter was not passed (args were message, handle)
|
||||
debug('cluster message received from worker ', worker, ': ', message);
|
||||
if (worker.topic && worker.data) {
|
||||
message = worker;
|
||||
worker = undefined;
|
||||
}
|
||||
if (message && message.topic && message.topic === 'log4js:message') {
|
||||
debug('received message: ', message.data);
|
||||
sendLogEventToAppender(deserialise(message.data));
|
||||
}
|
||||
};
|
||||
|
||||
function configure(configurationFileOrObject) {
|
||||
let configObject = configurationFileOrObject;
|
||||
|
||||
@ -202,32 +57,9 @@ function configure(configurationFileOrObject) {
|
||||
configObject = loadConfigurationFile(configurationFileOrObject);
|
||||
}
|
||||
debug(`Configuration is ${configObject}`);
|
||||
config = new Configuration(configObject);
|
||||
module.exports.levels = config.levels;
|
||||
const loggerModule = logger(config.levels, levelForCategory, setLevelForCategory);
|
||||
Logger = loggerModule.Logger;
|
||||
LoggingEvent = loggerModule.LoggingEvent;
|
||||
module.exports.connectLogger = connectModule(config.levels).connectLogger;
|
||||
configuration.configure(configObject);
|
||||
|
||||
// just in case configure is called after shutdown
|
||||
process.removeListener('message', receiver);
|
||||
if (cluster) {
|
||||
cluster.removeListener('message', receiver);
|
||||
}
|
||||
if (config.disableClustering) {
|
||||
debug('Not listening for cluster messages, because clustering disabled.');
|
||||
} else if (isPM2Master()) {
|
||||
// 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
|
||||
debug('listening for PM2 broadcast messages');
|
||||
process.on('message', receiver);
|
||||
} else if (cluster.isMaster) {
|
||||
debug('listening for cluster messages');
|
||||
cluster.on('message', receiver);
|
||||
} else {
|
||||
debug('not listening for messages, because we are not a master process');
|
||||
}
|
||||
clustering.onMessage(sendLogEventToAppender);
|
||||
|
||||
enabled = true;
|
||||
}
|
||||
@ -247,8 +79,8 @@ function shutdown(cb) {
|
||||
enabled = false;
|
||||
|
||||
// Call each of the shutdown functions in parallel
|
||||
const appenders = Array.from(config.appenders.values());
|
||||
const shutdownFunctions = appenders.reduceRight((accum, next) => (next.shutdown ? accum + 1 : accum), 0);
|
||||
const appendersToCheck = Array.from(appenders.values());
|
||||
const shutdownFunctions = appendersToCheck.reduceRight((accum, next) => (next.shutdown ? accum + 1 : accum), 0);
|
||||
let completed = 0;
|
||||
let error;
|
||||
|
||||
@ -268,11 +100,28 @@ function shutdown(cb) {
|
||||
return cb();
|
||||
}
|
||||
|
||||
appenders.filter(a => a.shutdown).forEach(a => a.shutdown(complete));
|
||||
appendersToCheck.filter(a => a.shutdown).forEach(a => a.shutdown(complete));
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a logger instance.
|
||||
* @static
|
||||
* @param loggerCategoryName
|
||||
* @return {Logger} instance of logger for the category
|
||||
*/
|
||||
function getLogger(category) {
|
||||
if (!enabled) {
|
||||
configure(process.env.LOG4JS_CONFIG || {
|
||||
appenders: { out: { type: 'stdout' } },
|
||||
categories: { default: { appenders: ['out'], level: 'OFF' } }
|
||||
});
|
||||
}
|
||||
return new Logger(category || 'default');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @name log4js
|
||||
* @namespace Log4js
|
||||
@ -284,8 +133,8 @@ const log4js = {
|
||||
getLogger,
|
||||
configure,
|
||||
shutdown,
|
||||
connectLogger: connectModule(levels()).connectLogger,
|
||||
levels: levels(),
|
||||
connectLogger,
|
||||
levels,
|
||||
addLayout: layouts.addLayout
|
||||
};
|
||||
|
||||
|
||||
200
lib/logger.js
200
lib/logger.js
@ -3,136 +3,92 @@
|
||||
'use strict';
|
||||
|
||||
const debug = require('debug')('log4js:logger');
|
||||
|
||||
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 LoggingEvent = require('./LoggingEvent');
|
||||
const levels = require('./levels');
|
||||
const clustering = require('./clustering');
|
||||
const categories = require('./categories');
|
||||
const configuration = require('./configuration');
|
||||
|
||||
/**
|
||||
* @name LoggingEvent
|
||||
* Logger to log messages.
|
||||
* use {@see log4js#getLogger(String)} to get an instance.
|
||||
*
|
||||
* @name Logger
|
||||
* @namespace Log4js
|
||||
* @param name name of category to log to
|
||||
* @param level - the loglevel for the category
|
||||
* @param dispatch - the function which will receive the logevents
|
||||
*
|
||||
* @author Stephan Strittmatter
|
||||
*/
|
||||
class LoggingEvent {
|
||||
/**
|
||||
* Models a logging event.
|
||||
* @constructor
|
||||
* @param {String} categoryName name of category
|
||||
* @param {Log4js.Level} level level of message
|
||||
* @param {Array} data objects to log
|
||||
* @author Seth Chisamore
|
||||
*/
|
||||
constructor(categoryName, level, data, context) {
|
||||
this.startTime = new Date();
|
||||
this.categoryName = categoryName;
|
||||
this.data = data;
|
||||
this.level = level;
|
||||
this.context = Object.assign({}, context);
|
||||
this.pid = process.pid;
|
||||
if (cluster && cluster.isWorker) {
|
||||
this.cluster = {
|
||||
workerId: cluster.worker.id,
|
||||
worker: process.pid
|
||||
};
|
||||
class Logger {
|
||||
constructor(name) {
|
||||
if (!name) {
|
||||
throw new Error('No category provided.');
|
||||
}
|
||||
this.category = name;
|
||||
this.context = {};
|
||||
debug(`Logger created (${this.category}, ${this.level})`);
|
||||
}
|
||||
|
||||
get level() {
|
||||
return levels.getLevel(categories.getLevelForCategory(this.category), levels.TRACE);
|
||||
}
|
||||
|
||||
set level(level) {
|
||||
categories.setLevelForCategory(this.category, levels.getLevel(level, this.level));
|
||||
}
|
||||
|
||||
log(level, ...args) {
|
||||
const logLevel = levels.getLevel(level, levels.INFO);
|
||||
if (this.isLevelEnabled(logLevel)) {
|
||||
this._log(logLevel, args);
|
||||
}
|
||||
}
|
||||
|
||||
isLevelEnabled(otherLevel) {
|
||||
return this.level.isLessThanOrEqualTo(otherLevel);
|
||||
}
|
||||
|
||||
_log(level, data) {
|
||||
debug(`sending log data (${level}) to appenders`);
|
||||
const loggingEvent = new LoggingEvent(this.category, level, data, this.context);
|
||||
clustering.send(loggingEvent);
|
||||
}
|
||||
|
||||
addContext(key, value) {
|
||||
this.context[key] = value;
|
||||
}
|
||||
|
||||
removeContext(key) {
|
||||
delete this.context[key];
|
||||
}
|
||||
|
||||
clearContext() {
|
||||
this.context = {};
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = function (levels, getLevelForCategory, setLevelForCategory) {
|
||||
/**
|
||||
* Logger to log messages.
|
||||
* use {@see log4js#getLogger(String)} to get an instance.
|
||||
*
|
||||
* @name Logger
|
||||
* @namespace Log4js
|
||||
* @param name name of category to log to
|
||||
* @param level - the loglevel for the category
|
||||
* @param dispatch - the function which will receive the logevents
|
||||
*
|
||||
* @author Stephan Strittmatter
|
||||
*/
|
||||
class Logger {
|
||||
constructor(dispatch, name) {
|
||||
if (typeof dispatch !== 'function') {
|
||||
throw new Error('No dispatch function provided.');
|
||||
}
|
||||
if (!name) {
|
||||
throw new Error('No category provided.');
|
||||
}
|
||||
this.category = name;
|
||||
this.dispatch = dispatch;
|
||||
this.context = {};
|
||||
debug(`Logger created (${this.category}, ${this.level}, ${this.dispatch})`);
|
||||
}
|
||||
function addLevelMethods(target) {
|
||||
const level = levels.getLevel(target);
|
||||
|
||||
get level() {
|
||||
return levels.getLevel(getLevelForCategory(this.category), levels.TRACE);
|
||||
}
|
||||
const levelStrLower = level.toString().toLowerCase();
|
||||
const levelMethod = levelStrLower.replace(/_([a-z])/g, g => g[1].toUpperCase());
|
||||
const isLevelMethod = levelMethod[0].toUpperCase() + levelMethod.slice(1);
|
||||
|
||||
set level(level) {
|
||||
setLevelForCategory(this.category, levels.getLevel(level, this.level));
|
||||
}
|
||||
|
||||
log() {
|
||||
/* eslint prefer-rest-params:0 */
|
||||
// todo: once node v4 support dropped, use rest parameter instead
|
||||
const args = Array.from(arguments);
|
||||
const logLevel = levels.getLevel(args[0], levels.INFO);
|
||||
if (this.isLevelEnabled(logLevel)) {
|
||||
this._log(logLevel, args.slice(1));
|
||||
}
|
||||
}
|
||||
|
||||
isLevelEnabled(otherLevel) {
|
||||
return this.level.isLessThanOrEqualTo(otherLevel);
|
||||
}
|
||||
|
||||
_log(level, data) {
|
||||
debug(`sending log data (${level}) to appenders`);
|
||||
const loggingEvent = new LoggingEvent(this.category, level, data, this.context);
|
||||
this.dispatch(loggingEvent);
|
||||
}
|
||||
|
||||
addContext(key, value) {
|
||||
this.context[key] = value;
|
||||
}
|
||||
|
||||
removeContext(key) {
|
||||
delete this.context[key];
|
||||
}
|
||||
|
||||
clearContext() {
|
||||
this.context = {};
|
||||
}
|
||||
}
|
||||
|
||||
function addLevelMethods(target) {
|
||||
const level = levels.getLevel(target);
|
||||
|
||||
const levelStrLower = level.toString().toLowerCase();
|
||||
const levelMethod = levelStrLower.replace(/_([a-z])/g, g => g[1].toUpperCase());
|
||||
const isLevelMethod = levelMethod[0].toUpperCase() + levelMethod.slice(1);
|
||||
|
||||
Logger.prototype[`is${isLevelMethod}Enabled`] = function () {
|
||||
return this.isLevelEnabled(level);
|
||||
};
|
||||
|
||||
Logger.prototype[levelMethod] = function () {
|
||||
/* eslint prefer-rest-params:0 */
|
||||
// todo: once node v4 support dropped, use rest parameter instead
|
||||
const args = Array.from(arguments);
|
||||
if (this.isLevelEnabled(level)) {
|
||||
this._log(level, args);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
levels.levels.forEach(addLevelMethods);
|
||||
|
||||
return {
|
||||
LoggingEvent: LoggingEvent,
|
||||
Logger: Logger
|
||||
Logger.prototype[`is${isLevelMethod}Enabled`] = function () {
|
||||
return this.isLevelEnabled(level);
|
||||
};
|
||||
};
|
||||
|
||||
Logger.prototype[levelMethod] = function (...args) {
|
||||
this.log(level, ...args);
|
||||
};
|
||||
}
|
||||
|
||||
levels.levels.forEach(addLevelMethods);
|
||||
|
||||
configuration.addListener(() => {
|
||||
levels.levels.forEach(addLevelMethods);
|
||||
});
|
||||
|
||||
module.exports = Logger;
|
||||
|
||||
@ -25,7 +25,7 @@
|
||||
"url": "http://github.com/log4js-node/log4js-node/issues"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4.0"
|
||||
"node": ">=6.0"
|
||||
},
|
||||
"scripts": {
|
||||
"clean": "find test -type f ! -name '*.json' ! -name '*.js' ! -name '.eslintrc' -delete && rm *.log",
|
||||
@ -57,7 +57,7 @@
|
||||
"eslint-plugin-import": "^2.8.0",
|
||||
"husky": "^0.14.3",
|
||||
"nyc": "^11.3.0",
|
||||
"sandboxed-module": "^2.0.3",
|
||||
"@log4js-node/sandboxed-module": "^2.1.1",
|
||||
"tap": "^10.7.3",
|
||||
"validate-commit-msg": "^2.14.0"
|
||||
},
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
'use strict';
|
||||
|
||||
const sandbox = require('sandboxed-module');
|
||||
const sandbox = require('@log4js-node/sandboxed-module');
|
||||
|
||||
sandbox.configure({
|
||||
sourceTransformers: {
|
||||
|
||||
42
test/tap/LoggingEvent-test.js
Normal file
42
test/tap/LoggingEvent-test.js
Normal file
@ -0,0 +1,42 @@
|
||||
const test = require('tap').test;
|
||||
const LoggingEvent = require('../../lib/LoggingEvent');
|
||||
const levels = require('../../lib/levels');
|
||||
|
||||
test('LoggingEvent', (batch) => {
|
||||
batch.test('should serialise to JSON', (t) => {
|
||||
const event = new LoggingEvent('cheese', levels.DEBUG, ['log message'], { user: 'bob' });
|
||||
// set the event date to a known value
|
||||
event.startTime = new Date(Date.UTC(2018, 1, 4, 18, 30, 23, 10));
|
||||
const rehydratedEvent = JSON.parse(event.serialise());
|
||||
t.equal(rehydratedEvent.startTime, '2018-02-04T18:30:23.010Z');
|
||||
t.equal(rehydratedEvent.categoryName, 'cheese');
|
||||
t.equal(rehydratedEvent.level.levelStr, 'DEBUG');
|
||||
t.equal(rehydratedEvent.data.length, 1);
|
||||
t.equal(rehydratedEvent.data[0], 'log message');
|
||||
t.equal(rehydratedEvent.context.user, 'bob');
|
||||
t.end();
|
||||
});
|
||||
|
||||
batch.test('should deserialise from JSON', (t) => {
|
||||
const dehydratedEvent = `{
|
||||
"startTime": "2018-02-04T10:25:23.010Z",
|
||||
"categoryName": "biscuits",
|
||||
"level": {
|
||||
"levelStr": "INFO"
|
||||
},
|
||||
"data": [ "some log message", { "x": 1 } ],
|
||||
"context": { "thing": "otherThing" }
|
||||
}`;
|
||||
const event = LoggingEvent.deserialise(dehydratedEvent);
|
||||
t.type(event, LoggingEvent);
|
||||
t.same(event.startTime, new Date(Date.UTC(2018, 1, 4, 10, 25, 23, 10)));
|
||||
t.equal(event.categoryName, 'biscuits');
|
||||
t.same(event.level, levels.INFO);
|
||||
t.equal(event.data[0], 'some log message');
|
||||
t.equal(event.data[1].x, 1);
|
||||
t.equal(event.context.thing, 'otherThing');
|
||||
t.end();
|
||||
});
|
||||
|
||||
batch.end();
|
||||
});
|
||||
@ -1,7 +1,7 @@
|
||||
'use strict';
|
||||
|
||||
const test = require('tap').test;
|
||||
const sandbox = require('sandboxed-module');
|
||||
const sandbox = require('@log4js-node/sandboxed-module');
|
||||
|
||||
test('log4js configure', (batch) => {
|
||||
batch.test('when configuration file loaded via LOG4JS_CONFIG env variable', (t) => {
|
||||
|
||||
@ -1,25 +1,25 @@
|
||||
'use strict';
|
||||
|
||||
const test = require('tap').test;
|
||||
const Configuration = require('../../lib/configuration');
|
||||
const util = require('util');
|
||||
const path = require('path');
|
||||
const sandbox = require('sandboxed-module');
|
||||
const sandbox = require('@log4js-node/sandboxed-module');
|
||||
const log4js = require('../../lib/log4js');
|
||||
const configuration = require('../../lib/configuration');
|
||||
const debug = require('debug')('log4js:test.configuration-validation');
|
||||
|
||||
function testAppender(label) {
|
||||
return {
|
||||
configure: function (config, layouts, findAppender) {
|
||||
return {
|
||||
configureCalled: true,
|
||||
type: config.type,
|
||||
label: label,
|
||||
config: config,
|
||||
layouts: layouts,
|
||||
findAppender: findAppender
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
const testAppender = (label, result) => ({
|
||||
configure: function (config, layouts, findAppender) {
|
||||
debug(`testAppender(${label}).configure called, with config: ${util.inspect(config)}`);
|
||||
result.configureCalled = true;
|
||||
result.type = config.type;
|
||||
result.label = label;
|
||||
result.config = config;
|
||||
result.layouts = layouts;
|
||||
result.findAppender = findAppender;
|
||||
return { };
|
||||
}
|
||||
});
|
||||
|
||||
test('log4js configuration validation', (batch) => {
|
||||
batch.test('should give error if config is just plain silly', (t) => {
|
||||
@ -27,7 +27,7 @@ test('log4js configuration validation', (batch) => {
|
||||
const expectedError =
|
||||
new Error(`Problem with log4js configuration: (${util.inspect(config)}) - must be an object.`);
|
||||
t.throws(
|
||||
() => new Configuration(config),
|
||||
() => configuration.configure(config),
|
||||
expectedError
|
||||
);
|
||||
});
|
||||
@ -38,7 +38,7 @@ test('log4js configuration validation', (batch) => {
|
||||
batch.test('should give error if config is an empty object', (t) => {
|
||||
const expectedError =
|
||||
new Error('Problem with log4js configuration: ({}) - must have a property "appenders" of type object.');
|
||||
t.throws(() => new Configuration({}), expectedError);
|
||||
t.throws(() => log4js.configure({}), expectedError);
|
||||
t.end();
|
||||
});
|
||||
|
||||
@ -46,15 +46,15 @@ test('log4js configuration validation', (batch) => {
|
||||
const expectedError =
|
||||
new Error('Problem with log4js configuration: ({ categories: {} }) ' +
|
||||
'- must have a property "appenders" of type object.');
|
||||
t.throws(() => new Configuration({ categories: {} }), expectedError);
|
||||
t.throws(() => log4js.configure({ categories: {} }), expectedError);
|
||||
t.end();
|
||||
});
|
||||
|
||||
batch.test('should give error if config has no categories', (t) => {
|
||||
const expectedError =
|
||||
new Error('Problem with log4js configuration: ({ appenders: {} }) ' +
|
||||
new Error('Problem with log4js configuration: ({ appenders: { out: { type: \'stdout\' } } }) ' +
|
||||
'- must have a property "categories" of type object.');
|
||||
t.throws(() => new Configuration({ appenders: {} }), expectedError);
|
||||
t.throws(() => log4js.configure({ appenders: { out: { type: 'stdout' } } }), expectedError);
|
||||
t.end();
|
||||
});
|
||||
|
||||
@ -63,7 +63,7 @@ test('log4js configuration validation', (batch) => {
|
||||
new Error('Problem with log4js configuration: ({ appenders: [], categories: [] })' +
|
||||
' - must have a property "appenders" of type object.');
|
||||
t.throws(
|
||||
() => new Configuration({ appenders: [], categories: [] }),
|
||||
() => log4js.configure({ appenders: [], categories: [] }),
|
||||
error
|
||||
);
|
||||
t.end();
|
||||
@ -74,7 +74,7 @@ test('log4js configuration validation', (batch) => {
|
||||
new Error('Problem with log4js configuration: ({ appenders: { thing: \'cheese\' }, categories: {} })' +
|
||||
' - appender "thing" is not valid (must be an object with property "type")');
|
||||
t.throws(
|
||||
() => new Configuration({ appenders: { thing: 'cheese' }, categories: {} }),
|
||||
() => log4js.configure({ appenders: { thing: 'cheese' }, categories: {} }),
|
||||
error
|
||||
);
|
||||
t.end();
|
||||
@ -84,7 +84,7 @@ test('log4js configuration validation', (batch) => {
|
||||
const error = new Error('Problem with log4js configuration: ({ appenders: {}, categories: {} })' +
|
||||
' - must define at least one appender.');
|
||||
t.throws(
|
||||
() => new Configuration({ appenders: {}, categories: {} }),
|
||||
() => log4js.configure({ appenders: {}, categories: {} }),
|
||||
error
|
||||
);
|
||||
t.end();
|
||||
@ -95,7 +95,7 @@ test('log4js configuration validation', (batch) => {
|
||||
'({ appenders: { stdout: { type: \'stdout\' } },\n categories: { thing: \'cheese\' } })' +
|
||||
' - category "thing" is not valid (must be an object with properties "appenders" and "level")');
|
||||
t.throws(
|
||||
() => new Configuration({ appenders: { stdout: { type: 'stdout' } }, categories: { thing: 'cheese' } }),
|
||||
() => log4js.configure({ appenders: { stdout: { type: 'stdout' } }, categories: { thing: 'cheese' } }),
|
||||
error
|
||||
);
|
||||
t.end();
|
||||
@ -107,7 +107,7 @@ test('log4js configuration validation', (batch) => {
|
||||
' categories: { thing: { appenders: [ \'stdout\' ], level: \'ERROR\' } } })' +
|
||||
' - must define a "default" category.');
|
||||
t.throws(
|
||||
() => new Configuration({
|
||||
() => log4js.configure({
|
||||
appenders: { stdout: { type: 'stdout' } },
|
||||
categories: { thing: { appenders: ['stdout'], level: 'ERROR' } }
|
||||
}),
|
||||
@ -121,7 +121,7 @@ test('log4js configuration validation', (batch) => {
|
||||
new Error('Problem with log4js configuration: ({ appenders: { stdout: { type: \'stdout\' } }, categories: {} })' +
|
||||
' - must define at least one category.');
|
||||
t.throws(
|
||||
() => new Configuration({ appenders: { stdout: { type: 'stdout' } }, categories: {} }),
|
||||
() => log4js.configure({ appenders: { stdout: { type: 'stdout' } }, categories: {} }),
|
||||
error
|
||||
);
|
||||
t.end();
|
||||
@ -133,7 +133,7 @@ test('log4js configuration validation', (batch) => {
|
||||
' categories: { thing: { appenders: {}, level: \'ERROR\' } } })' +
|
||||
' - category "thing" is not valid (appenders must be an array of appender names)');
|
||||
t.throws(
|
||||
() => new Configuration({
|
||||
() => log4js.configure({
|
||||
appenders: { stdout: { type: 'stdout' } },
|
||||
categories: { thing: { appenders: {}, level: 'ERROR' } }
|
||||
}),
|
||||
@ -148,7 +148,7 @@ test('log4js configuration validation', (batch) => {
|
||||
' categories: { thing: { appenders: [], level: \'ERROR\' } } })' +
|
||||
' - category "thing" is not valid (appenders must contain at least one appender name)');
|
||||
t.throws(
|
||||
() => new Configuration({
|
||||
() => log4js.configure({
|
||||
appenders: { stdout: { type: 'stdout' } },
|
||||
categories: { thing: { appenders: [], level: 'ERROR' } }
|
||||
}),
|
||||
@ -163,7 +163,7 @@ test('log4js configuration validation', (batch) => {
|
||||
' categories: { thing: { appenders: [ \'cheese\' ], level: \'ERROR\' } } })' +
|
||||
' - category "thing" is not valid (appender "cheese" is not defined)');
|
||||
t.throws(
|
||||
() => new Configuration({
|
||||
() => log4js.configure({
|
||||
appenders: { stdout: { type: 'stdout' } },
|
||||
categories: { thing: { appenders: ['cheese'], level: 'ERROR' } }
|
||||
}),
|
||||
@ -179,7 +179,7 @@ test('log4js configuration validation', (batch) => {
|
||||
' - category "default" is not valid (level "Biscuits" not recognised; ' +
|
||||
'valid levels are ALL, TRACE, DEBUG, INFO, WARN, ERROR, FATAL, MARK, OFF)');
|
||||
t.throws(
|
||||
() => new Configuration({
|
||||
() => log4js.configure({
|
||||
appenders: { stdout: { type: 'stdout' } },
|
||||
categories: { default: { appenders: ['stdout'], level: 'Biscuits' } }
|
||||
}),
|
||||
@ -194,7 +194,7 @@ test('log4js configuration validation', (batch) => {
|
||||
' categories: { default: { appenders: [ \'thing\' ], level: \'ERROR\' } } })' +
|
||||
' - appender "thing" is not valid (type "cheese" could not be found)');
|
||||
t.throws(
|
||||
() => new Configuration({
|
||||
() => log4js.configure({
|
||||
appenders: { thing: { type: 'cheese' } },
|
||||
categories: { default: { appenders: ['thing'], level: 'ERROR' } }
|
||||
}),
|
||||
@ -204,123 +204,136 @@ test('log4js configuration validation', (batch) => {
|
||||
});
|
||||
|
||||
batch.test('should create appender instances', (t) => {
|
||||
const SandboxedConfiguration = sandbox.require(
|
||||
'../../lib/configuration',
|
||||
const thing = {};
|
||||
const sandboxedLog4js = sandbox.require(
|
||||
'../../lib/log4js',
|
||||
{
|
||||
singleOnly: true,
|
||||
requires: {
|
||||
cheese: testAppender('cheesy')
|
||||
}
|
||||
cheese: testAppender('cheesy', thing)
|
||||
},
|
||||
ignoreMissing: true
|
||||
}
|
||||
);
|
||||
|
||||
const config = new SandboxedConfiguration({
|
||||
sandboxedLog4js.configure({
|
||||
appenders: { thing: { type: 'cheese' } },
|
||||
categories: { default: { appenders: ['thing'], level: 'ERROR' } }
|
||||
});
|
||||
|
||||
const thing = config.appenders.get('thing');
|
||||
t.ok(thing.configureCalled);
|
||||
t.equal(thing.type, 'cheese');
|
||||
t.end();
|
||||
});
|
||||
|
||||
batch.test('should load appenders from core first', (t) => {
|
||||
const SandboxedConfiguration = sandbox.require(
|
||||
'../../lib/configuration',
|
||||
const result = {};
|
||||
const sandboxedLog4js = sandbox.require(
|
||||
'../../lib/log4js',
|
||||
{
|
||||
singleOnly: true,
|
||||
requires: {
|
||||
'./appenders/cheese': testAppender('correct'),
|
||||
cheese: testAppender('wrong')
|
||||
}
|
||||
'./cheese': testAppender('correct', result),
|
||||
cheese: testAppender('wrong', result)
|
||||
},
|
||||
ignoreMissing: true
|
||||
}
|
||||
);
|
||||
|
||||
const config = new SandboxedConfiguration({
|
||||
sandboxedLog4js.configure({
|
||||
appenders: { thing: { type: 'cheese' } },
|
||||
categories: { default: { appenders: ['thing'], level: 'ERROR' } }
|
||||
});
|
||||
|
||||
const thing = config.appenders.get('thing');
|
||||
t.ok(thing.configureCalled);
|
||||
t.equal(thing.type, 'cheese');
|
||||
t.equal(thing.label, 'correct');
|
||||
t.ok(result.configureCalled);
|
||||
t.equal(result.type, 'cheese');
|
||||
t.equal(result.label, 'correct');
|
||||
t.end();
|
||||
});
|
||||
|
||||
batch.test('should load appenders relative to main file if not in core, or node_modules', (t) => {
|
||||
const result = {};
|
||||
const mainPath = path.dirname(require.main.filename);
|
||||
const sandboxConfig = { singleOnly: true, requires: {} };
|
||||
sandboxConfig.requires[`${mainPath}/cheese`] = testAppender('correct');
|
||||
const sandboxConfig = {
|
||||
ignoreMissing: true,
|
||||
requires: {}
|
||||
};
|
||||
sandboxConfig.requires[`${mainPath}/cheese`] = testAppender('correct', result);
|
||||
// add this one, because when we're running coverage the main path is a bit different
|
||||
sandboxConfig.requires[
|
||||
`${path.join(mainPath, '../../node_modules/nyc/bin/cheese')}`
|
||||
] = testAppender('correct');
|
||||
const SandboxedConfiguration = sandbox.require('../../lib/configuration', sandboxConfig);
|
||||
] = testAppender('correct', result);
|
||||
const sandboxedLog4js = sandbox.require('../../lib/log4js', sandboxConfig);
|
||||
|
||||
const config = new SandboxedConfiguration({
|
||||
sandboxedLog4js.configure({
|
||||
appenders: { thing: { type: 'cheese' } },
|
||||
categories: { default: { appenders: ['thing'], level: 'ERROR' } }
|
||||
});
|
||||
|
||||
const thing = config.appenders.get('thing');
|
||||
t.ok(thing.configureCalled);
|
||||
t.equal(thing.type, 'cheese');
|
||||
t.equal(thing.label, 'correct');
|
||||
t.ok(result.configureCalled);
|
||||
t.equal(result.type, 'cheese');
|
||||
t.equal(result.label, 'correct');
|
||||
t.end();
|
||||
});
|
||||
|
||||
batch.test('should load appenders relative to process.cwd if not found in core, node_modules', (t) => {
|
||||
const SandboxedConfiguration = sandbox.require(
|
||||
'../../lib/configuration',
|
||||
const result = {};
|
||||
const fakeProcess = new Proxy(process, {
|
||||
get(target, key) {
|
||||
if (key === 'cwd') {
|
||||
return () => '/var/lib/cheese';
|
||||
}
|
||||
|
||||
return target[key];
|
||||
}
|
||||
});
|
||||
const sandboxedLog4js = sandbox.require(
|
||||
'../../lib/log4js',
|
||||
{
|
||||
singleOnly: true,
|
||||
ignoreMissing: true,
|
||||
requires: {
|
||||
'/var/lib/cheese/cheese': testAppender('correct'),
|
||||
'/var/lib/cheese/cheese': testAppender('correct', result),
|
||||
},
|
||||
globals: {
|
||||
process: { cwd: () => '/var/lib/cheese', env: {} }
|
||||
process: fakeProcess
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
const config = new SandboxedConfiguration({
|
||||
sandboxedLog4js.configure({
|
||||
appenders: { thing: { type: 'cheese' } },
|
||||
categories: { default: { appenders: ['thing'], level: 'ERROR' } }
|
||||
});
|
||||
|
||||
const thing = config.appenders.get('thing');
|
||||
t.ok(thing.configureCalled);
|
||||
t.equal(thing.type, 'cheese');
|
||||
t.equal(thing.label, 'correct');
|
||||
t.ok(result.configureCalled);
|
||||
t.equal(result.type, 'cheese');
|
||||
t.equal(result.label, 'correct');
|
||||
t.end();
|
||||
});
|
||||
|
||||
batch.test('should pass config, layout, findAppender to appenders', (t) => {
|
||||
const SandboxedConfiguration = sandbox.require(
|
||||
'../../lib/configuration',
|
||||
const result = {};
|
||||
const sandboxedLog4js = sandbox.require(
|
||||
'../../lib/log4js',
|
||||
{
|
||||
singleOnly: true,
|
||||
ignoreMissing: true,
|
||||
requires: {
|
||||
cheese: testAppender('cheesy')
|
||||
cheese: testAppender('cheesy', result),
|
||||
notCheese: testAppender('notCheesy', {})
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
const config = new SandboxedConfiguration({
|
||||
appenders: { thing: { type: 'cheese', foo: 'bar' }, thing2: { type: 'cheese' } },
|
||||
sandboxedLog4js.configure({
|
||||
appenders: { thing: { type: 'cheese', foo: 'bar' }, thing2: { type: 'notCheese' } },
|
||||
categories: { default: { appenders: ['thing'], level: 'ERROR' } }
|
||||
});
|
||||
|
||||
const thing = config.appenders.get('thing');
|
||||
t.ok(thing.configureCalled);
|
||||
t.equal(thing.type, 'cheese');
|
||||
t.equal(thing.config.foo, 'bar');
|
||||
t.type(thing.layouts, 'object');
|
||||
t.type(thing.layouts.basicLayout, 'function');
|
||||
t.type(thing.findAppender, 'function');
|
||||
t.type(thing.findAppender('thing2'), 'object');
|
||||
t.ok(result.configureCalled);
|
||||
t.equal(result.type, 'cheese');
|
||||
t.equal(result.config.foo, 'bar');
|
||||
t.type(result.layouts, 'object');
|
||||
t.type(result.layouts.basicLayout, 'function');
|
||||
t.type(result.findAppender, 'function');
|
||||
t.type(result.findAppender('thing2'), 'object');
|
||||
t.end();
|
||||
});
|
||||
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
|
||||
const test = require('tap').test;
|
||||
const EE = require('events').EventEmitter;
|
||||
const levels = require('../../lib/levels')();
|
||||
const levels = require('../../lib/levels');
|
||||
|
||||
class MockLogger {
|
||||
constructor() {
|
||||
@ -58,13 +58,13 @@ function request(cl, method, url, code, reqHeaders, resHeaders) {
|
||||
}
|
||||
|
||||
test('log4js connect logger', (batch) => {
|
||||
const clm = require('../../lib/connect-logger')(levels);
|
||||
const clm = require('../../lib/connect-logger');
|
||||
batch.test('getConnectLoggerModule', (t) => {
|
||||
t.type(clm, 'object', 'should return a connect logger factory');
|
||||
t.type(clm, 'function', 'should return a connect logger factory');
|
||||
|
||||
t.test('should take a log4js logger and return a "connect logger"', (assert) => {
|
||||
const ml = new MockLogger();
|
||||
const cl = clm.connectLogger(ml);
|
||||
const cl = clm(ml);
|
||||
|
||||
assert.type(cl, 'function');
|
||||
assert.end();
|
||||
@ -72,7 +72,7 @@ test('log4js connect logger', (batch) => {
|
||||
|
||||
t.test('log events', (assert) => {
|
||||
const ml = new MockLogger();
|
||||
const cl = clm.connectLogger(ml);
|
||||
const cl = clm(ml);
|
||||
request(cl, 'GET', 'http://url', 200);
|
||||
|
||||
const messages = ml.messages;
|
||||
@ -89,7 +89,7 @@ test('log4js connect logger', (batch) => {
|
||||
t.test('log events with level below logging level', (assert) => {
|
||||
const ml = new MockLogger();
|
||||
ml.level = levels.FATAL;
|
||||
const cl = clm.connectLogger(ml);
|
||||
const cl = clm(ml);
|
||||
request(cl, 'GET', 'http://url', 200);
|
||||
|
||||
assert.type(ml.messages, 'Array');
|
||||
@ -100,7 +100,7 @@ test('log4js connect logger', (batch) => {
|
||||
t.test('log events with non-default level and custom format', (assert) => {
|
||||
const ml = new MockLogger();
|
||||
ml.level = levels.INFO;
|
||||
const cl = clm.connectLogger(ml, { level: levels.INFO, format: ':method :url' });
|
||||
const cl = clm(ml, { level: levels.INFO, format: ':method :url' });
|
||||
request(cl, 'GET', 'http://url', 200);
|
||||
|
||||
const messages = ml.messages;
|
||||
@ -116,7 +116,7 @@ test('log4js connect logger', (batch) => {
|
||||
batch.test('logger with options as string', (t) => {
|
||||
const ml = new MockLogger();
|
||||
ml.level = levels.INFO;
|
||||
const cl = clm.connectLogger(ml, ':method :url');
|
||||
const cl = clm(ml, ':method :url');
|
||||
request(cl, 'POST', 'http://meh', 200);
|
||||
|
||||
const messages = ml.messages;
|
||||
@ -127,7 +127,7 @@ test('log4js connect logger', (batch) => {
|
||||
batch.test('auto log levels', (t) => {
|
||||
const ml = new MockLogger();
|
||||
ml.level = levels.INFO;
|
||||
const cl = clm.connectLogger(ml, { level: 'auto', format: ':method :url' });
|
||||
const cl = clm(ml, { level: 'auto', format: ':method :url' });
|
||||
request(cl, 'GET', 'http://meh', 200);
|
||||
request(cl, 'GET', 'http://meh', 201);
|
||||
request(cl, 'GET', 'http://meh', 302);
|
||||
@ -161,7 +161,7 @@ test('log4js connect logger', (batch) => {
|
||||
batch.test('format using a function', (t) => {
|
||||
const ml = new MockLogger();
|
||||
ml.level = levels.INFO;
|
||||
const cl = clm.connectLogger(ml, () => 'I was called');
|
||||
const cl = clm(ml, () => 'I was called');
|
||||
request(cl, 'GET', 'http://blah', 200);
|
||||
|
||||
t.equal(ml.messages[0].message, 'I was called');
|
||||
@ -171,7 +171,7 @@ test('log4js connect logger', (batch) => {
|
||||
batch.test('format that includes request headers', (t) => {
|
||||
const ml = new MockLogger();
|
||||
ml.level = levels.INFO;
|
||||
const cl = clm.connectLogger(ml, ':req[Content-Type]');
|
||||
const cl = clm(ml, ':req[Content-Type]');
|
||||
request(
|
||||
cl,
|
||||
'GET', 'http://blah', 200,
|
||||
@ -185,7 +185,7 @@ test('log4js connect logger', (batch) => {
|
||||
batch.test('format that includes response headers', (t) => {
|
||||
const ml = new MockLogger();
|
||||
ml.level = levels.INFO;
|
||||
const cl = clm.connectLogger(ml, ':res[Content-Type]');
|
||||
const cl = clm(ml, ':res[Content-Type]');
|
||||
request(
|
||||
cl,
|
||||
'GET', 'http://blah', 200,
|
||||
@ -200,7 +200,7 @@ test('log4js connect logger', (batch) => {
|
||||
batch.test('log events with custom token', (t) => {
|
||||
const ml = new MockLogger();
|
||||
ml.level = levels.INFO;
|
||||
const cl = clm.connectLogger(ml, {
|
||||
const cl = clm(ml, {
|
||||
level: levels.INFO,
|
||||
format: ':method :url :custom_string',
|
||||
tokens: [
|
||||
@ -221,7 +221,7 @@ test('log4js connect logger', (batch) => {
|
||||
batch.test('log events with custom override token', (t) => {
|
||||
const ml = new MockLogger();
|
||||
ml.level = levels.INFO;
|
||||
const cl = clm.connectLogger(ml, {
|
||||
const cl = clm(ml, {
|
||||
level: levels.INFO,
|
||||
format: ':method :url :date',
|
||||
tokens: [
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
|
||||
const test = require('tap').test;
|
||||
const EE = require('events').EventEmitter;
|
||||
const levels = require('../../lib/levels')();
|
||||
const levels = require('../../lib/levels');
|
||||
|
||||
class MockLogger {
|
||||
constructor() {
|
||||
@ -41,11 +41,11 @@ class MockResponse extends EE {
|
||||
}
|
||||
|
||||
test('log4js connect logger', (batch) => {
|
||||
const clm = require('../../lib/connect-logger')(levels);
|
||||
const clm = require('../../lib/connect-logger');
|
||||
|
||||
batch.test('with nolog config', (t) => {
|
||||
const ml = new MockLogger();
|
||||
const cl = clm.connectLogger(ml, { nolog: '\\.gif' });
|
||||
const cl = clm(ml, { nolog: '\\.gif' });
|
||||
|
||||
t.beforeEach((done) => { ml.messages = []; done(); });
|
||||
|
||||
@ -82,7 +82,7 @@ test('log4js connect logger', (batch) => {
|
||||
|
||||
batch.test('nolog Strings', (t) => {
|
||||
const ml = new MockLogger();
|
||||
const cl = clm.connectLogger(ml, { nolog: '\\.gif|\\.jpe?g' });
|
||||
const cl = clm(ml, { nolog: '\\.gif|\\.jpe?g' });
|
||||
|
||||
t.beforeEach((done) => { ml.messages = []; done(); });
|
||||
|
||||
@ -129,7 +129,7 @@ test('log4js connect logger', (batch) => {
|
||||
|
||||
batch.test('nolog Array<String>', (t) => {
|
||||
const ml = new MockLogger();
|
||||
const cl = clm.connectLogger(ml, { nolog: ['\\.gif', '\\.jpe?g'] });
|
||||
const cl = clm(ml, { nolog: ['\\.gif', '\\.jpe?g'] });
|
||||
|
||||
t.beforeEach((done) => { ml.messages = []; done(); });
|
||||
|
||||
@ -176,7 +176,7 @@ test('log4js connect logger', (batch) => {
|
||||
|
||||
batch.test('nolog RegExp', (t) => {
|
||||
const ml = new MockLogger();
|
||||
const cl = clm.connectLogger(ml, { nolog: /\.gif|\.jpe?g/ });
|
||||
const cl = clm(ml, { nolog: /\.gif|\.jpe?g/ });
|
||||
|
||||
t.beforeEach((done) => { ml.messages = []; done(); });
|
||||
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
'use strict';
|
||||
|
||||
const test = require('tap').test;
|
||||
const sandbox = require('sandboxed-module');
|
||||
const sandbox = require('@log4js-node/sandboxed-module');
|
||||
|
||||
test('log4js console appender', (batch) => {
|
||||
batch.test('should output to console', (t) => {
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
'use strict';
|
||||
|
||||
const test = require('tap').test;
|
||||
const sandbox = require('sandboxed-module');
|
||||
const sandbox = require('@log4js-node/sandboxed-module');
|
||||
|
||||
test('default settings', (t) => {
|
||||
const output = [];
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
'use strict';
|
||||
|
||||
const test = require('tap').test;
|
||||
const sandbox = require('sandboxed-module');
|
||||
const sandbox = require('@log4js-node/sandboxed-module');
|
||||
|
||||
test('file appender SIGHUP', (t) => {
|
||||
let closeCalled = 0;
|
||||
@ -45,5 +45,5 @@ test('file appender SIGHUP', (t) => {
|
||||
t.equal(openCalled, 1, 'open should be called once');
|
||||
t.equal(closeCalled, 1, 'close should be called once');
|
||||
t.end();
|
||||
}, 10);
|
||||
}, 100);
|
||||
});
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
const test = require('tap').test;
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const sandbox = require('sandboxed-module');
|
||||
const sandbox = require('@log4js-node/sandboxed-module');
|
||||
const log4js = require('../../lib/log4js');
|
||||
const zlib = require('zlib');
|
||||
const EOL = require('os').EOL || '\n';
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
'use strict';
|
||||
|
||||
const test = require('tap').test;
|
||||
const sandbox = require('sandboxed-module');
|
||||
const sandbox = require('@log4js-node/sandboxed-module');
|
||||
const realLayouts = require('../../lib/layouts');
|
||||
|
||||
const setupLogging = function (options, category, compressedLength) {
|
||||
@ -48,6 +48,7 @@ const setupLogging = function (options, category, compressedLength) {
|
||||
let exitHandler;
|
||||
|
||||
const fakeConsole = {
|
||||
log: () => {},
|
||||
error: function (message) {
|
||||
this.message = message;
|
||||
}
|
||||
@ -63,7 +64,6 @@ const setupLogging = function (options, category, compressedLength) {
|
||||
};
|
||||
|
||||
const log4js = sandbox.require('../../lib/log4js', {
|
||||
// singleOnly: true,
|
||||
requires: {
|
||||
dgram: fakeDgram,
|
||||
zlib: fakeZlib,
|
||||
@ -71,6 +71,7 @@ const setupLogging = function (options, category, compressedLength) {
|
||||
},
|
||||
globals: {
|
||||
process: {
|
||||
version: process.version,
|
||||
on: function (evt, handler) {
|
||||
if (evt === 'exit') {
|
||||
exitHandler = handler;
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
'use strict';
|
||||
|
||||
const test = require('tap').test;
|
||||
const sandbox = require('sandboxed-module');
|
||||
const sandbox = require('@log4js-node/sandboxed-module');
|
||||
|
||||
function setupLogging(category, options) {
|
||||
const lastRequest = {};
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
'use strict';
|
||||
|
||||
const test = require('tap').test;
|
||||
const levels = require('../../lib/levels')();
|
||||
const levels = require('../../lib/levels');
|
||||
|
||||
function assertThat(assert, level) {
|
||||
function assertForEach(assertion, testFn, otherLevels) {
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
'use strict';
|
||||
|
||||
const test = require('tap').test;
|
||||
const sandbox = require('sandboxed-module');
|
||||
const sandbox = require('@log4js-node/sandboxed-module');
|
||||
|
||||
function setupLogging(category, options) {
|
||||
const fakeAxios = {
|
||||
@ -21,6 +21,7 @@ function setupLogging(category, options) {
|
||||
};
|
||||
|
||||
const fakeConsole = {
|
||||
log: () => {},
|
||||
error: function (msg) {
|
||||
this.msg = msg;
|
||||
}
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
'use strict';
|
||||
|
||||
const test = require('tap').test;
|
||||
const sandbox = require('sandboxed-module');
|
||||
const sandbox = require('@log4js-node/sandboxed-module');
|
||||
|
||||
function setupLogging(category, options) {
|
||||
const fakeDgram = {
|
||||
@ -21,6 +21,7 @@ function setupLogging(category, options) {
|
||||
};
|
||||
|
||||
const fakeConsole = {
|
||||
log: () => {},
|
||||
error: function (msg, err) {
|
||||
this.msg = msg;
|
||||
this.err = err;
|
||||
|
||||
@ -1,30 +1,35 @@
|
||||
'use strict';
|
||||
|
||||
const test = require('tap').test;
|
||||
const levels = require('../../lib/levels')();
|
||||
const debug = require('debug')('log4js:test.logger');
|
||||
const levels = require('../../lib/levels');
|
||||
const sandbox = require('@log4js-node/sandboxed-module');
|
||||
|
||||
const events = [];
|
||||
const Logger = sandbox.require(
|
||||
'../../lib/logger',
|
||||
{
|
||||
requires: {
|
||||
'./levels': levels,
|
||||
'./clustering': {
|
||||
isMaster: () => true,
|
||||
onlyOnMaster: fn => fn(),
|
||||
send: (evt) => {
|
||||
debug('fake clustering got event:', evt);
|
||||
events.push(evt);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
const testConfig = {
|
||||
level: levels.TRACE
|
||||
};
|
||||
|
||||
const loggerModule = require('../../lib/logger')(
|
||||
levels,
|
||||
() => testConfig.level,
|
||||
(category, level) => { testConfig.level = level; }
|
||||
);
|
||||
|
||||
const Logger = loggerModule.Logger;
|
||||
const testDispatcher = {
|
||||
events: [],
|
||||
dispatch: function (evt) {
|
||||
this.events.push(evt);
|
||||
}
|
||||
};
|
||||
const dispatch = testDispatcher.dispatch.bind(testDispatcher);
|
||||
|
||||
test('../../lib/logger', (batch) => {
|
||||
batch.beforeEach((done) => {
|
||||
testDispatcher.events = [];
|
||||
events.length = 0;
|
||||
testConfig.level = levels.TRACE;
|
||||
done();
|
||||
});
|
||||
@ -32,28 +37,20 @@ test('../../lib/logger', (batch) => {
|
||||
batch.test('constructor with no parameters', (t) => {
|
||||
t.throws(
|
||||
() => new Logger(),
|
||||
new Error('No dispatch function provided.')
|
||||
);
|
||||
t.end();
|
||||
});
|
||||
|
||||
batch.test('constructor with only dispatch', (t) => {
|
||||
t.throws(
|
||||
() => new Logger(dispatch),
|
||||
new Error('No category provided.')
|
||||
);
|
||||
t.end();
|
||||
});
|
||||
|
||||
batch.test('constructor with category', (t) => {
|
||||
const logger = new Logger(dispatch, 'cheese');
|
||||
const logger = new Logger('cheese');
|
||||
t.equal(logger.category, 'cheese', 'should use category');
|
||||
t.equal(logger.level, levels.TRACE, 'should use TRACE log level');
|
||||
t.equal(logger.level, levels.OFF, 'should use OFF log level');
|
||||
t.end();
|
||||
});
|
||||
|
||||
batch.test('set level should delegate', (t) => {
|
||||
const logger = new Logger(dispatch, 'cheese');
|
||||
const logger = new Logger('cheese');
|
||||
logger.level = 'debug';
|
||||
t.equal(logger.category, 'cheese', 'should use category');
|
||||
t.equal(logger.level, levels.DEBUG, 'should use level');
|
||||
@ -61,7 +58,7 @@ test('../../lib/logger', (batch) => {
|
||||
});
|
||||
|
||||
batch.test('isLevelEnabled', (t) => {
|
||||
const logger = new Logger(dispatch, 'cheese');
|
||||
const logger = new Logger('cheese');
|
||||
const functions = [
|
||||
'isTraceEnabled', 'isDebugEnabled', 'isInfoEnabled',
|
||||
'isWarnEnabled', 'isErrorEnabled', 'isFatalEnabled'
|
||||
@ -83,11 +80,11 @@ test('../../lib/logger', (batch) => {
|
||||
});
|
||||
|
||||
batch.test('should send log events to dispatch function', (t) => {
|
||||
const logger = new Logger(dispatch, 'cheese');
|
||||
const logger = new Logger('cheese');
|
||||
logger.level = 'debug';
|
||||
logger.debug('Event 1');
|
||||
logger.debug('Event 2');
|
||||
logger.debug('Event 3');
|
||||
const events = testDispatcher.events;
|
||||
|
||||
t.equal(events.length, 3);
|
||||
t.equal(events[0].data[0], 'Event 1');
|
||||
@ -97,7 +94,8 @@ test('../../lib/logger', (batch) => {
|
||||
});
|
||||
|
||||
batch.test('should add context values to every event', (t) => {
|
||||
const logger = new Logger(dispatch, 'fromage');
|
||||
const logger = new Logger('fromage');
|
||||
logger.level = 'debug';
|
||||
logger.debug('Event 1');
|
||||
logger.addContext('cheese', 'edam');
|
||||
logger.debug('Event 2');
|
||||
@ -108,7 +106,6 @@ test('../../lib/logger', (batch) => {
|
||||
logger.debug('Event 5');
|
||||
logger.clearContext();
|
||||
logger.debug('Event 6');
|
||||
const events = testDispatcher.events;
|
||||
|
||||
t.equal(events.length, 6);
|
||||
t.same(events[0].context, {});
|
||||
@ -121,10 +118,10 @@ test('../../lib/logger', (batch) => {
|
||||
});
|
||||
|
||||
batch.test('should not break when log data has no toString', (t) => {
|
||||
const logger = new Logger(dispatch, 'thing');
|
||||
const logger = new Logger('thing');
|
||||
logger.level = 'debug';
|
||||
logger.info('Just testing ', Object.create(null));
|
||||
|
||||
const events = testDispatcher.events;
|
||||
t.equal(events.length, 1);
|
||||
t.end();
|
||||
});
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
'use strict';
|
||||
|
||||
const test = require('tap').test;
|
||||
const sandbox = require('sandboxed-module');
|
||||
const sandbox = require('@log4js-node/sandboxed-module');
|
||||
const recording = require('../../lib/appenders/recording');
|
||||
|
||||
test('log4js', (batch) => {
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
'use strict';
|
||||
|
||||
const test = require('tap').test;
|
||||
const sandbox = require('sandboxed-module');
|
||||
const sandbox = require('@log4js-node/sandboxed-module');
|
||||
const layouts = require('../../lib/layouts');
|
||||
|
||||
function setupLogging(category, options) {
|
||||
@ -33,6 +33,7 @@ function setupLogging(category, options) {
|
||||
};
|
||||
|
||||
const fakeConsole = {
|
||||
log: () => {},
|
||||
errors: [],
|
||||
error: function (msg, value) {
|
||||
this.errors.push({ msg: msg, value: value });
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
'use strict';
|
||||
|
||||
const test = require('tap').test;
|
||||
const sandbox = require('sandboxed-module');
|
||||
const sandbox = require('@log4js-node/sandboxed-module');
|
||||
|
||||
function setupLogging(category, options) {
|
||||
const fakeAxios = {
|
||||
@ -21,6 +21,7 @@ function setupLogging(category, options) {
|
||||
};
|
||||
|
||||
const fakeConsole = {
|
||||
log: () => {},
|
||||
error: function (msg) {
|
||||
this.msg = msg;
|
||||
}
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
'use strict';
|
||||
|
||||
const test = require('tap').test;
|
||||
const sandbox = require('sandboxed-module');
|
||||
const sandbox = require('@log4js-node/sandboxed-module');
|
||||
|
||||
function setupLogging(category, options) {
|
||||
const udpSent = {};
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
|
||||
const test = require('tap').test;
|
||||
const layouts = require('../../lib/layouts');
|
||||
const sandbox = require('sandboxed-module');
|
||||
const sandbox = require('@log4js-node/sandboxed-module');
|
||||
|
||||
function setupLogging(category, options) {
|
||||
const msgs = [];
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
const test = require('tap').test;
|
||||
const log4js = require('../../lib/log4js');
|
||||
const net = require('net');
|
||||
const sandbox = require('sandboxed-module');
|
||||
const sandbox = require('@log4js-node/sandboxed-module');
|
||||
|
||||
test('multiprocess appender shutdown (master)', { timeout: 2000 }, (t) => {
|
||||
log4js.configure({
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
'use strict';
|
||||
|
||||
const test = require('tap').test;
|
||||
const sandbox = require('sandboxed-module');
|
||||
const sandbox = require('@log4js-node/sandboxed-module');
|
||||
const recording = require('../../lib/appenders/recording');
|
||||
|
||||
function makeFakeNet() {
|
||||
|
||||
51
test/tap/passenger-test.js
Normal file
51
test/tap/passenger-test.js
Normal file
@ -0,0 +1,51 @@
|
||||
const test = require('tap').test;
|
||||
const sandbox = require('@log4js-node/sandboxed-module');
|
||||
|
||||
// passenger provides a non-functional cluster module,
|
||||
// but it does not implement the event emitter functions
|
||||
// this is taken from https://github.com/phusion/passenger/blob/82bef697c0019c034faeb9b0f8c08a43ec4e1e22/src/helper-scripts/node-loader.js#L64
|
||||
const passengerCluster = {
|
||||
disconnect: function () { return false; },
|
||||
fork: function () { return false; },
|
||||
setupMaster: function () { return false; },
|
||||
isWorker: true,
|
||||
isMaster: false,
|
||||
schedulingPolicy: false,
|
||||
settings: false,
|
||||
worker: false,
|
||||
workers: false,
|
||||
};
|
||||
|
||||
const vcr = require('../../lib/appenders/recording');
|
||||
|
||||
const log4js = sandbox.require(
|
||||
'../../lib/log4js',
|
||||
{
|
||||
requires: {
|
||||
cluster: passengerCluster,
|
||||
'./appenders/recording': vcr
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
test('When running in Passenger', (batch) => {
|
||||
batch.test('it should still log', (t) => {
|
||||
log4js.configure({
|
||||
appenders: {
|
||||
vcr: { type: 'recording' }
|
||||
},
|
||||
categories: {
|
||||
default: { appenders: ['vcr'], level: 'info' }
|
||||
},
|
||||
disableClustering: true
|
||||
});
|
||||
log4js.getLogger().info('This should still work');
|
||||
|
||||
const events = vcr.replay();
|
||||
t.equal(events.length, 1);
|
||||
t.equal(events[0].data[0], 'This should still work');
|
||||
t.end();
|
||||
});
|
||||
|
||||
batch.end();
|
||||
});
|
||||
@ -1,7 +1,7 @@
|
||||
'use strict';
|
||||
|
||||
const test = require('tap').test;
|
||||
const sandbox = require('sandboxed-module');
|
||||
const sandbox = require('@log4js-node/sandboxed-module');
|
||||
|
||||
function setupLogging(category, options) {
|
||||
const fakeRabbitmq = {
|
||||
@ -24,6 +24,7 @@ function setupLogging(category, options) {
|
||||
};
|
||||
|
||||
const fakeConsole = {
|
||||
log: () => {},
|
||||
errors: [],
|
||||
error: function (msg) {
|
||||
this.errors.push(msg);
|
||||
|
||||
@ -1,8 +1,7 @@
|
||||
'use strict';
|
||||
|
||||
const test = require('tap').test;
|
||||
// const log4js = require('../../lib/log4js');
|
||||
const sandbox = require('sandboxed-module');
|
||||
const sandbox = require('@log4js-node/sandboxed-module');
|
||||
|
||||
function setupLogging(category, options) {
|
||||
const fakeRedis = {
|
||||
@ -25,6 +24,7 @@ function setupLogging(category, options) {
|
||||
};
|
||||
|
||||
const fakeConsole = {
|
||||
log: () => {},
|
||||
errors: [],
|
||||
error: function (msg) {
|
||||
this.errors.push(msg);
|
||||
|
||||
47
test/tap/server-test.js
Normal file
47
test/tap/server-test.js
Normal file
@ -0,0 +1,47 @@
|
||||
const test = require('tap').test;
|
||||
const net = require('net');
|
||||
const log4js = require('../../lib/log4js');
|
||||
const vcr = require('../../lib/appenders/recording');
|
||||
const levels = require('../../lib/levels');
|
||||
const LoggingEvent = require('../../lib/LoggingEvent');
|
||||
|
||||
log4js.configure({
|
||||
appenders: {
|
||||
vcr: { type: 'recording' },
|
||||
tcp: { type: 'tcp-server', port: 5678 }
|
||||
},
|
||||
categories: {
|
||||
default: { appenders: ['vcr'], level: 'debug' }
|
||||
}
|
||||
});
|
||||
|
||||
test('TCP Server', (batch) => {
|
||||
batch.test('should listen for TCP messages and re-send via process.send', (t) => {
|
||||
// give the socket a chance to start up
|
||||
setTimeout(() => {
|
||||
const socket = net.connect(5678, () => {
|
||||
socket.write(
|
||||
(new LoggingEvent('test-category', levels.INFO, ['something'], {})).serialise(),
|
||||
() => {
|
||||
socket.end();
|
||||
setTimeout(() => {
|
||||
log4js.shutdown(() => {
|
||||
const logs = vcr.replay();
|
||||
t.equal(logs.length, 1);
|
||||
t.match(logs[0], {
|
||||
data: ['something'],
|
||||
categoryName: 'test-category',
|
||||
level: { levelStr: 'INFO' },
|
||||
context: {}
|
||||
});
|
||||
t.end();
|
||||
});
|
||||
}, 100);
|
||||
}
|
||||
);
|
||||
});
|
||||
socket.unref();
|
||||
}, 100);
|
||||
});
|
||||
batch.end();
|
||||
});
|
||||
@ -1,7 +1,7 @@
|
||||
'use strict';
|
||||
|
||||
const test = require('tap').test;
|
||||
const sandbox = require('sandboxed-module');
|
||||
const sandbox = require('@log4js-node/sandboxed-module');
|
||||
const realLayouts = require('../../lib/layouts');
|
||||
|
||||
function setupLogging(category, options) {
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
|
||||
const test = require('tap').test;
|
||||
const realLayouts = require('../../lib/layouts');
|
||||
const sandbox = require('sandboxed-module');
|
||||
const sandbox = require('@log4js-node/sandboxed-module');
|
||||
|
||||
function setupLogging(category, options, errorOnSend) {
|
||||
const msgs = [];
|
||||
@ -36,6 +36,7 @@ function setupLogging(category, options, errorOnSend) {
|
||||
};
|
||||
|
||||
const fakeConsole = {
|
||||
log: () => {},
|
||||
errors: [],
|
||||
error: function (msg, value) {
|
||||
this.errors.push({ msg: msg, value: value });
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
|
||||
const test = require('tap').test;
|
||||
const layouts = require('../../lib/layouts');
|
||||
const sandbox = require('sandboxed-module');
|
||||
const sandbox = require('@log4js-node/sandboxed-module');
|
||||
|
||||
test('stderr appender', (t) => {
|
||||
const output = [];
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
|
||||
const test = require('tap').test;
|
||||
const layouts = require('../../lib/layouts');
|
||||
const sandbox = require('sandboxed-module');
|
||||
const sandbox = require('@log4js-node/sandboxed-module');
|
||||
|
||||
test('stdout appender', (t) => {
|
||||
const output = [];
|
||||
|
||||
45
test/tap/tcp-appender-test.js
Normal file
45
test/tap/tcp-appender-test.js
Normal file
@ -0,0 +1,45 @@
|
||||
const test = require('tap').test;
|
||||
const net = require('net');
|
||||
const log4js = require('../../lib/log4js');
|
||||
|
||||
const messages = [];
|
||||
const server = net.createServer((socket) => {
|
||||
socket.setEncoding('utf8');
|
||||
socket.on('data', (data) => {
|
||||
messages.push(JSON.parse(data));
|
||||
});
|
||||
});
|
||||
|
||||
server.unref();
|
||||
|
||||
server.listen(() => {
|
||||
const port = server.address().port;
|
||||
log4js.configure({
|
||||
appenders: {
|
||||
tcp: { type: 'tcp', port: port }
|
||||
},
|
||||
categories: {
|
||||
default: { appenders: ['tcp'], level: 'debug' }
|
||||
}
|
||||
});
|
||||
|
||||
const logger = log4js.getLogger();
|
||||
logger.info('This should be sent via TCP.');
|
||||
log4js.shutdown(() => {
|
||||
server.close(() => {
|
||||
test('TCP Appender', (batch) => {
|
||||
batch.test('should send log messages as JSON over TCP', (t) => {
|
||||
t.equal(messages.length, 1);
|
||||
t.match(messages[0], {
|
||||
data: ['This should be sent via TCP.'],
|
||||
categoryName: 'default',
|
||||
context: {},
|
||||
level: { levelStr: 'INFO' }
|
||||
});
|
||||
t.end();
|
||||
});
|
||||
batch.end();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
Loading…
x
Reference in New Issue
Block a user