8000 Merge pull request #31 from ZachHaber/layout · log4js-node/logFaces-HTTP@07dbd73 · GitHub
[go: up one dir, main page]

Skip to content

Commit 07dbd73

Browse files
authored
Merge pull request #31 from ZachHaber/layout
Add `eventLayout` config option
2 parents 60e07f5 + 90f6f3d commit 07dbd73

File tree

5 files changed

+157
-6
lines changed

5 files changed

+157
-6
lines changed

.eslintrc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
"import/no-extraneous-dependencies": 2
1111
},
1212
"parserOptions": {
13-
"ecmaVersion": 6
13+
"ecmaVersion": 2018
1414
},
1515
"ignorePatterns": ["coverage/**/*", "commitlint.config.js"]
1616
}

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ npm install log4js @log4js-node/logfaces-http
1515
- `configContext` - function (optional) returning a global context object accessible to all appenders. Properties from configContext added as `p_` values in the logFaces event.
1616
- `hostname` - `string` (optional) - used to add the hostname `h` property to the logFaces event.
1717
- `agent` - `http.Agent | https.Agent` (optional) - used to configure the requests being sent out if needed.
18+
- `eventLayout` - `(LoggingEvent, LogFacesEvent) => LogFacesEvent` (optional) - allows more control and ability to modify and set the properties of the LogFacesEvent that will get sent to the server. Note: returning `null` or `undefined` will cause the event to be ignored and not logged. If `LogFacesEvent.m` is nullish, the default format for the message will be applied.
1819

1920
This appender will also pick up Logger context values from the events, and add them as `p_` values in the logFaces event. See the example below for more details. Note that Logger context may override the same properties defined in `configContext`.
2021

lib/index.js

Lines changed: 39 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
/**
22
* logFaces appender sends JSON formatted log events to logFaces receivers over HTTP.
33
*/
4-
/* eslint global-require:0 */
54

65
'use strict';
76

@@ -24,6 +23,8 @@ function getErrorStack(logData) {
2423
return null;
2524
}
2625

