@api3/commons 0.8.0 → 0.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -16,114 +16,125 @@ const createTestLogger = (
16
16
  return { baseLogger, logger };
17
17
  };
18
18
 
19
- describe('log context', () => {
20
- it('works with sync functions', () => {
21
- const { baseLogger, logger } = createTestLogger();
22
-
23
- logger.runWithContext({ requestId: 'parent' }, () => {
24
- logger.debug('parent start');
25
- logger.runWithContext({ requestId: 'child' }, () => {
26
- logger.debug('child');
27
- });
19
+ test('works with sync functions', () => {
20
+ const { baseLogger, logger } = createTestLogger();
28
21
 
29
- logger.debug('parent end');
22
+ logger.runWithContext({ requestId: 'parent' }, () => {
23
+ logger.debug('parent start');
24
+ logger.runWithContext({ requestId: 'child' }, () => {
25
+ logger.debug('child');
30
26
  });
31
27
 
32
- expect(baseLogger.debug).toHaveBeenCalledWith('parent start', { ctx: { requestId: 'parent' } });
33
- expect(baseLogger.debug).toHaveBeenCalledWith('child', { ctx: { requestId: 'child' } });
34
- expect(baseLogger.debug).toHaveBeenCalledWith('parent end', { ctx: { requestId: 'parent' } });
28
+ logger.debug('parent end');
35
29
  });
36
30
 
37
- it('works with async functions', async () => {
38
- const { baseLogger, logger } = createTestLogger();
31
+ expect(baseLogger.debug).toHaveBeenCalledWith('parent start', { ctx: { requestId: 'parent' } });
32
+ expect(baseLogger.debug).toHaveBeenCalledWith('child', { ctx: { requestId: 'child' } });
33
+ expect(baseLogger.debug).toHaveBeenCalledWith('parent end', { ctx: { requestId: 'parent' } });
34
+ });
39
35
 
40
- await logger.runWithContext({ requestId: 'parent' }, async () => {
41
- logger.debug('parent start');
42
- await logger.runWithContext({ requestId: 'child' }, async () => {
43
- await new Promise((resolve) => setTimeout(resolve, 50));
44
- logger.debug('child');
45
- });
36
+ test('works with async functions', async () => {
37
+ const { baseLogger, logger } = createTestLogger();
46
38
 
47
- logger.debug('parent end');
39
+ await logger.runWithContext({ requestId: 'parent' }, async () => {
40
+ logger.debug('parent start');
41
+ await logger.runWithContext({ requestId: 'child' }, async () => {
42
+ await new Promise((resolve) => setTimeout(resolve, 50));
43
+ logger.debug('child');
48
44
  });
49
45
 
50
- expect(baseLogger.debug).toHaveBeenCalledTimes(3);
51
- expect(baseLogger.debug).toHaveBeenCalledWith('parent start', { ctx: { requestId: 'parent' } });
52
- expect(baseLogger.debug).toHaveBeenCalledWith('child', { ctx: { requestId: 'child' } });
53
- expect(baseLogger.debug).toHaveBeenCalledWith('parent end', { ctx: { requestId: 'parent' } });
46
+ logger.debug('parent end');
54
47
  });
55
48
 
56
- it('works with deeply nested functions', async () => {
57
- const { baseLogger, logger } = createTestLogger();
49
+ expect(baseLogger.debug).toHaveBeenCalledTimes(3);
50
+ expect(baseLogger.debug).toHaveBeenCalledWith('parent start', { ctx: { requestId: 'parent' } });
51
+ expect(baseLogger.debug).toHaveBeenCalledWith('child', { ctx: { requestId: 'child' } });
52
+ expect(baseLogger.debug).toHaveBeenCalledWith('parent end', { ctx: { requestId: 'parent' } });
53
+ });
58
54
 
59
- await logger.runWithContext({ parent: true }, async () => {
60
- logger.debug('parent start');
55
+ test('works with deeply nested functions', async () => {
56
+ const { baseLogger, logger } = createTestLogger();
61
57
 
62
- await logger.runWithContext({ A: true }, async () => {
63
- logger.debug('A start');
58
+ await logger.runWithContext({ parent: true }, async () => {
59
+ logger.debug('parent start');
64
60
 
65
- await logger.runWithContext({ B: true }, async () => {
66
- setTimeout(() => logger.debug('C'), 25);
67
- setTimeout(() => logger.debug('D'), 50);
68
- setTimeout(() => logger.debug('E'), 75);
61
+ await logger.runWithContext({ A: true }, async () => {
62
+ logger.debug('A start');
69
63
 
70
- await new Promise((resolve) => setTimeout(resolve, 100));
71
- logger.debug('B end');
72
- });
64
+ await logger.runWithContext({ B: true }, async () => {
65
+ setTimeout(() => logger.debug('C'), 25);
66
+ setTimeout(() => logger.debug('D'), 50);
67
+ setTimeout(() => logger.debug('E'), 75);
73
68
 
74
- logger.debug('A end');
69
+ await new Promise((resolve) => setTimeout(resolve, 100));
70
+ logger.debug('B end');
75
71
  });
76
72
 
77
- logger.debug('parent end');
73
+ logger.debug('A end');
78
74
  });
79
75
 
80
- expect(baseLogger.debug).toHaveBeenCalledTimes(8);
81
- expect(baseLogger.debug).toHaveBeenCalledWith('parent start', { ctx: { parent: true } });
82
- expect(baseLogger.debug).toHaveBeenCalledWith('A start', { ctx: { parent: true, A: true } });
83
- expect(baseLogger.debug).toHaveBeenCalledWith('C', { ctx: { parent: true, A: true, B: true } });
84
- expect(baseLogger.debug).toHaveBeenCalledWith('D', { ctx: { parent: true, A: true, B: true } });
85
- expect(baseLogger.debug).toHaveBeenCalledWith('E', { ctx: { parent: true, A: true, B: true } });
86
- expect(baseLogger.debug).toHaveBeenCalledWith('B end', { ctx: { parent: true, A: true, B: true } });
87
- expect(baseLogger.debug).toHaveBeenCalledWith('A end', { ctx: { parent: true, A: true } });
88
- expect(baseLogger.debug).toHaveBeenCalledWith('parent end', { ctx: { parent: true } });
76
+ logger.debug('parent end');
89
77
  });
90
78
 
91
- it('throws if the sync callback function throws', () => {
92
- const { logger } = createTestLogger();
79
+ expect(baseLogger.debug).toHaveBeenCalledTimes(8);
80
+ expect(baseLogger.debug).toHaveBeenCalledWith('parent start', { ctx: { parent: true } });
81
+ expect(baseLogger.debug).toHaveBeenCalledWith('A start', { ctx: { parent: true, A: true } });
82
+ expect(baseLogger.debug).toHaveBeenCalledWith('C', { ctx: { parent: true, A: true, B: true } });
83
+ expect(baseLogger.debug).toHaveBeenCalledWith('D', { ctx: { parent: true, A: true, B: true } });
84
+ expect(baseLogger.debug).toHaveBeenCalledWith('E', { ctx: { parent: true, A: true, B: true } });
85
+ expect(baseLogger.debug).toHaveBeenCalledWith('B end', { ctx: { parent: true, A: true, B: true } });
86
+ expect(baseLogger.debug).toHaveBeenCalledWith('A end', { ctx: { parent: true, A: true } });
87
+ expect(baseLogger.debug).toHaveBeenCalledWith('parent end', { ctx: { parent: true } });
88
+ });
93
89
 
94
- expect(() =>
95
- logger.runWithContext({}, () => {
96
- throw new Error('some-error');
97
- })
98
- ).toThrow('some-error');
99
- });
90
+ test('throws if the sync callback function throws', () => {
91
+ const { logger } = createTestLogger();
100
92
 
101
- it('returns rejected promise if the async callback function rejects', async () => {
102
- const { logger } = createTestLogger();
93
+ expect(() =>
94
+ logger.runWithContext({}, () => {
95
+ throw new Error('some-error');
96
+ })
97
+ ).toThrow('some-error');
98
+ });
103
99
 
104
- await expect(async () =>
105
- // eslint-disable-next-line @typescript-eslint/require-await
106
- logger.runWithContext({}, async () => {
107
- throw new Error('some-error');
108
- })
109
- ).rejects.toThrow('some-error');
110
- });
100
+ test('returns rejected promise if the async callback function rejects', async () => {
101
+ const { logger } = createTestLogger();
111
102
 
112
- it('can log using all variants of logger.error', () => {
113
- const { baseLogger, logger } = createTestLogger();
114
-
115
- logger.error('only message');
116
- logger.error('message and context', { requestId: 'parent' });
117
- logger.error('message and error', new Error('some-error'));
118
- logger.error('message, error and context', new Error('some-error'), { requestId: 'parent' });
119
-
120
- expect(baseLogger.error).toHaveBeenNthCalledWith(1, 'only message', undefined);
121
- expect(baseLogger.error).toHaveBeenNthCalledWith(2, 'message and context', { ctx: { requestId: 'parent' } });
122
- expect(baseLogger.error).toHaveBeenNthCalledWith(3, 'message and error', new Error('some-error'), undefined);
123
- expect(baseLogger.error).toHaveBeenNthCalledWith(4, 'message, error and context', new Error('some-error'), {
124
- ctx: {
125
- requestId: 'parent',
126
- },
127
- });
103
+ await expect(async () =>
104
+ // eslint-disable-next-line @typescript-eslint/require-await
105
+ logger.runWithContext({}, async () => {
106
+ throw new Error('some-error');
107
+ })
108
+ ).rejects.toThrow('some-error');
109
+ });
110
+
111
+ test('can log using all variants of logger.error', () => {
112
+ const { baseLogger, logger } = createTestLogger();
113
+
114
+ logger.error('only message');
115
+ logger.error('message and context', { requestId: 'parent' });
116
+ logger.error('message and error', new Error('some-error'));
117
+ logger.error('message, error and context', new Error('some-error'), { requestId: 'parent' });
118
+
119
+ expect(baseLogger.error).toHaveBeenNthCalledWith(1, 'only message', undefined);
120
+ expect(baseLogger.error).toHaveBeenNthCalledWith(2, 'message and context', { ctx: { requestId: 'parent' } });
121
+ expect(baseLogger.error).toHaveBeenNthCalledWith(3, 'message and error', new Error('some-error'), undefined);
122
+ expect(baseLogger.error).toHaveBeenNthCalledWith(4, 'message, error and context', new Error('some-error'), {
123
+ ctx: {
124
+ requestId: 'parent',
125
+ },
128
126
  });
129
127
  });
128
+
129
+ test('logs an error when passed as context to non error level', () => {
130
+ const { baseLogger, logger } = createTestLogger();
131
+ const e = new Error('some-error');
132
+
133
+ logger.debug('debug message', e);
134
+ logger.info('info message', e);
135
+ logger.warn('warn message', e);
136
+
137
+ expect(baseLogger.debug).toHaveBeenCalledWith('debug message', { ctx: { error: 'some-error', name: 'Error' } });
138
+ expect(baseLogger.info).toHaveBeenCalledWith('info message', { ctx: { error: 'some-error', name: 'Error' } });
139
+ expect(baseLogger.warn).toHaveBeenCalledWith('warn message', { ctx: { error: 'some-error', name: 'Error' } });
140
+ });
@@ -1,3 +1,4 @@
1
+ import isError from 'lodash/isError';
1
2
  import winston from 'winston';
2
3
  import { consoleFormat } from 'winston-console-format';
3
4
 
@@ -87,10 +88,17 @@ export interface Logger {
87
88
  child: (options: { name: string }) => Logger;
88
89
  }
89
90
 
91
+ const parseLocalContext = (localContext: LogContext | undefined) => {
92
+ // Sometimes an error passed as a context, but when JS error has no own enumerable properties, so when it is spread
93
+ // (using ...) we get an empty object and lose all the context.
94
+ if (isError(localContext)) return { error: localContext.message, name: localContext.name };
95
+ return localContext;
96
+ };
97
+
90
98
  const createFullContext = (localContext: LogContext | undefined) => {
91
99
  const globalContext = getAsyncLocalStorage().getStore();
92
100
  if (!globalContext && !localContext) return;
93
- const fullContext = { ...globalContext, ...localContext };
101
+ const fullContext = { ...globalContext, ...parseLocalContext(localContext) };
94
102
 
95
103
  // If the context contains a `name` or `message` field, it will override the `name` and `message` fields of the log
96
104
  // entry. To avoid this, we return the context as a separate field.