Skip to content
This repository was archived by the owner on Jun 21, 2023. It is now read-only.

Commit 4dc3798

Browse files
authored
Merge pull request #23 from AckeeCZ/feat/inherit-name
✨ Update factory interface
2 parents 5a89e88 + 680b784 commit 4dc3798

3 files changed

Lines changed: 84 additions & 41 deletions

File tree

README.md

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,6 @@ Simple pino-based logger for all your writing needs
2121

2222
## How to use
2323

24-
First step is to create a root logger. Its configuration can be specified on creation and it will be used for all other loggers created.
25-
2624
### Import the logger factory
2725

2826
```js
@@ -35,15 +33,15 @@ or with import
3533
import loggerFactory from 'cosmas';
3634
```
3735

38-
### Create root logger with default configuration
36+
### Create logger with default configuration
3937

4038
```js
4139
const logger = loggerFactory; // factory itself is a logger
4240
// or
4341
const logger = loggerFactory();
4442
```
4543

46-
### Create root logger with custom configuration
44+
### Create logger with custom configuration
4745

4846
```js
4947
const logger = loggerFactory({
@@ -52,18 +50,18 @@ const logger = loggerFactory({
5250
});
5351
```
5452

55-
Note: If you want to specify custom configuration it must be done **in the first require** of `cosmas`. Otherwise, default configuration will be used.
56-
5753
See **Options** for a list of possible options.
5854

59-
After you create a root logger, you may use it or you can create a child logger.
55+
### Child loggers
56+
Every logger can be used to create a *child* logger. Child logger inherits all configuration of its *parent* and cannot override them.
57+
58+
Child logger can specify its own *name* which is then concatenated with parent's *name*. Therefore the child logger name is `parentNamechildName`.
6059

6160
```js
62-
const databaseLogger = loggerFactory('database')
61+
const parentLogger = loggerFactory('database', { pretty: false });
62+
const childLogger = parentLogger('.updates');
6363
```
6464

65-
The only difference between root logger and a child logger is that the child logger will print its name in each log message.
66-
6765
## Logger usage
6866

6967
Logger itself is an enhanced and specifically configured `pino` instance, so you may use all basic `pino` log methods

src/index.ts

Lines changed: 19 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,20 @@ export interface AckeeLogger extends PinoLogger {
1818
express: AckeeLoggerExpressMiddleware;
1919
expressError: ErrorRequestHandler;
2020
stream: Writable;
21+
(childName: string): any;
2122
}
2223

2324
export interface AckeeLoggerFactory extends AckeeLogger {
24-
(data: string | AckeeLoggerOptions): AckeeLogger;
25+
(data?: string | AckeeLoggerOptions): AckeeLogger;
2526
}
2627

28+
const makeCallable = <T extends object, F extends (...args: any[]) => any>(obj: T, fun: F): T & F =>
29+
new Proxy(fun as any, {
30+
get: (_target, key) => (obj as any)[key],
31+
});
32+
33+
const objEmpty = (obj: object) => Object.keys(obj).length === 0;
34+
2735
// This is a custom slightly edited version of pino-multistream's write method, which adds support for maximum log level
2836
// The original version was pino-multistream 4.2.0 (commit bf7941f) - https://github.com/pinojs/pino-multi-stream/blob/bf7941f77661b6c14dd40840ff4a4db6897f08eb/multistream.js#L43
2937
const maxLevelWrite: pino.WriteFn = function(this: any, data: object): void {
@@ -95,9 +103,6 @@ const defaultLogger = (options: AckeeLoggerOptions & { loggerName?: string } = {
95103
});
96104
};
97105

