Simple appender for log4js to submit logs to AWS cloudwatch based on the lawgs module.
This module is installed via npm:
npm install --save log4js-cloudwatch-appender
Add aws appender to the log4js config file:
const config = {
appenders: {
aws: {
type: "log4js-cloudwatch-appender",
accessKeyId: '<accessKeyId>',
secretAccessKey: '<secretAccessKey>',
region: 'eu-central-1',
logGroup: 'prod',
logStream: 'apps',
layout: '<custom layout object>',
lawgsConfig: '<optional lawgs config object>'
}
},
categories: {
default: {appenders: ['aws'], level: 'info'}
}
}
This will cause logs to be sent to AWS CloudWatch with the specified group and stream.
If you are using roles, you will need the following roles:
- logs:DescribeLogGroups
- logs:DescribeLogStreams
- logs:CreateLogGroup
- logs:CreateLogStream
- logs:PutLogEvents
region
- The CloudWatch regionlogGroup
- The log group to send the metrics tologStream
- The log stream of the group to send the metrics to
accessKeyId
- Optional if credentials are set in~/.aws/credentials
secretAccessKey
- Optional if credentials are set in~/.aws/credentials
layout
- Custom layout. See suggested layoutlawgsConfig
- Optional config object for lawgs:showDebugLogs
- Show debug logs. Default: false.uploadMaxTimer
- After this ms timeout, flush to server. Default: 5000.uploadBatchSize
- After this amount of logs, flush to server. Default: 500.
Logs are easier to query when they are formatted as json. Following is a suggested json layout to set for this appender. The logging style should be:
const uuid = require('node-uuid');
const corr = uuid.v4();
const logger = logFactory.getLogger('category');
logger.info(corr, 'methodName()','part1','part2');
Which will output:
{
"timestamp": "2017-06-10T11:55:38.251Z",
"corr": "2e2c99aa-7eee-4fd2-ae36-cd9dc9533816",
"app": "<appName>",
"host": "<ip>",
"pid": 24532,
"level": "INFO",
"category": "category",
"method": "methodName()",
"message": "part1 part2"
}
The layout:
const util = require('util');
const _ = require('underscore');
let processName = path.basename(process.argv[1]);
processName = processName.substring(0, processName.length - 3);
const publicIp = require('public-ip').v4;
let ip = '';
publicIp()
.then(function (_ip) {
ip = _ip;
})
.catch(function (e) {
console.log(e);
ip = 'unknown';
});
const jsonLayout = {
"type": "pattern",
"pattern": '{"timestamp": "%d{yyyy-MM-ddThh:mm:ss.SSSZ}", "app": "' + processName + '", "ip": "%x{my_ip}", "host": "%h", "pid": %z, "level": "%p", "category": "%c"%x{corr}%x{method}, "message": "%x{message}"}',
"tokens": {
"my_ip": function () {
return ip;
},
"corr": function (logEvent) {
logEvent.__data__ = _.map(logEvent.data, _.clone);
if (logEvent.__data__) {
let corr = logEvent.__data__[0];
if (Array.isArray(corr) && corr.length === 2) {
corr = corr[0];
if (typeof corr === 'string' && corr.length === 36 && corr.split("-").length === 5) {
logEvent.__data__[0] = logEvent.__data__[0][1];
return ', "corr": "' + corr + '"';
}
}
if (logEvent.__data__.length > 1 && corr && typeof corr === 'string' && corr.length === 36 && corr.split("-").length === 5) {
logEvent.__data__.shift();
return ', "corr": "' + corr + '"';
}
}
return '';
},
"method": function (logEvent) {
if (logEvent.__data__) {
const method = logEvent.__data__[0];
if (logEvent.__data__.length > 1 && method && typeof method === 'string' && method.indexOf("()", method.length - 2) !== -1) {
logEvent.__data__.shift();
return ', "method": "' + method + '"';
}
}
return '';
},
"message": function (logEvent) {
if (logEvent.__data__) {
let data = logEvent.__data__;
data = util.format.apply(util, wrapErrorsWithInspect(data));
data = escapedStringify(data);
logEvent.__data__ = undefined;
return data;
}
return '';
}
}
};
function wrapErrorsWithInspect(items) {
return items.map(function (item) {
if ((item instanceof Error) && item.stack) {
return {
inspect: function () {
return util.format(item) + '\n' + item.stack;
}
};
} else {
return item;
}
});
}
function escapedStringify(json) {
return json
.replace(/[\\]/g, '\\\\')
.replace(/[\"]/g, '\\\"')
.replace(/[\/]/g, '\\/')
.replace(/[\b]/g, '\\b')
.replace(/[\f]/g, '\\f')
.replace(/[\n]/g, '\\n')
.replace(/[\r]/g, '\\r')
.replace(/[\t]/g, '\\t');
}
Please make all pull requests to the master
branch and ensure tests pass
locally.