@squiz/component-logger-lib 1.32.1-alpha.22 → 1.32.1-alpha.26
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.
- package/.npm/_logs/{2023-04-28T06_34_24_877Z-debug-0.log → 2023-05-03T01_21_21_355Z-debug-0.log} +18 -18
- package/jest.integration.config.ts +15 -0
- package/lib/component-log-message.d.ts +1 -1
- package/lib/component-logger-config.d.ts +3 -3
- package/lib/component-logger.d.ts +1 -1
- package/lib/component-logger.integration.spec.d.ts +1 -0
- package/lib/component-logger.integration.spec.js +17 -0
- package/lib/component-logger.integration.spec.js.map +1 -0
- package/lib/component-logger.js +6 -5
- package/lib/component-logger.js.map +1 -1
- package/lib/component-logger.spec.js +211 -30
- package/lib/component-logger.spec.js.map +1 -1
- package/lib/getComponentLogger.d.ts +8 -0
- package/lib/getComponentLogger.js +43 -0
- package/lib/getComponentLogger.js.map +1 -0
- package/lib/getComponentLogger.spec.d.ts +1 -0
- package/lib/getComponentLogger.spec.js +28 -0
- package/lib/getComponentLogger.spec.js.map +1 -0
- package/lib/index.d.ts +1 -0
- package/lib/index.js +3 -1
- package/lib/index.js.map +1 -1
- package/package.json +5 -3
- package/src/component-log-message.ts +1 -1
- package/src/component-logger-config.ts +3 -3
- package/src/component-logger.integration.spec.ts +16 -0
- package/src/component-logger.spec.ts +246 -8
- package/src/component-logger.ts +8 -6
- package/src/getComponentLogger.spec.ts +31 -0
- package/src/getComponentLogger.ts +55 -0
- package/src/index.ts +1 -0
- package/tsconfig.tsbuildinfo +1 -1
- package/.env.example +0 -4
@@ -1,20 +1,258 @@
|
|
1
1
|
import { ComponentLogger } from './component-logger';
|
2
2
|
import { ComponentLoggerConfig } from './component-logger-config';
|
3
|
-
import
|
3
|
+
import superagent from 'superagent';
|
4
|
+
|
5
|
+
const postSetMock = {
|
6
|
+
send: jest.fn(),
|
7
|
+
};
|
8
|
+
const postMock = {
|
9
|
+
set: () => {
|
10
|
+
return postSetMock;
|
11
|
+
},
|
12
|
+
};
|
13
|
+
jest.mock('superagent', () => ({
|
14
|
+
post: () => {
|
15
|
+
return postMock;
|
16
|
+
},
|
17
|
+
}));
|
18
|
+
|
19
|
+
const loggerConfig = <ComponentLoggerConfig>{
|
20
|
+
enabled: true,
|
21
|
+
logUrl: 'some-log-url',
|
22
|
+
key: 'some-key',
|
23
|
+
tenantId: 'some-tenant-id',
|
24
|
+
tags: 'some-tags',
|
25
|
+
};
|
4
26
|
|
5
27
|
describe('ComponentLogger', () => {
|
6
|
-
|
28
|
+
afterEach(() => {
|
29
|
+
jest.clearAllMocks();
|
30
|
+
});
|
31
|
+
|
32
|
+
beforeAll(() => {
|
33
|
+
jest.useFakeTimers();
|
34
|
+
jest.setSystemTime(new Date('2023-05-28T00:00:00.000Z'));
|
35
|
+
});
|
36
|
+
|
37
|
+
afterAll(() => {
|
38
|
+
jest.useRealTimers();
|
39
|
+
});
|
40
|
+
|
41
|
+
it('should throw error if config.logUrl is not set', async () => {
|
42
|
+
const loggerConfig = <ComponentLoggerConfig>{
|
43
|
+
enabled: true,
|
44
|
+
logUrl: '',
|
45
|
+
key: 'some-key',
|
46
|
+
tenantId: 'some-tenant-id',
|
47
|
+
tags: 'some-tags',
|
48
|
+
};
|
49
|
+
expect(() => new ComponentLogger(loggerConfig)).toThrow('config.logUrl is required');
|
50
|
+
});
|
51
|
+
|
52
|
+
it('should throw error if config.key is not set', async () => {
|
7
53
|
const config = <ComponentLoggerConfig>{
|
8
|
-
enabled:
|
9
|
-
logUrl:
|
10
|
-
key:
|
11
|
-
tenantId:
|
12
|
-
tags:
|
54
|
+
enabled: true,
|
55
|
+
logUrl: 'some-log-url',
|
56
|
+
key: '',
|
57
|
+
tenantId: 'some-tenant-id',
|
58
|
+
tags: 'some-tags',
|
13
59
|
};
|
60
|
+
expect(() => new ComponentLogger(config)).toThrow('config.key is required');
|
61
|
+
});
|
62
|
+
|
63
|
+
it('should throw error if config.tenantId is not set', async () => {
|
64
|
+
const loggerConfig = <ComponentLoggerConfig>{
|
65
|
+
enabled: true,
|
66
|
+
logUrl: 'some-log-url',
|
67
|
+
key: 'some-key',
|
68
|
+
tenantId: '',
|
69
|
+
tags: 'some-tags',
|
70
|
+
};
|
71
|
+
expect(() => new ComponentLogger(loggerConfig)).toThrow('config.tenantId is required');
|
72
|
+
});
|
14
73
|
|
15
|
-
|
74
|
+
it('should set correct log service URL and API key', async () => {
|
75
|
+
const postSpy = jest.spyOn(superagent, 'post');
|
76
|
+
const postSetSpy = jest.spyOn(postMock, 'set');
|
16
77
|
|
78
|
+
const logger = new ComponentLogger(loggerConfig);
|
17
79
|
await logger.info('The quick brown fox', '123');
|
18
80
|
await logger.flush();
|
81
|
+
|
82
|
+
expect(postSpy).toBeCalledWith('some-log-url/some-tenant-id');
|
83
|
+
expect(postSetSpy).toBeCalledWith('x-api-key', 'some-key');
|
84
|
+
});
|
85
|
+
|
86
|
+
it('should strip off extra "/" on the logUrl', async () => {
|
87
|
+
const loggerConfig = <ComponentLoggerConfig>{
|
88
|
+
enabled: true,
|
89
|
+
logUrl: 'some-log-url/',
|
90
|
+
key: 'some-key',
|
91
|
+
tenantId: 'some-tenant-id',
|
92
|
+
tags: 'some-tags',
|
93
|
+
};
|
94
|
+
|
95
|
+
const postSpy = jest.spyOn(superagent, 'post');
|
96
|
+
|
97
|
+
const logger = new ComponentLogger(loggerConfig);
|
98
|
+
await logger.info('The quick brown fox', '123');
|
99
|
+
await logger.flush();
|
100
|
+
|
101
|
+
expect(postSpy).toBeCalledWith('some-log-url/some-tenant-id');
|
102
|
+
});
|
103
|
+
|
104
|
+
it('should not send message if disabled', async () => {
|
105
|
+
const loggerConfig = <ComponentLoggerConfig>{
|
106
|
+
enabled: false,
|
107
|
+
logUrl: 'some-log-url',
|
108
|
+
key: 'some-key',
|
109
|
+
tenantId: 'some-tenant-id',
|
110
|
+
tags: 'some-tags',
|
111
|
+
};
|
112
|
+
const logger = new ComponentLogger(loggerConfig);
|
113
|
+
|
114
|
+
await logger.warn('this message wont be sent', '123');
|
115
|
+
await logger.flush();
|
116
|
+
expect(postSetMock.send).toBeCalledTimes(0);
|
117
|
+
});
|
118
|
+
|
119
|
+
it('should send the log messages in batch', async () => {
|
120
|
+
const logger = new ComponentLogger(loggerConfig);
|
121
|
+
|
122
|
+
await logger.info('The quick brown fox 1', '123');
|
123
|
+
await logger.info('The quick brown fox 2', '124');
|
124
|
+
await logger.flush();
|
125
|
+
|
126
|
+
const expectedMessages = [
|
127
|
+
{
|
128
|
+
level: 'INFO',
|
129
|
+
service: 'render-runtime',
|
130
|
+
timestamp: '2023-05-28T00:00:00.000Z',
|
131
|
+
message: 'The quick brown fox 1',
|
132
|
+
tags: 'some-tags',
|
133
|
+
traceid: '123',
|
134
|
+
},
|
135
|
+
{
|
136
|
+
level: 'INFO',
|
137
|
+
service: 'render-runtime',
|
138
|
+
timestamp: '2023-05-28T00:00:00.000Z',
|
139
|
+
message: 'The quick brown fox 2',
|
140
|
+
tags: 'some-tags',
|
141
|
+
traceid: '124',
|
142
|
+
},
|
143
|
+
];
|
144
|
+
expect(postSetMock.send).toBeCalledWith(expectedMessages);
|
145
|
+
});
|
146
|
+
|
147
|
+
it('should json encode the message if not string', async () => {
|
148
|
+
const logger = new ComponentLogger(loggerConfig);
|
149
|
+
|
150
|
+
await logger.warn(['this', 'is', 'not string'], '123');
|
151
|
+
await logger.flush();
|
152
|
+
|
153
|
+
const expectedMessages = [
|
154
|
+
{
|
155
|
+
level: 'WARN',
|
156
|
+
service: 'render-runtime',
|
157
|
+
timestamp: '2023-05-28T00:00:00.000Z',
|
158
|
+
message: '["this","is","not string"]',
|
159
|
+
tags: 'some-tags',
|
160
|
+
traceid: '123',
|
161
|
+
},
|
162
|
+
];
|
163
|
+
expect(postSetMock.send).toBeCalledWith(expectedMessages);
|
164
|
+
});
|
165
|
+
|
166
|
+
it('should not send message if there is nothing the message queue', async () => {
|
167
|
+
const logger = new ComponentLogger(loggerConfig);
|
168
|
+
|
169
|
+
await logger.flush();
|
170
|
+
expect(postSetMock.send).toBeCalledTimes(0);
|
171
|
+
});
|
172
|
+
|
173
|
+
it('should clear the message queue after sending the message', async () => {
|
174
|
+
const logger = new ComponentLogger(loggerConfig);
|
175
|
+
|
176
|
+
await logger.info('The quick brown fox', '123');
|
177
|
+
// flushing the log messages again should trigger send only once
|
178
|
+
await logger.flush();
|
179
|
+
await logger.flush();
|
180
|
+
await logger.flush();
|
181
|
+
await logger.flush();
|
182
|
+
expect(postSetMock.send).toBeCalledTimes(1);
|
183
|
+
});
|
184
|
+
|
185
|
+
it('should not send message till queue reaching the max message count', async () => {
|
186
|
+
const logger = new ComponentLogger(loggerConfig);
|
187
|
+
|
188
|
+
await logger.info('The quick brown fox', '123');
|
189
|
+
expect(postSetMock.send).toBeCalledTimes(0);
|
190
|
+
});
|
191
|
+
|
192
|
+
it('should automatically flush the queue after reaching the max message count', async () => {
|
193
|
+
const logger = new ComponentLogger(loggerConfig);
|
194
|
+
|
195
|
+
for (let count = 0; count <= 100; count++) {
|
196
|
+
await logger.info('The quick brown fox', `${count}`);
|
197
|
+
}
|
198
|
+
// this should send all 100 messages above in a single go
|
199
|
+
expect(postSetMock.send).toBeCalledTimes(1);
|
200
|
+
});
|
201
|
+
|
202
|
+
it('should send debug message', async () => {
|
203
|
+
const logger = new ComponentLogger(loggerConfig);
|
204
|
+
|
205
|
+
await logger.debug('some debug message', '123');
|
206
|
+
await logger.flush();
|
207
|
+
|
208
|
+
const expectedMessages = [
|
209
|
+
{
|
210
|
+
level: 'DEBUG',
|
211
|
+
service: 'render-runtime',
|
212
|
+
timestamp: '2023-05-28T00:00:00.000Z',
|
213
|
+
message: 'some debug message',
|
214
|
+
tags: 'some-tags',
|
215
|
+
traceid: '123',
|
216
|
+
},
|
217
|
+
];
|
218
|
+
expect(postSetMock.send).toBeCalledWith(expectedMessages);
|
219
|
+
});
|
220
|
+
|
221
|
+
it('should send warn message', async () => {
|
222
|
+
const logger = new ComponentLogger(loggerConfig);
|
223
|
+
|
224
|
+
await logger.warn('some warn message', '123');
|
225
|
+
await logger.flush();
|
226
|
+
|
227
|
+
const expectedMessages = [
|
228
|
+
{
|
229
|
+
level: 'WARN',
|
230
|
+
service: 'render-runtime',
|
231
|
+
timestamp: '2023-05-28T00:00:00.000Z',
|
232
|
+
message: 'some warn message',
|
233
|
+
tags: 'some-tags',
|
234
|
+
traceid: '123',
|
235
|
+
},
|
236
|
+
];
|
237
|
+
expect(postSetMock.send).toBeCalledWith(expectedMessages);
|
238
|
+
});
|
239
|
+
|
240
|
+
it('should send error message', async () => {
|
241
|
+
const logger = new ComponentLogger(loggerConfig);
|
242
|
+
|
243
|
+
await logger.error('some error message', '123');
|
244
|
+
await logger.flush();
|
245
|
+
|
246
|
+
const expectedMessages = [
|
247
|
+
{
|
248
|
+
level: 'ERROR',
|
249
|
+
service: 'render-runtime',
|
250
|
+
timestamp: '2023-05-28T00:00:00.000Z',
|
251
|
+
message: 'some error message',
|
252
|
+
tags: 'some-tags',
|
253
|
+
traceid: '123',
|
254
|
+
},
|
255
|
+
];
|
256
|
+
expect(postSetMock.send).toBeCalledWith(expectedMessages);
|
19
257
|
});
|
20
258
|
});
|
package/src/component-logger.ts
CHANGED
@@ -2,6 +2,8 @@ import superagent from 'superagent';
|
|
2
2
|
import { ComponentLogLevel, ComponentLogMessage } from './component-log-message';
|
3
3
|
import { ComponentLoggerConfig } from './component-logger-config';
|
4
4
|
|
5
|
+
const MESSAGE_BATCH_SIZE = 100;
|
6
|
+
|
5
7
|
export class ComponentLogger {
|
6
8
|
private batch: Array<ComponentLogMessage> = [];
|
7
9
|
|
@@ -35,7 +37,7 @@ export class ComponentLogger {
|
|
35
37
|
return this.append('ERROR', message, traceId);
|
36
38
|
}
|
37
39
|
|
38
|
-
async append(level: ComponentLogLevel, message: string | any, traceId?: string): Promise<void> {
|
40
|
+
protected async append(level: ComponentLogLevel, message: string | any, traceId?: string): Promise<void> {
|
39
41
|
const payload = typeof message == 'string' ? message : JSON.stringify(message);
|
40
42
|
|
41
43
|
this.batch.push({
|
@@ -44,20 +46,20 @@ export class ComponentLogger {
|
|
44
46
|
timestamp: new Date().toISOString(),
|
45
47
|
message: payload,
|
46
48
|
tags: this.config.tags,
|
47
|
-
|
49
|
+
traceid: traceId,
|
48
50
|
});
|
49
51
|
|
50
|
-
if (this.batch.length ==
|
52
|
+
if (this.batch.length == MESSAGE_BATCH_SIZE) {
|
51
53
|
await this.flush();
|
52
|
-
this.batch = [];
|
53
54
|
}
|
54
55
|
}
|
55
56
|
|
56
57
|
async flush(): Promise<void> {
|
57
58
|
if (this.config.enabled && this.batch.length > 0) {
|
58
59
|
try {
|
59
|
-
const url = `${this.config.logUrl}/${this.config.tenantId}`;
|
60
|
-
await superagent.post(url).set('
|
60
|
+
const url = `${this.config.logUrl.replace(/\/+$/, '')}/${this.config.tenantId}`;
|
61
|
+
await superagent.post(url).set('x-api-key', this.config.key).send(this.batch);
|
62
|
+
this.batch = [];
|
61
63
|
} catch (err) {
|
62
64
|
console.error(err);
|
63
65
|
throw err;
|
@@ -0,0 +1,31 @@
|
|
1
|
+
import { getComponentLogger } from './getComponentLogger';
|
2
|
+
import { ComponentLogger } from '.';
|
3
|
+
|
4
|
+
describe('getComponentLogger', () => {
|
5
|
+
it('should return the logger object', async () => {
|
6
|
+
const logger: ComponentLogger = await getComponentLogger({
|
7
|
+
enabled: false,
|
8
|
+
accessCredentials: JSON.stringify({
|
9
|
+
url: 'some-url',
|
10
|
+
key: 'some-key',
|
11
|
+
}),
|
12
|
+
tags: 'some-tags',
|
13
|
+
tenantId: 'some-tenant-id',
|
14
|
+
});
|
15
|
+
|
16
|
+
expect(logger).toBeTruthy();
|
17
|
+
});
|
18
|
+
|
19
|
+
it('should throw error if both accessSecret and accessCredential is provided', async () => {
|
20
|
+
const logger = getComponentLogger({
|
21
|
+
enabled: false,
|
22
|
+
accessSecret: 'some-access-secret',
|
23
|
+
accessCredentials: 'some-access-creds',
|
24
|
+
tags: 'some-tags',
|
25
|
+
tenantId: 'some-tenant-id',
|
26
|
+
});
|
27
|
+
await expect(logger).rejects.toThrowError(
|
28
|
+
'Only either of Logging Service API accessSecret or accessCredentials should be provided',
|
29
|
+
);
|
30
|
+
});
|
31
|
+
});
|
@@ -0,0 +1,55 @@
|
|
1
|
+
import { ComponentLogger } from './component-logger';
|
2
|
+
import { GetSecretValueCommand, SecretsManagerClient } from '@aws-sdk/client-secrets-manager';
|
3
|
+
|
4
|
+
type ApiAccessInfo = {
|
5
|
+
url: string;
|
6
|
+
key: string;
|
7
|
+
};
|
8
|
+
|
9
|
+
export async function getComponentLogger(config: {
|
10
|
+
enabled: boolean;
|
11
|
+
tenantId: string;
|
12
|
+
accessSecret?: string | false | undefined;
|
13
|
+
accessCredentials?: string | false | undefined;
|
14
|
+
tags: string;
|
15
|
+
}) {
|
16
|
+
if (config.accessSecret && config.accessCredentials) {
|
17
|
+
throw new Error('Only either of Logging Service API accessSecret or accessCredentials should be provided');
|
18
|
+
}
|
19
|
+
|
20
|
+
let apiAccessString = '';
|
21
|
+
if (config.enabled) {
|
22
|
+
if (config.accessCredentials) {
|
23
|
+
apiAccessString = config.accessCredentials;
|
24
|
+
} else if (config.accessSecret) {
|
25
|
+
apiAccessString = await getSecretString(config.accessSecret);
|
26
|
+
}
|
27
|
+
}
|
28
|
+
let apiAccessInfo: ApiAccessInfo = {
|
29
|
+
url: '',
|
30
|
+
key: '',
|
31
|
+
};
|
32
|
+
if (apiAccessString.length) {
|
33
|
+
apiAccessInfo = JSON.parse(apiAccessString);
|
34
|
+
}
|
35
|
+
|
36
|
+
return new ComponentLogger({
|
37
|
+
enabled: config.enabled,
|
38
|
+
tenantId: config.tenantId,
|
39
|
+
logUrl: apiAccessInfo.url,
|
40
|
+
key: apiAccessInfo.key,
|
41
|
+
tags: config.tags,
|
42
|
+
});
|
43
|
+
}
|
44
|
+
|
45
|
+
async function getSecretString(id: string) {
|
46
|
+
const client = new SecretsManagerClient({});
|
47
|
+
|
48
|
+
const result = await client.send(new GetSecretValueCommand({ SecretId: id }));
|
49
|
+
|
50
|
+
if (result.SecretString === undefined) {
|
51
|
+
throw new Error(`Secret ${id} could not be found`);
|
52
|
+
}
|
53
|
+
|
54
|
+
return result.SecretString;
|
55
|
+
}
|
package/src/index.ts
CHANGED