98-
let rootLogger: AckeeLogger;
99-
let rootOptions: AckeeLoggerOptions;
100-
101106
const parseLoggerData = (data: string | AckeeLoggerOptions = {}) => {
102107
let loggerName: string | undefined;
103108
let options: AckeeLoggerOptions = {};
@@ -113,21 +118,19 @@ const parseLoggerData = (data: string | AckeeLoggerOptions = {}) => {
113118
return { loggerName, options };
114119
};
115120

116-
const loggerFactory = (data: string | AckeeLoggerOptions = {}): AckeeLogger => {
121+
const loggerFactory = (data: string | AckeeLoggerOptions = {}, loggerOptions: AckeeLoggerOptions = {}): AckeeLogger => {
117122
const { loggerName, options } = parseLoggerData(data);
123+
loggerOptions = objEmpty(options) ? loggerOptions : options;
124+
const logger = defaultLogger(Object.assign({ loggerName }, loggerOptions));
118125

119-
if (!rootLogger) {
120-
rootLogger = defaultLogger(options);
121-
rootOptions = options;
122-
}
123-
if (!loggerName) {
124-
return rootLogger;
125-
}
126-
return defaultLogger(Object.assign({ loggerName }, rootOptions));
126+
const loggerProxy = makeCallable(logger, (childName: string) => {
127+
const childLoggerName = [loggerName, childName].join('');
128+
const childOptions = loggerOptions;
129+
return loggerFactory(childLoggerName, childOptions);
130+
});
131+
return loggerProxy;
127132
};
128133

129-
const factoryProxy = new Proxy(loggerFactory, {
130-
get: (target, key) => (target() as any)[key],
131-
}) as AckeeLoggerFactory;
134+
const factoryProxy = makeCallable(loggerFactory(), loggerFactory);
132135

133136
export default factoryProxy;

src/tests/index.test.ts

Lines changed: 57 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,9 @@
11
import 'jest-extended';
2+
import isString = require('lodash.isstring');
23
import { Writable } from 'stream';
4+
import loggerFactory from '..';
35
import { levels } from '../levels';
46

5-
let loggerFactory;
6-
7-
beforeEach(() => {
8-
jest.resetModules();
9-
loggerFactory = require('..').default;
10-
});
11-
127
test('can create default logger', () => {
138
const logger = loggerFactory();
149
expect(logger).toBeDefined();
@@ -17,6 +12,13 @@ test('can create default logger', () => {
1712
test('can create named logger', () => {
1813
const logger = loggerFactory('myApp');
1914
expect(logger).toBeDefined();
15+
expect((logger.options as any).loggerName).toBe('myApp');
16+
});
17+
18+
test.skip('can create logger with options', () => { // TODO: will work after pretty mechanics update - broken bc of deps update
19+
const logger = loggerFactory({ pretty: true });
20+
expect(logger).toBeDefined();
21+
expect(logger.options.pretty).toBe(true);
2022
});
2123

2224
const testWriteStream = (resolve, assert) => ({
@@ -55,15 +57,15 @@ test('can use warning level', () =>
5557

5658
test('child logger has warning level', () =>
5759
new Promise((resolve, reject) => {
58-
loggerFactory({
60+
const rootLogger = loggerFactory({
5961
streams: [
6062
testWriteStream(resolve, json => {
6163
expect(json.message).toContain('Hello');
6264
expect(json.level).toBe(levels.warn);
6365
}),
6466
],
6567
});
66-
const childLogger = loggerFactory('child');
68+
const childLogger = rootLogger('child');
6769

6870
childLogger.warning('Hello');
6971
}));
@@ -126,34 +128,74 @@ exampleMessages.forEach(data => {
126128
test(`logger name is shown in non-pretty ${data.type} message`, () =>
127129
new Promise(resolve => {
128130
const loggerName = 'database';
129-
loggerFactory({
131+
const rootLogger = loggerFactory({
130132
pretty: false,
131133
streams: [
132134
testWriteStream(resolve, json => {
133135
expect(json.message).toStartWith(`[${loggerName}] `);
134136
}),
135137
],
136138
});
137-
const logger = loggerFactory(loggerName);
139+
const logger = rootLogger(loggerName);
138140

139-
logger.fatal(data.logData);
141+
if (isString(data.logData)) {
142+
logger.fatal(data.logData);
143+
} else {
144+
logger.fatal(data.logData, 'Data');
145+
}
140146
}));
141147
});
142148

143149
exampleMessages.forEach(data => {
144150
test(`logger name is propagated to pretty object with ${data.type} message`, () =>
145151
new Promise(resolve => {
146152
const loggerName = 'database';
147-
loggerFactory({
153+
const rootLogger = loggerFactory({
148154
pretty: true,
149155
streams: [
150156
testWriteStream(resolve, json => {
151157
expect(json.name).toEqual(loggerName);
152158
}),
153159
],
154160
});
155-
const logger = loggerFactory(loggerName);
161+
const logger = rootLogger(loggerName);
156162

157-
logger.fatal(data.logData);
163+
if (isString(data.logData)) {
164+
logger.fatal(data.logData);
165+
} else {
166+
logger.fatal(data.logData, 'Data');
167+
}
158168
}));
159169
});
170+
171+
test('multiple logger configs are not affected', () => {
172+
const primaryLogger = loggerFactory({ disableStackdriverFormat: true, ignoredHttpMethods: ['POST'] });
173+
const secondaryLogger = loggerFactory({ disableStackdriverFormat: false, ignoredHttpMethods: ['GET'] });
174+
175+
expect(primaryLogger.options.disableStackdriverFormat).toBe(true);
176+
expect(primaryLogger.options.ignoredHttpMethods).toIncludeSameMembers(['POST']);
177+
expect(secondaryLogger.options.disableStackdriverFormat).toBe(false);
178+
expect(secondaryLogger.options.ignoredHttpMethods).toIncludeSameMembers(['GET']);
179+
});
180+
181+
test('Child logger takes parent config', () => {
182+
const logger = loggerFactory({ disableStackdriverFormat: true });
183+
const childLogger = logger('child');
184+
185+
expect(childLogger.options.disableStackdriverFormat).toBe(true);
186+
});
187+
188+
test('Child logger inherits parent name', () => {
189+
const logger = loggerFactory('parent', { disableStackdriverFormat: true });
190+
const childLogger = logger('child');
191+
192+
expect(childLogger.options.loggerName).toBe('parentchild');
193+
});
194+
195+
test('Child logger can create another child', () => {
196+
const logger = loggerFactory('parent', { disableStackdriverFormat: true });
197+
const childLogger = logger('child');
198+
const kid = childLogger('grandkid');
199+
200+
expect(kid.options.loggerName).toBe('parentchildgrandkid');
201+
});

0 commit comments

Comments
 (0)