26+
const NUM_REQUIRED_PROPERTIES = 5;
27+
2728
/**
2829
*
2930
* For HTTP (browsers or node.js) use the following configuration params:
@@ -35,6 +36,10 @@ function getErrorStack(logData) {
3536
* @param {import('../types').LogFacesHTTPAppender} config
3637
*/
3738
function logFacesAppender(config) {
39+
if (config.eventLayout && typeof config.eventLayout !== 'function') {
40+
throw new TypeError('eventLayout must be a function');
41+
}
42+
3843
const sender = axios.create({
3944
baseURL: config.url,
4045
timeout: config.timeout || 5000,
@@ -48,14 +53,17 @@ function logFacesAppender(config) {
4853

4954
const { configContext } = config;
5055

56+
/**
57+
* @param {import('log4js').LoggingEvent} event
58+
*/
5159
return function log(event) {
5260
// convert to logFaces compact json format
53-
const lfsEvent = {
61+
/** @type {import('../types').LogFacesEvent} */
62+
let lfsEvent = {
5463
a: config.application || '', // application name
5564
t: event.startTime.getTime(), // time stamp
5665
p: event.level.levelStr, // level (priority)
5766
g: event.categoryName, // logger name
58-
m: format(event.data), // message text
5967
h: config.hostname, // hostname
6068
};
6169

@@ -87,7 +95,29 @@ function logFacesAppender(config) {
8795
Object.keys(event.context).forEach((key) => {
8896
lfsEvent[`p_${key}`] = event.context[key];
8997
});
90-
98+
if (config.eventLayout) {
99+
lfsEvent = config.eventLayout(event, { ...lfsEvent });
100+
if (!lfsEvent) {
101+
// no event object returned, consider the event ignored
102+
return;
103+
}
104+
if (
105+
typeof lfsEvent !== 'object' ||
106+
Array.isArray(lfsEvent) ||
107+
// require at least the number of required properties ignoring `m`
108+
Object.keys(lfsEvent).length < NUM_REQUIRED_PROPERTIES - 1
109+
) {
110+
// eslint-disable-next-line no-console
111+
console.error(
112+
`log4js.logFaces-HTTP Appender eventLayout() must return an object`
113+
);
114+
return;
115+
}
116+
}
117+
if (lfsEvent.m == null) {
118+
// Add the default message on if not set
119+
lfsEvent.m = format(event.data);
120+
}
91121
// send to server
92122
sender.post('', lfsEvent).catch((error) => {
93123
if (error.response) {
@@ -103,6 +133,11 @@ function logFacesAppender(config) {
103133
};
104134
}
105135

136+
/**
137+
*
138+
* @param {import('../types').LogFacesHTTPAppender} config
139+
* @returns
140+
*/
106141
function configure(config) {
107142
return logFacesAppender(config);
108143
}

test/tap/index-test.js

Lines changed: 66 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,9 @@ function setupLogging(category, enableCallStack, options) {
2121
};
2222
},
2323
};
24-
24+
/**
25+
* @type {{msg?: string, error(msg:string):void}}
26+
*/
2527
const fakeConsole = {
2628 error(msg) {
2729
this.msg = msg;
@@ -234,6 +236,69 @@ test('logFaces appender', (batch) => {
234236

235237
t.end();
236238
});
239+
batch.test('eventLayout should error if set incorrectly', (t) => {
240+
t.throws(() => {
241+
setupLogging('myCategory', false, {
242+
application: 'LFS-HTTP',
243+
url: 'http://localhost/receivers/rx1',
244+
eventLayout: 'banana',
245+
});
246+
});
247+
const setup = setupLogging('myCategory', false, {
248+
application: 'LFS-HTTP',
249+
url: 'http://localhost/receivers/rx1',
250+
eventLayout: (event) => {
251+
if (event.data[0] === 'array') {
252+
return [];
253+
}
254+
if (event.data[0] === 'string') {
255+
return 'string';
256+
}
257+
if (event.data[0] === 'object') {
258+
return {};
259+
}
260+
return undefined;
261+
},
262+
});
263+
const message =
264+
'log4js.logFaces-HTTP Appender eventLayout() must return an object';
265+
['array', 'string', 'object'].forEach((input) => {
266+
setup.logger.info(input);
267+
t.equal(setup.fakeConsole.msg, message);
268+
setup.fakeConsole.msg = undefined;
269+
});
270+
271+
t.end();
272+
});
273+
batch.test('eventLayout should enable changing the results', (t) => {
274+
const setup = setupLogging('myCategory', false, {
275+
application: 'LFS-HTTP',
276+
url: 'http://localhost/receivers/rx1',
277+
eventLayout: (event, lfEvent) => {
278+
if (event.data[0] === 'SUPER SECRET!') {
279+
return undefined;
280+
}
281+
lfEvent.m = `test: ${event.data[0]}`;
282+
lfEvent.r = 'steve';
283+
return lfEvent;
284+
},
285+
});
286+
287+
setup.logger.info('Log event #1');
288+
const event = setup.fakeAxios.args[1];
289+
t.match(event, {
290+
a: 'LFS-HTTP',
291+
m: 'test: Log event #1',
292+
g: 'myCategory',
293+
p: 'INFO',
294+
r: 'steve',
295+
});
296+
297+
setup.fakeAxios.args = undefined;
298+
setup.logger.info('SUPER SECRET!');
299+
t.equal(setup.fakeAxios.args, undefined);
300+
t.end();
301+
});
237302

238303
batch.end();
239304
});

types/index.d.ts

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
import type { Agent as httpAgent } from 'http';
22
import type { Agent as httpsAgent } from 'https';
3+
import type { LoggingEvent } from 'log4js';
4+
5+
/** https://stackoverflow.com/a/53742583/13175138 */
6+
type PickPartial<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;
37

48
export interface LogFacesHTTPAppender {
59
type: '@log4js-node/logfaces-http';
@@ -21,6 +25,52 @@ export interface LogFacesHTTPAppender {
2125
* Make sure you use the correct type base on your url
2226
*/
2327
agent?: httpAgent | httpsAgent;
28+
/** Adjust the resulting logfacesEvent that is sent out.
29+
*
30+
* Needs to return the new layout or undefined to ignore the event.
31+
*
32+
* If `LogFacesEvent.m` is nullish: it will be populated with the default formatter
33+
* */
34+
eventLayout?: LogFacesLayoutFunction;
35+
}
36+
37+
export type LogFacesLayoutFunction = (
38+
loggingEvent: LoggingEvent,
39+
logFacesEvent: Omit<LogFacesEvent, 'm'>
40+
) => PickPartial<LogFacesEvent, 'm'> | undefined | null;
41+
42+
/** [Data model: Log events](http://www.moonlit-software.com/logfaces/downloads/logfaces-manual.pdf) */
43+
export interface LogFacesEvent {
44+
/** Time stamp as specified by the source or server */
45+
t: number;
46+
/** Severity of event expressed in term of log4j levels */
47+
p: number | string;
48+
/** Name of the domain (or application) originating the event */
49+
a: string;
50+
/** Name of the host originating the event */
51+
h?: string;
52+
/** Name of the logger (class, module, etc) originating the event */
53+
g: string;
54+
/** Name of the thread originating the event */
55+
r?: string;
56+
/** Message Content */
57+
m: string;
58+
/** [Network Diagnostic Context](http://logging.apache.org/log4j/1.2/apidocs/org/apache/log4j/NDC.html) */
59+
n?: string;
60+
/** Indication whether the event is a thrown exception */
61+
w?: boolean;
62+
/** Stack trace of thrown exceptions */
63+
i?: string;
64+
/** File name (of the source code location originating the event) */
65+
f?: string;
66+
/** Class name (of the source code location originating the event) */
67+
c?: string;
68+
/** Method name (of the source code location originating the event) */
69+
e?: string;
70+
/** Line number (of the source code location originating the event) */
71+
l?: string | number;
72+
/** MDC (Mapped Diagnostic Context) properties, p_XXX, where XXX is a property name */
73+
[key: `p_${string}`]: string | number;
2474
}
2575

2676
// Add the LogFacesHTTPAppender to the list of appenders in log4js for better type support

0 commit comments

Comments
 (0)
0