
log4js-cloudwatch-appender
Simple appender for log4js to submit logs to AWS cloudwatch based on the lawgs module.
Installation
This module is installed via npm: ``` npm install --save log4js-cloudwatch-appender ```Usage
Add aws appender to the log4js config file: ```js 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 alwgs config object>'
}
},
categories: {
default: {appenders: ['aws'], level: 'info'}
}
}
```
This will cause logs to be sent to AWS CloudWatch with the specified group and stream.
Configuration
If you are using roles, you will need the following roles:- logs:DescribeLogGroups
- logs:DescribeLogStreams
- logs:CreateLogGroup
- logs:CreateLogStream
- logs:PutLogEvents
mandatory
region
- The CloudWatch region
logGroup
- The log group to send the metrics to
logStream
- The log stream of the group to send the metrics to
optional
accessKeyId
- Optional if credentials are set in~/.aws/credentials
secretAccessKey
- Optional if credentials are set in~/.aws/credentials
layout
- Custom layout. See suggested layout
lawgsConfig
- Optional vonfig 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.
Suggested json layout
Logs are easier to query whn they are formatted as json. Following is a suggested json layout to set for this appender. The logging style should be: ```js const uuid = require('node-uuid'); const corr = uuid.v4(); const logger = logFactory.getLogger('category'); logger.info(corr, 'methodName()','part1','part2'); ``` Which will output: ```json { "timestamp": "2017-06-10T11:55:38.251Z", "corr": "2e2c99aa-7eee-4fd2-ae36-cd9dc9533816", "app": "", "host": "", "pid": 24532, "level": "INFO", "category": "category", "method": "methodName()", "message": "part1 part2" } ``` The layout: ```js const util = require('util'); const = require('underscore'); let processName = path.basename(process.argv1); 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{myip}", "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');
}
```
Contributing
Please make all pull requests to themaster
branch and ensure tests pass
locally.