Skip to content

Commit c5f342c

Browse files
authored
Support custom layouts (#6)
- require logs4js 2.x as a peer dependency - use msg_timestamp (in default layout) instead of logtime so logmet treats timestamp as date type
1 parent 0d2d7d3 commit c5f342c

File tree

6 files changed

+97
-231
lines changed

6 files changed

+97
-231
lines changed

.eslintrc.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ rules:
33
quotes: [error, single, {allowTemplateLiterals: true}]
44
indent: [error, 4]
55
linebreak-style: [error, unix]
6-
no-unused-vars: warn
6+
no-console: warn
77
env:
88
es6: true
99
node: true
10-
extends: eslint:recommended
10+
extends: eslint:recommended

.gitignore

Lines changed: 0 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1 @@
1-
# Logs
2-
logs
3-
*.log
4-
5-
# Runtime data
6-
pids
7-
*.pid
8-
*.seed
9-
10-
# Directory for instrumented libs generated by jscoverage/JSCover
11-
lib-cov
12-
13-
# Coverage directory used by tools like istanbul
14-
coverage
15-
16-
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
17-
.grunt
18-
19-
# node-waf configuration
20-
.lock-wscript
21-
22-
# Compiled binary addons (http://nodejs.org/api/addons.html)
23-
build/Release
24-
25-
# Dependency directory
26-
# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git
271
node_modules

README.md

Lines changed: 22 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -19,63 +19,41 @@ export log4js_logmet_logging_port=9091
1919
To get the secure token and space id see the instructions here: https://pages.github.ibm.com/alchemy-logmet/getting-started/authentication.html
2020

2121
## Usage
22-
You must be using [log4js-node](https://github.com/nomiddlename/log4js-node) and must call `log4js.configure('/path/to/log4js.json')`
22+
You must be using [log4js-node](https://github.com/nomiddlename/log4js-node) 2.x and must call `log4js.configure('/path/to/log4js.json')`
2323
somewhere in your code.
2424

2525
You must add the following to the list of appenders in your `log4js.json` file:
2626

2727
```
2828
{
29-
"type": "log4js-logmet-appender"
29+
"appenders": {
30+
"logmet": {
31+
"type": "log4js-logmet-appender",
32+
}
33+
},
34+
"categories": {
35+
"default": { "appenders": [ "logmet" ], "level": "info" },
36+
}
3037
}
3138
```
3239

33-
Using this configuration log messages to logmet will contain the following fields:
34-
- `component`: value of the environment variable `log4js_logmet_component`
35-
- `host-ip`: value of the environmental variable `CF_INSTANCE_IP`
36-
- `instance-id`: value of the environmental variable `CF_INSTANCE_INDEX`
37-
- `loglevel`: logging level (eg. `INFO`)
38-
- `logtime`: time message was logged in ISO string format (eg. `2011-10-05T14:48:00.000Z`)
39-
- `message`: log message/data, data array elements are concatenated with ` | ` delimeter
40-
41-
### Custom Fields
42-
43-
To use custom fields, you should define an `options.fields` object.
44-
Note: If a `fields` object is defined, none of the default fields will be sent.
40+
The appender supports custom layouts, the default layout function used is:
4541

46-
You can choose names for the log level (`"level_field_name"`), timestamp (`"timestamp_field_name"`) and data/message (`"data_field_name"`) fields. You may also add more custom static fields (under the `"static_fields"` object), fields that have node `process` object properties as values (under the `"process_fields"` object), and fields that have environment variables as values (under the `"env_fields"` object).
47-
48-
Example with custom fields:
4942
```
50-
{
51-
"type": "log4js-logmet-appender",
52-
"options": {
53-
"fields": {
54-
"level_field_name": "PRIORITY",
55-
"timestamp_field_name": "TIMESTAMP",
56-
"data_field_name": "MESSAGE"
57-
58-
"static_fields": {
59-
"COMPONENT": "otc-api"
60-
},
61-
62-
"process_fields": {
63-
"PROCESS_ID": "pid"
64-
},
65-
66-
"env_fields": {
67-
"INSTANCE_INDEX": "CF_INSTANCE_INDEX"
68-
}
69-
}
70-
}
71-
}
43+
let layout = (logEvent) => {
44+
return {
45+
'component': process.env.log4js_logmet_component || config.component,
46+
'host-ip': process.env.CF_INSTANCE_IP,
47+
'instance-id': process.env.CF_INSTANCE_INDEX,
48+
'loglevel': logEvent.level.levelStr,
49+
'msg_timestamp': logEvent.startTime.toISOString(),
50+
'message': logEvent.data.join(' | '),
51+
'type': logEvent.categoryName
52+
};
53+
};
7254
```
7355

74-
You may also define custom dynamic fields by setting `options.fields` `"data_field_augment"` to `true` and passing a single object argument to the logger. Object keys will be mapped to field names.
75-
76-
Example: `logger.info({ MESSAGE: "hello", FLAG: "urgent" });`
77-
78-
Note: If `"data_field_augment"` is set to `true` and a single object argument is NOT passed to the logger, the log message/data will still be sent as a regular field if `"data_field_name"` is also set.
56+
Each key will mapped to a field name in Logmet.
7957

8058
## Pending work
8159
No automated tests currently

index.js

Lines changed: 51 additions & 103 deletions
Original file line numberDiff line numberDiff line change
@@ -1,131 +1,79 @@
11
/**
22
* Licensed Materials - Property of IBM
3-
* (c) Copyright IBM Corporation 2016, 2017. All Rights Reserved.
3+
* (c) Copyright IBM Corporation 2016, 2018. All Rights Reserved.
44
*
55
* Note to U.S. Government Users Restricted Rights:
66
* Use, duplication or disclosure restricted by GSA ADP Schedule
77
* Contract with IBM Corp.
88
*/
9+
910
'use strict';
1011

11-
var log4js = require('log4js'),
12-
util = require('util'),
13-
logmetConnection = require('./lib/logmet-connection-singleton'),
14-
logmet = require('logmet-client');
12+
const LogmetProducer = require('logmet-client').LogmetProducer;
1513

16-
module.exports = {
17-
appender: appender,
18-
configure: configure,
19-
shutdown: shutdown
20-
};
14+
function logmetAppender(logmetClientProducer, spaceId, layout, timezoneOffset) {
2115

22-
function sendData(options, tlsOpts, log) {
23-
var event = buildEvent(log, options);
24-
logmetConnection.producer.sendData(event, log.categoryName, options.space_id, function(/*error, status*/) {
25-
// message is dropped if an error is returned, errors already logged by logmet client
26-
});
27-
}
28-
29-
function buildEvent(log, options) {
30-
// build up message to send
31-
let event = {};
32-
let fields = options.fields;
16+
const appender = (loggingEvent) => {
17+
logmetClientProducer.sendData(layout(loggingEvent, timezoneOffset), spaceId);
18+
};
3319

34-
if (fields.level_field_name) {
35-
event[fields.level_field_name] = log.level && log.level.levelStr;
36-
}
37-
if (fields.timestamp_field_name) {
38-
event[fields.timestamp_field_name] = log.startTime && log.startTime.toISOString();
39-
}
40-
if (fields.data_field_augment && log.data && log.data.length === 1 && typeof log.data[0] === 'object') {
41-
let customDynamicFields = log.data[0];
42-
for (let customDynamicFieldName in customDynamicFields) {
43-
event[customDynamicFieldName] = customDynamicFields[customDynamicFieldName];
20+
appender.shutdown = (callback) => {
21+
if (logmetClientProducer) {
22+
logmetClientProducer.terminate(callback);
4423
}
45-
} else if (fields.data_field_name) {
46-
event[fields.data_field_name] = log.data && log.data.join(' | ');
47-
}
48-
for (let staticFieldName in fields.static_fields) {
49-
event[staticFieldName] = fields.static_fields[staticFieldName];
50-
}
51-
for (let processFieldName in fields.process_fields) {
52-
event[processFieldName] = process[fields.process_fields[processFieldName]];
53-
}
54-
for (let envFieldName in fields.env_fields) {
55-
event[envFieldName] = process.env[fields.env_fields[envFieldName]];
56-
}
57-
58-
return event;
59-
}
60-
61-
function appender(options, tlsOpts) {
62-
logmetConnection.producer = new logmet.LogmetProducer(tlsOpts.host, tlsOpts.port, options.space_id, options.logging_token, false, {bufferSize: logmetConnection.BUFFER_SIZE});
63-
logmetConnection.producer.connect(function(error, status) {
64-
if (error) {
65-
util.log('Logmet Appender: Connection with Logmet failed. ERROR: ' + error);
66-
} else if (status.handshakeCompleted) {
67-
util.log('Logmet Appender: LogmetClient is ready to send data.');
24+
else {
25+
callback();
6826
}
69-
});
70-
return sendData.bind(this, options, tlsOpts);
27+
};
28+
return appender;
7129
}
7230

73-
function configure(config) {
74-
if (process.env.log4js_logmet_enabled !== 'true') return function() {};
31+
function configure(config, layouts) {
32+
if (process.env.log4js_logmet_enabled !== 'true') { return () => {}; }
7533

76-
const defaultFields = {
77-
'level_field_name': 'loglevel',
78-
'timestamp_field_name': 'logtime',
79-
'data_field_name': 'message',
34+
const options = {};
8035

81-
'static_fields': {
82-
'component': process.env.log4js_logmet_component || config.options && config.options.component
83-
},
84-
'env_fields': {
85-
'host-ip': 'CF_INSTANCE_IP',
86-
'instance-id': 'CF_INSTANCE_INDEX'
87-
}
88-
};
36+
options.host = process.env.log4js_logmet_logging_host || config.logging_host;
37+
options.port = process.env.log4js_logmet_logging_port || config.logging_port;
38+
options.spaceId = process.env.log4js_logmet_space_id || config.space_id;
39+
options.loggingToken = process.env.log4js_logmet_logging_token || config.logging_token;
8940

90-
const options = {
91-
logging_token: process.env.log4js_logmet_logging_token || config.options && config.options.logging_token,
92-
space_id: process.env.log4js_logmet_space_id || config.options && config.options.space_id,
93-
fields: (config.options && config.options.fields) || defaultFields
94-
};
41+
for (let option in options) {
42+
if (!options[option]) {
43+
console.log(`Logmet appender: ${ option } not specified`);
44+
return () => {};
45+
}
46+
}
9547

96-
const tlsOpts = {
97-
host: process.env.log4js_logmet_logging_host || config.options && config.options.logging_host,
98-
port: process.env.log4js_logmet_logging_port || config.options && config.options.logging_port,
99-
secureProtocol: logmetConnection.DEFAULT_SECURE_PROTOCOL,
100-
rejectUnauthorized: logmetConnection.DEFAULT_REJECT_UNAUTHORIZED
48+
let layout = (logEvent) => {
49+
return {
50+
'component': process.env.log4js_logmet_component || config.component,
51+
'host-ip': process.env.CF_INSTANCE_IP,
52+
'instance-id': process.env.CF_INSTANCE_INDEX,
53+
'loglevel': logEvent.level.levelStr,
54+
'msg_timestamp': logEvent.startTime.toISOString(),
55+
'message': logEvent.data.join(' | '),
56+
'type': logEvent.categoryName
57+
};
10158
};
102-
103-
var optionsInvalid = false;
104-
105-
for (var i in options) {
106-
if (!options[i]) {
107-
util.log('Logmet Appender: ' + i + ' not specified');
108-
optionsInvalid = true;
109-
}
59+
if (config.layout) {
60+
layout = layouts.layout(config.layout.type, config.layout);
11061
}
11162

112-
if (optionsInvalid) return function() {};
63+
console.log('Logmet appender configured');
11364

114-
if (config.appender) {
115-
log4js.loadAppender(config.appender.type);
116-
config.actualAppender = log4js.appenderMakers[config.appender.type](config.appender);
117-
}
65+
const logmetClientProducer = new LogmetProducer(options.host, options.port, options.spaceId, options.loggingToken, false,
66+
{bufferSize: process.env.log4js_logmet_buffer_size || 10000});
11867

119-
util.log('Logmet Appender configured');
120-
return appender(options, tlsOpts);
68+
logmetClientProducer.connect((error, status) => {
69+
if (error) {
70+
console.error(`Logmet appender: Connection with Logmet failed. Details: ${ error }`);
71+
} else if (status.handshakeCompleted) {
72+
console.log('Logmet appender: LogmetClient is ready to send data.');
73+
}
74+
});
75+
return logmetAppender(logmetClientProducer, options.spaceId, layout, config.timezoneOffset);
12176
}
12277

123-
function shutdown(callback) {
124-
if (logmetConnection.producer) {
125-
logmetConnection.producer.terminate(callback);
126-
}
127-
else {
128-
callback();
129-
}
130-
}
78+
module.exports.configure = configure;
13179

lib/logmet-connection-singleton.js

Lines changed: 0 additions & 36 deletions
This file was deleted.

package.json

Lines changed: 22 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,24 @@
11
{
2-
"name": "log4js-logmet-appender",
3-
"version": "0.3.1",
4-
"description": "Logmet appender for node-log4js",
5-
"author": "Herman Singh Badwal <hermanba@ca.ibm.com>",
6-
"license": "MIT",
7-
"repository": {
8-
"type": "git",
9-
"url": "https://github.com/IBM/node-log4js-logmet-appender.git"
10-
},
11-
"dependencies": {
12-
"log4js": "0.6.35 - 3",
13-
"logmet-client": "^0.1.1"
14-
},
15-
"main": "index.js",
16-
"devDependencies": {
17-
"eslint": "^2.10.2"
18-
},
19-
"scripts": {
20-
"test": "node node_modules/eslint/bin/eslint ."
21-
}
2+
"name": "log4js-logmet-appender",
3+
"version": "1.0.0",
4+
"description": "Logmet appender for node-log4js",
5+
"author": "Herman Singh Badwal <hermanba@ca.ibm.com>",
6+
"license": "MIT",
7+
"repository": {
8+
"type": "git",
9+
"url": "https://github.com/IBM/node-log4js-logmet-appender.git"
10+
},
11+
"dependencies": {
12+
"logmet-client": "^1.0.0"
13+
},
14+
"peerDependencies": {
15+
"log4js": "2.x"
16+
},
17+
"devDependencies": {
18+
"eslint": "^2.10.2"
19+
},
20+
"main": "index.js",
21+
"scripts": {
22+
"test": "node node_modules/eslint/bin/eslint ."
23+
}
2224
}

0 commit comments

Comments
 (0)