@unito/integration-sdk 2.3.3 → 2.3.5
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/dist/src/index.cjs
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
var integrationApi = require('@unito/integration-api');
|
|
4
4
|
var cachette = require('cachette');
|
|
5
5
|
var crypto = require('crypto');
|
|
6
|
+
var util = require('util');
|
|
6
7
|
var express = require('express');
|
|
7
8
|
var busboy = require('busboy');
|
|
8
9
|
var https = require('https');
|
|
@@ -161,7 +162,13 @@ class Logger {
|
|
|
161
162
|
status: logLevel,
|
|
162
163
|
};
|
|
163
164
|
if (process.env.NODE_ENV === 'development') {
|
|
164
|
-
|
|
165
|
+
const coloredMessage = Logger.colorize(message, processedLogs, logLevel);
|
|
166
|
+
const metadata = {
|
|
167
|
+
date: new Date(processedLogs.date).toISOString(),
|
|
168
|
+
...(processedMetadata.error && { error: processedMetadata.error }),
|
|
169
|
+
};
|
|
170
|
+
const metadataString = Object.keys(metadata).length > 1 ? ` ${JSON.stringify(metadata, null, 2)}` : ` ${JSON.stringify(metadata)}`;
|
|
171
|
+
console[logLevel](`${coloredMessage}${metadataString}`);
|
|
165
172
|
}
|
|
166
173
|
else {
|
|
167
174
|
console[logLevel](JSON.stringify(processedLogs));
|
|
@@ -204,6 +211,55 @@ class Logger {
|
|
|
204
211
|
}
|
|
205
212
|
return prunedMetadata;
|
|
206
213
|
}
|
|
214
|
+
/**
|
|
215
|
+
* Colorizes the log message based on the log level and status codes.
|
|
216
|
+
* @param message The message to colorize.
|
|
217
|
+
* @param metadata The metadata associated with the log.
|
|
218
|
+
* @param logLevel The log level of the message.
|
|
219
|
+
* @returns The colorized output string.
|
|
220
|
+
*/
|
|
221
|
+
static colorize(message, metadata, logLevel) {
|
|
222
|
+
if (!process.stdout.isTTY) {
|
|
223
|
+
return `${logLevel}: ${message}`;
|
|
224
|
+
}
|
|
225
|
+
// Extract status code from logs
|
|
226
|
+
let statusCode;
|
|
227
|
+
if (metadata.http && typeof metadata.http === 'object' && !Array.isArray(metadata.http)) {
|
|
228
|
+
const statusCodeValue = metadata.http.status_code;
|
|
229
|
+
if (typeof statusCodeValue === 'number') {
|
|
230
|
+
statusCode = statusCodeValue;
|
|
231
|
+
}
|
|
232
|
+
else if (typeof statusCodeValue === 'string') {
|
|
233
|
+
statusCode = parseInt(statusCodeValue, 10);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
// Color based on status code first
|
|
237
|
+
if (statusCode) {
|
|
238
|
+
if (statusCode >= 400) {
|
|
239
|
+
return `${util.styleText('red', logLevel)}: ${util.styleText('red', message)}`;
|
|
240
|
+
}
|
|
241
|
+
else if (statusCode >= 300) {
|
|
242
|
+
return `${util.styleText('yellow', logLevel)}: ${util.styleText('yellow', message)}`;
|
|
243
|
+
}
|
|
244
|
+
else if (statusCode >= 200) {
|
|
245
|
+
return `${util.styleText('green', logLevel)}: ${util.styleText('green', message)}`;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
// Fall back to log level if no status code found
|
|
249
|
+
switch (logLevel) {
|
|
250
|
+
case LogLevel.ERROR:
|
|
251
|
+
return `${util.styleText('red', logLevel)}: ${util.styleText('red', message)}`;
|
|
252
|
+
case LogLevel.WARN:
|
|
253
|
+
return `${util.styleText('yellow', logLevel)}: ${util.styleText('yellow', message)}`;
|
|
254
|
+
case LogLevel.INFO:
|
|
255
|
+
case LogLevel.LOG:
|
|
256
|
+
return `${util.styleText('green', logLevel)}: ${util.styleText('green', message)}`;
|
|
257
|
+
case LogLevel.DEBUG:
|
|
258
|
+
return `${util.styleText('cyan', logLevel)}: ${util.styleText('cyan', message)}`;
|
|
259
|
+
default:
|
|
260
|
+
return `${logLevel}: ${message}`;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
207
263
|
}
|
|
208
264
|
const NULL_LOGGER = new Logger({}, true);
|
|
209
265
|
|
|
@@ -68,6 +68,14 @@ export default class Logger {
|
|
|
68
68
|
private send;
|
|
69
69
|
private static snakifyKeys;
|
|
70
70
|
private static pruneSensitiveMetadata;
|
|
71
|
+
/**
|
|
72
|
+
* Colorizes the log message based on the log level and status codes.
|
|
73
|
+
* @param message The message to colorize.
|
|
74
|
+
* @param metadata The metadata associated with the log.
|
|
75
|
+
* @param logLevel The log level of the message.
|
|
76
|
+
* @returns The colorized output string.
|
|
77
|
+
*/
|
|
78
|
+
private static colorize;
|
|
71
79
|
}
|
|
72
80
|
export declare const NULL_LOGGER: Logger;
|
|
73
81
|
export {};
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { styleText } from 'util';
|
|
1
2
|
var LogLevel;
|
|
2
3
|
(function (LogLevel) {
|
|
3
4
|
LogLevel["ERROR"] = "error";
|
|
@@ -133,7 +134,13 @@ export default class Logger {
|
|
|
133
134
|
status: logLevel,
|
|
134
135
|
};
|
|
135
136
|
if (process.env.NODE_ENV === 'development') {
|
|
136
|
-
|
|
137
|
+
const coloredMessage = Logger.colorize(message, processedLogs, logLevel);
|
|
138
|
+
const metadata = {
|
|
139
|
+
date: new Date(processedLogs.date).toISOString(),
|
|
140
|
+
...(processedMetadata.error && { error: processedMetadata.error }),
|
|
141
|
+
};
|
|
142
|
+
const metadataString = Object.keys(metadata).length > 1 ? ` ${JSON.stringify(metadata, null, 2)}` : ` ${JSON.stringify(metadata)}`;
|
|
143
|
+
console[logLevel](`${coloredMessage}${metadataString}`);
|
|
137
144
|
}
|
|
138
145
|
else {
|
|
139
146
|
console[logLevel](JSON.stringify(processedLogs));
|
|
@@ -176,5 +183,54 @@ export default class Logger {
|
|
|
176
183
|
}
|
|
177
184
|
return prunedMetadata;
|
|
178
185
|
}
|
|
186
|
+
/**
|
|
187
|
+
* Colorizes the log message based on the log level and status codes.
|
|
188
|
+
* @param message The message to colorize.
|
|
189
|
+
* @param metadata The metadata associated with the log.
|
|
190
|
+
* @param logLevel The log level of the message.
|
|
191
|
+
* @returns The colorized output string.
|
|
192
|
+
*/
|
|
193
|
+
static colorize(message, metadata, logLevel) {
|
|
194
|
+
if (!process.stdout.isTTY) {
|
|
195
|
+
return `${logLevel}: ${message}`;
|
|
196
|
+
}
|
|
197
|
+
// Extract status code from logs
|
|
198
|
+
let statusCode;
|
|
199
|
+
if (metadata.http && typeof metadata.http === 'object' && !Array.isArray(metadata.http)) {
|
|
200
|
+
const statusCodeValue = metadata.http.status_code;
|
|
201
|
+
if (typeof statusCodeValue === 'number') {
|
|
202
|
+
statusCode = statusCodeValue;
|
|
203
|
+
}
|
|
204
|
+
else if (typeof statusCodeValue === 'string') {
|
|
205
|
+
statusCode = parseInt(statusCodeValue, 10);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
// Color based on status code first
|
|
209
|
+
if (statusCode) {
|
|
210
|
+
if (statusCode >= 400) {
|
|
211
|
+
return `${styleText('red', logLevel)}: ${styleText('red', message)}`;
|
|
212
|
+
}
|
|
213
|
+
else if (statusCode >= 300) {
|
|
214
|
+
return `${styleText('yellow', logLevel)}: ${styleText('yellow', message)}`;
|
|
215
|
+
}
|
|
216
|
+
else if (statusCode >= 200) {
|
|
217
|
+
return `${styleText('green', logLevel)}: ${styleText('green', message)}`;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
// Fall back to log level if no status code found
|
|
221
|
+
switch (logLevel) {
|
|
222
|
+
case LogLevel.ERROR:
|
|
223
|
+
return `${styleText('red', logLevel)}: ${styleText('red', message)}`;
|
|
224
|
+
case LogLevel.WARN:
|
|
225
|
+
return `${styleText('yellow', logLevel)}: ${styleText('yellow', message)}`;
|
|
226
|
+
case LogLevel.INFO:
|
|
227
|
+
case LogLevel.LOG:
|
|
228
|
+
return `${styleText('green', logLevel)}: ${styleText('green', message)}`;
|
|
229
|
+
case LogLevel.DEBUG:
|
|
230
|
+
return `${styleText('cyan', logLevel)}: ${styleText('cyan', message)}`;
|
|
231
|
+
default:
|
|
232
|
+
return `${logLevel}: ${message}`;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
179
235
|
}
|
|
180
236
|
export const NULL_LOGGER = new Logger({}, true);
|
|
@@ -129,7 +129,7 @@ describe('Logger', () => {
|
|
|
129
129
|
{ id: 2, organization_id: 'b' },
|
|
130
130
|
]);
|
|
131
131
|
});
|
|
132
|
-
it('prunes sensitive Metadata',
|
|
132
|
+
it('prunes sensitive Metadata', testContext => {
|
|
133
133
|
const logSpy = testContext.mock.method(global.console, 'log', () => { });
|
|
134
134
|
assert.strictEqual(logSpy.mock.calls.length, 0);
|
|
135
135
|
const metadata = {
|
|
@@ -177,4 +177,134 @@ describe('Logger', () => {
|
|
|
177
177
|
assert.strictEqual(infoSpy.mock.calls.length, 0);
|
|
178
178
|
assert.strictEqual(debugSpy.mock.calls.length, 0);
|
|
179
179
|
});
|
|
180
|
+
it('colorizes logs by status codes over log levels', () => {
|
|
181
|
+
const originalEnv = process.env.NODE_ENV;
|
|
182
|
+
const originalIsTTY = process.stdout.isTTY;
|
|
183
|
+
try {
|
|
184
|
+
process.env.NODE_ENV = 'development';
|
|
185
|
+
Object.defineProperty(process.stdout, 'isTTY', {
|
|
186
|
+
value: true,
|
|
187
|
+
configurable: true,
|
|
188
|
+
});
|
|
189
|
+
// 4xx status with warn level, should be red
|
|
190
|
+
const metadataWith400 = {
|
|
191
|
+
http: { status_code: 400 },
|
|
192
|
+
};
|
|
193
|
+
const redResult = Logger['colorize']('Some error', metadataWith400, 'warn');
|
|
194
|
+
// warn level without status code, should be yellow
|
|
195
|
+
const metadataNoStatus = {};
|
|
196
|
+
const yellowResult = Logger['colorize']('Some warning', metadataNoStatus, 'warn');
|
|
197
|
+
// Results should be different colors
|
|
198
|
+
assert.notEqual(redResult, yellowResult);
|
|
199
|
+
assert.ok(redResult.includes('\x1b[31m')); // Red color code
|
|
200
|
+
assert.ok(yellowResult.includes('\x1b[33m')); // Yellow color code
|
|
201
|
+
}
|
|
202
|
+
finally {
|
|
203
|
+
// Restore original values
|
|
204
|
+
process.env.NODE_ENV = originalEnv;
|
|
205
|
+
Object.defineProperty(process.stdout, 'isTTY', {
|
|
206
|
+
value: originalIsTTY,
|
|
207
|
+
configurable: true,
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
});
|
|
211
|
+
it('colorizes logs with different status code ranges', () => {
|
|
212
|
+
const originalEnv = process.env.NODE_ENV;
|
|
213
|
+
const originalIsTTY = process.stdout.isTTY;
|
|
214
|
+
try {
|
|
215
|
+
process.env.NODE_ENV = 'development';
|
|
216
|
+
Object.defineProperty(process.stdout, 'isTTY', {
|
|
217
|
+
value: true,
|
|
218
|
+
configurable: true,
|
|
219
|
+
});
|
|
220
|
+
// 2xx status codes should be green
|
|
221
|
+
const successMetadata = {
|
|
222
|
+
http: { status_code: 200 },
|
|
223
|
+
};
|
|
224
|
+
const greenResult = Logger['colorize']('Success', successMetadata, 'info');
|
|
225
|
+
assert.ok(greenResult.includes('\x1b[32m')); // Green color code
|
|
226
|
+
// 3xx status codes should be yellow
|
|
227
|
+
const redirectMetadata = {
|
|
228
|
+
http: { status_code: 301 },
|
|
229
|
+
};
|
|
230
|
+
const yellowResult = Logger['colorize']('Redirect', redirectMetadata, 'info');
|
|
231
|
+
assert.ok(yellowResult.includes('\x1b[33m')); // Yellow color code
|
|
232
|
+
// 4xx status codes should be red
|
|
233
|
+
const clientErrorMetadata = {
|
|
234
|
+
http: { status_code: 404 },
|
|
235
|
+
};
|
|
236
|
+
const redResult = Logger['colorize']('Not Found', clientErrorMetadata, 'warn');
|
|
237
|
+
assert.ok(redResult.includes('\x1b[31m')); // Red color code
|
|
238
|
+
// 5xx status codes should be red
|
|
239
|
+
const serverErrorMetadata = {
|
|
240
|
+
http: { status_code: 500 },
|
|
241
|
+
};
|
|
242
|
+
const redResult2 = Logger['colorize']('Server Error', serverErrorMetadata, 'error');
|
|
243
|
+
assert.ok(redResult2.includes('\x1b[31m')); // Red color code
|
|
244
|
+
}
|
|
245
|
+
finally {
|
|
246
|
+
process.env.NODE_ENV = originalEnv;
|
|
247
|
+
Object.defineProperty(process.stdout, 'isTTY', {
|
|
248
|
+
value: originalIsTTY,
|
|
249
|
+
configurable: true,
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
});
|
|
253
|
+
it('only logs date for metadata of successful requests in development', testContext => {
|
|
254
|
+
const originalEnv = process.env.NODE_ENV;
|
|
255
|
+
try {
|
|
256
|
+
process.env.NODE_ENV = 'development';
|
|
257
|
+
const infoSpy = testContext.mock.method(global.console, 'info', () => { });
|
|
258
|
+
const metadata = {
|
|
259
|
+
correlation_id: '123456789',
|
|
260
|
+
http: { method: 'GET', status_code: 200 },
|
|
261
|
+
user_id: 'user123',
|
|
262
|
+
request_id: 'req456',
|
|
263
|
+
};
|
|
264
|
+
const logger = new Logger(metadata);
|
|
265
|
+
logger.info('Test message without error');
|
|
266
|
+
assert.strictEqual(infoSpy.mock.calls.length, 1);
|
|
267
|
+
const loggedOutput = infoSpy.mock.calls[0]?.arguments[0];
|
|
268
|
+
assert.ok(loggedOutput.includes('Test message without error'));
|
|
269
|
+
// Should only contain date in metadata JSON
|
|
270
|
+
const metadataMatch = loggedOutput.match(/({.*})/);
|
|
271
|
+
assert.ok(metadataMatch);
|
|
272
|
+
const parsedMetadata = JSON.parse(metadataMatch[1]);
|
|
273
|
+
assert.ok(parsedMetadata.date);
|
|
274
|
+
assert.strictEqual(Object.keys(parsedMetadata).length, 1); // What matters: Only the date
|
|
275
|
+
}
|
|
276
|
+
finally {
|
|
277
|
+
process.env.NODE_ENV = originalEnv;
|
|
278
|
+
}
|
|
279
|
+
});
|
|
280
|
+
it('logs date & error stack for metadata of failed requests in development', testContext => {
|
|
281
|
+
const originalEnv = process.env.NODE_ENV;
|
|
282
|
+
try {
|
|
283
|
+
process.env.NODE_ENV = 'development';
|
|
284
|
+
const metadata = {
|
|
285
|
+
correlation_id: '123456789',
|
|
286
|
+
http: { method: 'GET', status_code: 200 },
|
|
287
|
+
user_id: 'user123',
|
|
288
|
+
request_id: 'req456',
|
|
289
|
+
};
|
|
290
|
+
const logger = new Logger(metadata);
|
|
291
|
+
// Test with error metadata
|
|
292
|
+
const errorSpy = testContext.mock.method(global.console, 'error', () => { });
|
|
293
|
+
logger.error('Test message with error', { error: { code: 500, message: 'Internal Server Error' } });
|
|
294
|
+
assert.strictEqual(errorSpy.mock.calls.length, 1);
|
|
295
|
+
const errorLoggedOutput = errorSpy.mock.calls[0]?.arguments[0];
|
|
296
|
+
assert.ok(errorLoggedOutput.includes('Test message with error'));
|
|
297
|
+
// Should contain both date and error in metadata JSON
|
|
298
|
+
const errorMetadataMatch = errorLoggedOutput.match(/({.*})/s);
|
|
299
|
+
assert.ok(errorMetadataMatch);
|
|
300
|
+
const parsedErrorMetadata = JSON.parse(errorMetadataMatch[1]);
|
|
301
|
+
assert.ok(parsedErrorMetadata.date);
|
|
302
|
+
assert.ok(parsedErrorMetadata.error);
|
|
303
|
+
assert.deepEqual(parsedErrorMetadata.error, { code: 500, message: 'Internal Server Error' });
|
|
304
|
+
assert.strictEqual(Object.keys(parsedErrorMetadata).length, 2); // What matters: Date and error only
|
|
305
|
+
}
|
|
306
|
+
finally {
|
|
307
|
+
process.env.NODE_ENV = originalEnv;
|
|
308
|
+
}
|
|
309
|
+
});
|
|
180
310
|
});
|
package/package.json
CHANGED
package/src/resources/logger.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { styleText } from 'util';
|
|
2
|
+
|
|
1
3
|
const enum LogLevel {
|
|
2
4
|
ERROR = 'error',
|
|
3
5
|
WARN = 'warn',
|
|
@@ -162,7 +164,17 @@ export default class Logger {
|
|
|
162
164
|
};
|
|
163
165
|
|
|
164
166
|
if (process.env.NODE_ENV === 'development') {
|
|
165
|
-
|
|
167
|
+
const coloredMessage = Logger.colorize(message, processedLogs, logLevel);
|
|
168
|
+
|
|
169
|
+
const metadata = {
|
|
170
|
+
date: new Date(processedLogs.date).toISOString(),
|
|
171
|
+
...(processedMetadata.error && { error: processedMetadata.error }),
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
const metadataString =
|
|
175
|
+
Object.keys(metadata).length > 1 ? ` ${JSON.stringify(metadata, null, 2)}` : ` ${JSON.stringify(metadata)}`;
|
|
176
|
+
|
|
177
|
+
console[logLevel](`${coloredMessage}${metadataString}`);
|
|
166
178
|
} else {
|
|
167
179
|
console[logLevel](JSON.stringify(processedLogs));
|
|
168
180
|
}
|
|
@@ -209,6 +221,57 @@ export default class Logger {
|
|
|
209
221
|
|
|
210
222
|
return prunedMetadata;
|
|
211
223
|
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Colorizes the log message based on the log level and status codes.
|
|
227
|
+
* @param message The message to colorize.
|
|
228
|
+
* @param metadata The metadata associated with the log.
|
|
229
|
+
* @param logLevel The log level of the message.
|
|
230
|
+
* @returns The colorized output string.
|
|
231
|
+
*/
|
|
232
|
+
private static colorize(message: string, metadata: Value, logLevel: LogLevel): string {
|
|
233
|
+
if (!process.stdout.isTTY) {
|
|
234
|
+
return `${logLevel}: ${message}`;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// Extract status code from logs
|
|
238
|
+
let statusCode: number | undefined;
|
|
239
|
+
if (metadata.http && typeof metadata.http === 'object' && !Array.isArray(metadata.http)) {
|
|
240
|
+
const statusCodeValue = metadata.http.status_code;
|
|
241
|
+
|
|
242
|
+
if (typeof statusCodeValue === 'number') {
|
|
243
|
+
statusCode = statusCodeValue;
|
|
244
|
+
} else if (typeof statusCodeValue === 'string') {
|
|
245
|
+
statusCode = parseInt(statusCodeValue, 10);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// Color based on status code first
|
|
250
|
+
if (statusCode) {
|
|
251
|
+
if (statusCode >= 400) {
|
|
252
|
+
return `${styleText('red', logLevel)}: ${styleText('red', message)}`;
|
|
253
|
+
} else if (statusCode >= 300) {
|
|
254
|
+
return `${styleText('yellow', logLevel)}: ${styleText('yellow', message)}`;
|
|
255
|
+
} else if (statusCode >= 200) {
|
|
256
|
+
return `${styleText('green', logLevel)}: ${styleText('green', message)}`;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// Fall back to log level if no status code found
|
|
261
|
+
switch (logLevel) {
|
|
262
|
+
case LogLevel.ERROR:
|
|
263
|
+
return `${styleText('red', logLevel)}: ${styleText('red', message)}`;
|
|
264
|
+
case LogLevel.WARN:
|
|
265
|
+
return `${styleText('yellow', logLevel)}: ${styleText('yellow', message)}`;
|
|
266
|
+
case LogLevel.INFO:
|
|
267
|
+
case LogLevel.LOG:
|
|
268
|
+
return `${styleText('green', logLevel)}: ${styleText('green', message)}`;
|
|
269
|
+
case LogLevel.DEBUG:
|
|
270
|
+
return `${styleText('cyan', logLevel)}: ${styleText('cyan', message)}`;
|
|
271
|
+
default:
|
|
272
|
+
return `${logLevel}: ${message}`;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
212
275
|
}
|
|
213
276
|
|
|
214
277
|
export const NULL_LOGGER = new Logger({}, true);
|
|
@@ -155,7 +155,7 @@ describe('Logger', () => {
|
|
|
155
155
|
]);
|
|
156
156
|
});
|
|
157
157
|
|
|
158
|
-
it('prunes sensitive Metadata',
|
|
158
|
+
it('prunes sensitive Metadata', testContext => {
|
|
159
159
|
const logSpy = testContext.mock.method(global.console, 'log', () => {});
|
|
160
160
|
assert.strictEqual(logSpy.mock.calls.length, 0);
|
|
161
161
|
|
|
@@ -209,4 +209,155 @@ describe('Logger', () => {
|
|
|
209
209
|
assert.strictEqual(infoSpy.mock.calls.length, 0);
|
|
210
210
|
assert.strictEqual(debugSpy.mock.calls.length, 0);
|
|
211
211
|
});
|
|
212
|
+
|
|
213
|
+
it('colorizes logs by status codes over log levels', () => {
|
|
214
|
+
const originalEnv = process.env.NODE_ENV;
|
|
215
|
+
const originalIsTTY = process.stdout.isTTY;
|
|
216
|
+
|
|
217
|
+
try {
|
|
218
|
+
process.env.NODE_ENV = 'development';
|
|
219
|
+
Object.defineProperty(process.stdout, 'isTTY', {
|
|
220
|
+
value: true,
|
|
221
|
+
configurable: true,
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
// 4xx status with warn level, should be red
|
|
225
|
+
const metadataWith400 = {
|
|
226
|
+
http: { status_code: 400 },
|
|
227
|
+
};
|
|
228
|
+
const redResult = Logger['colorize']('Some error', metadataWith400, 'warn' as any);
|
|
229
|
+
|
|
230
|
+
// warn level without status code, should be yellow
|
|
231
|
+
const metadataNoStatus = {};
|
|
232
|
+
const yellowResult = Logger['colorize']('Some warning', metadataNoStatus, 'warn' as any);
|
|
233
|
+
|
|
234
|
+
// Results should be different colors
|
|
235
|
+
assert.notEqual(redResult, yellowResult);
|
|
236
|
+
assert.ok(redResult.includes('\x1b[31m')); // Red color code
|
|
237
|
+
assert.ok(yellowResult.includes('\x1b[33m')); // Yellow color code
|
|
238
|
+
} finally {
|
|
239
|
+
// Restore original values
|
|
240
|
+
process.env.NODE_ENV = originalEnv;
|
|
241
|
+
Object.defineProperty(process.stdout, 'isTTY', {
|
|
242
|
+
value: originalIsTTY,
|
|
243
|
+
configurable: true,
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
it('colorizes logs with different status code ranges', () => {
|
|
249
|
+
const originalEnv = process.env.NODE_ENV;
|
|
250
|
+
const originalIsTTY = process.stdout.isTTY;
|
|
251
|
+
|
|
252
|
+
try {
|
|
253
|
+
process.env.NODE_ENV = 'development';
|
|
254
|
+
Object.defineProperty(process.stdout, 'isTTY', {
|
|
255
|
+
value: true,
|
|
256
|
+
configurable: true,
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
// 2xx status codes should be green
|
|
260
|
+
const successMetadata = {
|
|
261
|
+
http: { status_code: 200 },
|
|
262
|
+
};
|
|
263
|
+
const greenResult = Logger['colorize']('Success', successMetadata, 'info' as any);
|
|
264
|
+
assert.ok(greenResult.includes('\x1b[32m')); // Green color code
|
|
265
|
+
|
|
266
|
+
// 3xx status codes should be yellow
|
|
267
|
+
const redirectMetadata = {
|
|
268
|
+
http: { status_code: 301 },
|
|
269
|
+
};
|
|
270
|
+
const yellowResult = Logger['colorize']('Redirect', redirectMetadata, 'info' as any);
|
|
271
|
+
assert.ok(yellowResult.includes('\x1b[33m')); // Yellow color code
|
|
272
|
+
|
|
273
|
+
// 4xx status codes should be red
|
|
274
|
+
const clientErrorMetadata = {
|
|
275
|
+
http: { status_code: 404 },
|
|
276
|
+
};
|
|
277
|
+
const redResult = Logger['colorize']('Not Found', clientErrorMetadata, 'warn' as any);
|
|
278
|
+
assert.ok(redResult.includes('\x1b[31m')); // Red color code
|
|
279
|
+
|
|
280
|
+
// 5xx status codes should be red
|
|
281
|
+
const serverErrorMetadata = {
|
|
282
|
+
http: { status_code: 500 },
|
|
283
|
+
};
|
|
284
|
+
const redResult2 = Logger['colorize']('Server Error', serverErrorMetadata, 'error' as any);
|
|
285
|
+
assert.ok(redResult2.includes('\x1b[31m')); // Red color code
|
|
286
|
+
} finally {
|
|
287
|
+
process.env.NODE_ENV = originalEnv;
|
|
288
|
+
Object.defineProperty(process.stdout, 'isTTY', {
|
|
289
|
+
value: originalIsTTY,
|
|
290
|
+
configurable: true,
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
it('only logs date for metadata of successful requests in development', testContext => {
|
|
296
|
+
const originalEnv = process.env.NODE_ENV;
|
|
297
|
+
|
|
298
|
+
try {
|
|
299
|
+
process.env.NODE_ENV = 'development';
|
|
300
|
+
|
|
301
|
+
const infoSpy = testContext.mock.method(global.console, 'info', () => {});
|
|
302
|
+
const metadata = {
|
|
303
|
+
correlation_id: '123456789',
|
|
304
|
+
http: { method: 'GET', status_code: 200 },
|
|
305
|
+
user_id: 'user123',
|
|
306
|
+
request_id: 'req456',
|
|
307
|
+
};
|
|
308
|
+
|
|
309
|
+
const logger = new Logger(metadata);
|
|
310
|
+
|
|
311
|
+
logger.info('Test message without error');
|
|
312
|
+
assert.strictEqual(infoSpy.mock.calls.length, 1);
|
|
313
|
+
|
|
314
|
+
const loggedOutput = infoSpy.mock.calls[0]?.arguments[0];
|
|
315
|
+
assert.ok(loggedOutput.includes('Test message without error'));
|
|
316
|
+
|
|
317
|
+
// Should only contain date in metadata JSON
|
|
318
|
+
const metadataMatch = loggedOutput.match(/({.*})/);
|
|
319
|
+
assert.ok(metadataMatch);
|
|
320
|
+
const parsedMetadata = JSON.parse(metadataMatch[1]);
|
|
321
|
+
assert.ok(parsedMetadata.date);
|
|
322
|
+
assert.strictEqual(Object.keys(parsedMetadata).length, 1); // What matters: Only the date
|
|
323
|
+
} finally {
|
|
324
|
+
process.env.NODE_ENV = originalEnv;
|
|
325
|
+
}
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
it('logs date & error stack for metadata of failed requests in development', testContext => {
|
|
329
|
+
const originalEnv = process.env.NODE_ENV;
|
|
330
|
+
|
|
331
|
+
try {
|
|
332
|
+
process.env.NODE_ENV = 'development';
|
|
333
|
+
|
|
334
|
+
const metadata = {
|
|
335
|
+
correlation_id: '123456789',
|
|
336
|
+
http: { method: 'GET', status_code: 200 },
|
|
337
|
+
user_id: 'user123',
|
|
338
|
+
request_id: 'req456',
|
|
339
|
+
};
|
|
340
|
+
|
|
341
|
+
const logger = new Logger(metadata);
|
|
342
|
+
|
|
343
|
+
// Test with error metadata
|
|
344
|
+
const errorSpy = testContext.mock.method(global.console, 'error', () => {});
|
|
345
|
+
logger.error('Test message with error', { error: { code: 500, message: 'Internal Server Error' } });
|
|
346
|
+
assert.strictEqual(errorSpy.mock.calls.length, 1);
|
|
347
|
+
|
|
348
|
+
const errorLoggedOutput = errorSpy.mock.calls[0]?.arguments[0];
|
|
349
|
+
assert.ok(errorLoggedOutput.includes('Test message with error'));
|
|
350
|
+
|
|
351
|
+
// Should contain both date and error in metadata JSON
|
|
352
|
+
const errorMetadataMatch = errorLoggedOutput.match(/({.*})/s);
|
|
353
|
+
assert.ok(errorMetadataMatch);
|
|
354
|
+
const parsedErrorMetadata = JSON.parse(errorMetadataMatch[1]);
|
|
355
|
+
assert.ok(parsedErrorMetadata.date);
|
|
356
|
+
assert.ok(parsedErrorMetadata.error);
|
|
357
|
+
assert.deepEqual(parsedErrorMetadata.error, { code: 500, message: 'Internal Server Error' });
|
|
358
|
+
assert.strictEqual(Object.keys(parsedErrorMetadata).length, 2); // What matters: Date and error only
|
|
359
|
+
} finally {
|
|
360
|
+
process.env.NODE_ENV = originalEnv;
|
|
361
|
+
}
|
|
362
|
+
});
|
|
212
363
|
});
|