@vrplatform/log 1.0.0

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/src/baselog.ts ADDED
@@ -0,0 +1,170 @@
1
+ import { getCorrelationId, requestToContext } from './common';
2
+ import type { BaseLog, ChildLogOptions, Log } from './type';
3
+ export * from './type';
4
+ import { Axiom } from '@axiomhq/js';
5
+ import { serializeError } from 'serialize-error';
6
+ import { logger } from './color';
7
+
8
+ let lastMessageTimestamp: number | undefined = undefined;
9
+
10
+ export function createBaseLog(
11
+ {
12
+ token,
13
+ orgId = 'vrplatform-mv6k',
14
+ dataset,
15
+ consoleLog,
16
+ }: {
17
+ token?: string;
18
+ orgId?: string;
19
+ dataset?: string;
20
+ consoleLog?: boolean | 'color';
21
+ },
22
+ {
23
+ environment,
24
+ workerId,
25
+ app,
26
+ version,
27
+ context,
28
+ type,
29
+ correlationId,
30
+ executionContext,
31
+ }: {
32
+ correlationId?: string;
33
+ environment?: 'development' | 'production';
34
+ type?: 'worker' | 'durableObject';
35
+ workerId?: string;
36
+ app?: string;
37
+ version?: string;
38
+ context?: Record<string, any>;
39
+ executionContext?: {
40
+ waitUntil(promise: Promise<any>): void;
41
+ };
42
+ }
43
+ ): BaseLog {
44
+ const baseFields: Record<string, any> = {
45
+ type: type || 'worker',
46
+ environment: environment || 'production',
47
+ worker: {
48
+ id: workerId || 'default',
49
+ app: app || 'default',
50
+ version: version || 'default',
51
+ started: new Date().toISOString(),
52
+ },
53
+ ...context,
54
+ };
55
+
56
+ const axiom = token
57
+ ? new Axiom({
58
+ token,
59
+ orgId,
60
+ onError() {},
61
+ })
62
+ : undefined;
63
+
64
+ function ingest(
65
+ level: 'info' | 'log' | 'error' | 'debug' | string,
66
+ message: string,
67
+ additionalFields: Record<string, any> = {}
68
+ ) {
69
+ axiom?.ingest(dataset!, {
70
+ _time: Date.now(),
71
+ level,
72
+ message,
73
+ ...baseFields,
74
+ ...additionalFields,
75
+ });
76
+ }
77
+
78
+ function createBaseLog(arg: ChildLogOptions = {}): Log {
79
+ const childContext = arg.context || {};
80
+ if (arg.request) {
81
+ childContext.request = requestToContext(arg.request);
82
+ }
83
+ const baseLog = (
84
+ level: string,
85
+ message: any,
86
+ additionalContext: Record<string, any> = {}
87
+ ) => {
88
+ const cid =
89
+ arg.correlationId ||
90
+ correlationId ||
91
+ childContext?.correlationId ||
92
+ additionalContext?.correlationId ||
93
+ correlationId;
94
+ ingest(level, message, {
95
+ ...childContext,
96
+ ...additionalContext,
97
+ correlationId: cid,
98
+ });
99
+ if (consoleLog) {
100
+ const name = arg.name ? `${app}/${arg.name}` : app;
101
+ const lev = level.toUpperCase();
102
+
103
+ const now = Date.now();
104
+ const diff = (
105
+ lastMessageTimestamp ? `${now - lastMessageTimestamp}ms ` : ''
106
+ ).padEnd(6, ' ');
107
+ lastMessageTimestamp = now;
108
+ if (consoleLog === 'color')
109
+ logger
110
+ .dim()
111
+ .append(`${diff}`)
112
+ .reset()
113
+ .color('green')
114
+ .append(`[${name}] `)
115
+ .reset()
116
+ .bold()
117
+ .append(`${lev} `)
118
+ .reset()
119
+ .color('blue')
120
+ .bold()
121
+ .append(cid ? `[${cid}] ` : '')
122
+ .reset()
123
+ .append(message)
124
+ .log();
125
+ else
126
+ console.log(`[${lev}] [${name}]${cid ? ` [${cid}]` : ''} ${message}`);
127
+ }
128
+ };
129
+ return {
130
+ addContext(arg) {
131
+ for (const key in arg) {
132
+ childContext[key] = arg[key];
133
+ }
134
+ },
135
+ child: ({
136
+ correlationId,
137
+ name,
138
+ request,
139
+ context = {},
140
+ }: ChildLogOptions) =>
141
+ createBaseLog({
142
+ correlationId:
143
+ correlationId ||
144
+ (request ? getCorrelationId(request) : undefined) ||
145
+ undefined,
146
+ name,
147
+ request,
148
+ context: { ...childContext, ...context },
149
+ }),
150
+ error: (message, error) => {
151
+ const m =
152
+ typeof message === 'string'
153
+ ? message
154
+ : error?.message || error?.name || 'Error';
155
+ const e = typeof message === 'string' ? error : message;
156
+ return baseLog('error', m, e instanceof Error ? serializeError(e) : e);
157
+ },
158
+ debug: (message, data) => baseLog('debug', message, data),
159
+ warn: (message, data) => baseLog('warn', message, data),
160
+ info: (message, data) => baseLog('info', message, data),
161
+ };
162
+ }
163
+ const baseLog = createBaseLog() as any as BaseLog;
164
+ baseLog.flush = () => {
165
+ if (executionContext && axiom) {
166
+ executionContext.waitUntil(axiom.flush());
167
+ } else if (axiom) return axiom.flush();
168
+ };
169
+ return baseLog;
170
+ }
package/src/color.ts ADDED
@@ -0,0 +1,456 @@
1
+ export const objectEntries = Object.entries as <T extends object>(
2
+ value: T
3
+ ) => {
4
+ [K in keyof T]-?: [K, T[K]];
5
+ }[keyof T][];
6
+
7
+ export type LEVEL = 'debug' | 'info' | 'warn' | 'error' | 'disable' | 'success';
8
+
9
+ type ticketObject = {
10
+ font?: COLOR;
11
+ bg?: COLOR;
12
+ };
13
+
14
+ type settingObject = {
15
+ [key in SETTING]?: boolean;
16
+ };
17
+
18
+ export type SETTING =
19
+ | 'bold'
20
+ | 'italic'
21
+ | 'dim'
22
+ | 'underscore'
23
+ | 'reverse'
24
+ | 'strikethrough';
25
+
26
+ export type COLOR =
27
+ | 'black'
28
+ | 'red'
29
+ | 'green'
30
+ | 'yellow'
31
+ | 'blue'
32
+ | 'magenta'
33
+ | 'cyan'
34
+ | 'white';
35
+
36
+ const CONFIG = {
37
+ SYSTEM: {
38
+ reset: '\x1b[0m',
39
+ bold: '\x1b[1m',
40
+ dim: '\x1b[2m',
41
+ italic: '\x1b[3m',
42
+ underscore: '\x1b[4m',
43
+ reverse: '\x1b[7m',
44
+ strikethrough: '\x1b[9m',
45
+ backoneline: '\x1b[1A',
46
+ cleanthisline: '\x1b[K',
47
+ },
48
+ FONT: {
49
+ black: '\x1b[30m',
50
+ red: '\x1b[31m',
51
+ green: '\x1b[32m',
52
+ yellow: '\x1b[33m',
53
+ blue: '\x1b[34m',
54
+ magenta: '\x1b[35m',
55
+ cyan: '\x1b[36m',
56
+ white: '\x1b[37m',
57
+ },
58
+ BACKGROUND: {
59
+ black: '\x1b[40m',
60
+ red: '\x1b[41m',
61
+ green: '\x1b[42m',
62
+ yellow: '\x1b[43m',
63
+ blue: '\x1b[44m',
64
+ magenta: '\x1b[45m',
65
+ cyan: '\x1b[46m',
66
+ white: '\x1b[47m',
67
+ },
68
+ };
69
+
70
+ // Sequence of levels is important.
71
+ const LEVELS = ['success', 'debug', 'info', 'warn', 'error', 'disable'];
72
+
73
+ class Logger {
74
+ private command: string;
75
+ private lastCommand: string;
76
+ private name: string;
77
+ private level?: LEVEL;
78
+ private noColor: boolean;
79
+ private _getDate: () => string;
80
+ private _customizedConsole: Console;
81
+ constructor(name?: string) {
82
+ // Current command
83
+ this.command = '';
84
+ // Last line
85
+ this.lastCommand = '';
86
+
87
+ this.name = name || '';
88
+
89
+ // set level from env
90
+ const level =
91
+ typeof process !== 'undefined' ? process.env.LOGGER : undefined;
92
+ if (this.isLevelValid(level)) {
93
+ this.level = level as LEVEL;
94
+ }
95
+
96
+ this.noColor = false;
97
+
98
+ this._getDate = () => new Date().toISOString();
99
+
100
+ this._customizedConsole = console;
101
+ }
102
+
103
+ createNamedLogger(name: string) {
104
+ return new Logger(name);
105
+ }
106
+
107
+ setLevel(level: LEVEL) {
108
+ if (this.isLevelValid(level)) {
109
+ this.level = level;
110
+ } else {
111
+ throw 'Level you are trying to set is invalid';
112
+ }
113
+ }
114
+
115
+ setLogStream(newStream: any) {
116
+ if (newStream.writable) {
117
+ this._customizedConsole = new console.Console(newStream);
118
+ } else {
119
+ throw 'invalid writable stream object';
120
+ }
121
+ }
122
+
123
+ setLevelNoColor() {
124
+ this.noColor = true;
125
+ }
126
+
127
+ setLevelColor() {
128
+ this.noColor = false;
129
+ }
130
+
131
+ isLevelValid(level?: string) {
132
+ return LEVELS.includes(level!);
133
+ }
134
+
135
+ isAllowedLevel(level: LEVEL) {
136
+ return this.level
137
+ ? LEVELS.indexOf(this.level) <= LEVELS.indexOf(level)
138
+ : true;
139
+ }
140
+
141
+ log(...args: any[]) {
142
+ this.append(...args);
143
+ if (!this.noColor) {
144
+ this.command += CONFIG.SYSTEM.reset;
145
+ }
146
+ this._print(this.command);
147
+ // Save last command if we need to use for joint
148
+ this.lastCommand = this.command;
149
+ this.command = '';
150
+ return this;
151
+ }
152
+
153
+ // deprecated
154
+ joint() {
155
+ console.error(
156
+ 'node-color-log warning: `joint` is deprecated, please use `append`'
157
+ );
158
+
159
+ // Clear the last line
160
+ this._print(CONFIG.SYSTEM.backoneline + CONFIG.SYSTEM.cleanthisline);
161
+
162
+ // Reset the command to let it joint the next
163
+ // And print from the position of last line
164
+ this.command = '';
165
+
166
+ // if joint more than twice, we should clean the previous
167
+ // backline command, since we should only do it for the
168
+ // current time.
169
+ this.lastCommand = this.lastCommand.replace(CONFIG.SYSTEM.backoneline, '');
170
+
171
+ // back to the last line
172
+ this.command += CONFIG.SYSTEM.backoneline;
173
+
174
+ this.command += this.lastCommand;
175
+ return this;
176
+ }
177
+
178
+ setDate(callback: () => string) {
179
+ this._getDate = callback;
180
+ }
181
+
182
+ getPrefix() {
183
+ if (this.name) {
184
+ return `${this._getDate()} [${this.name}]`;
185
+ } else {
186
+ return this._getDate();
187
+ }
188
+ }
189
+
190
+ color(ticket: COLOR) {
191
+ if (ticket in CONFIG.FONT) {
192
+ this.command += CONFIG.FONT[ticket];
193
+ } else {
194
+ console.error(
195
+ 'node-color-log warning: Font color not found! Use the default.'
196
+ );
197
+ }
198
+ return this;
199
+ }
200
+
201
+ bgColor(ticket: COLOR) {
202
+ if (ticket in CONFIG.BACKGROUND) {
203
+ this.command += CONFIG.BACKGROUND[ticket];
204
+ } else {
205
+ console.error(
206
+ 'node-color-log warning: Background color not found! Use the default.'
207
+ );
208
+ }
209
+ return this;
210
+ }
211
+
212
+ bold() {
213
+ this.command += CONFIG.SYSTEM.bold;
214
+ return this;
215
+ }
216
+
217
+ dim() {
218
+ this.command += CONFIG.SYSTEM.dim;
219
+ return this;
220
+ }
221
+
222
+ underscore() {
223
+ this.command += CONFIG.SYSTEM.underscore;
224
+ return this;
225
+ }
226
+
227
+ strikethrough() {
228
+ this.command += CONFIG.SYSTEM.strikethrough;
229
+ return this;
230
+ }
231
+
232
+ reverse() {
233
+ this.command += CONFIG.SYSTEM.reverse;
234
+ return this;
235
+ }
236
+
237
+ italic() {
238
+ this.command += CONFIG.SYSTEM.italic;
239
+ return this;
240
+ }
241
+
242
+ fontColorLog(ticket: COLOR, text: string, setting?: settingObject) {
243
+ let command = '';
244
+ if (setting) {
245
+ command += this.checkSetting(setting);
246
+ }
247
+ if (ticket in CONFIG.FONT) {
248
+ command += CONFIG.FONT[ticket];
249
+ } else {
250
+ console.error(
251
+ 'node-color-log warning: Font color not found! Use the default.'
252
+ );
253
+ }
254
+ command += text;
255
+
256
+ command += CONFIG.SYSTEM.reset;
257
+ this._print(command);
258
+ }
259
+
260
+ bgColorLog(ticket: COLOR, text: string, setting?: settingObject) {
261
+ let command = '';
262
+ if (setting) {
263
+ command += this.checkSetting(setting);
264
+ }
265
+ if (ticket in CONFIG.BACKGROUND) {
266
+ command += CONFIG.BACKGROUND[ticket];
267
+ } else {
268
+ console.error(
269
+ 'node-color-log warning: Background color not found! Use the default.'
270
+ );
271
+ }
272
+ command += text;
273
+
274
+ command += CONFIG.SYSTEM.reset;
275
+ this._print(command);
276
+ }
277
+
278
+ colorLog(ticketObj: ticketObject, text: string, setting?: settingObject) {
279
+ let command = '';
280
+ if (setting) {
281
+ command += this.checkSetting(setting);
282
+ }
283
+ if (ticketObj.font! in CONFIG.FONT) {
284
+ command += CONFIG.FONT[ticketObj.font!];
285
+ } else {
286
+ console.error(
287
+ 'node-color-log warning: Font color not found! Use the default.'
288
+ );
289
+ }
290
+ if (ticketObj.bg! in CONFIG.BACKGROUND) {
291
+ command += CONFIG.BACKGROUND[ticketObj.bg!];
292
+ } else {
293
+ console.error(
294
+ 'node-color-log warning: Background color not found! Use the default.'
295
+ );
296
+ }
297
+
298
+ command += text;
299
+
300
+ command += CONFIG.SYSTEM.reset;
301
+ this._print(command);
302
+ }
303
+
304
+ error(...args: any[]) {
305
+ if (!this.isAllowedLevel('error')) return;
306
+
307
+ if (this.noColor) {
308
+ const d = this.getPrefix();
309
+ this.log(d, ' [ERROR] ', ...args);
310
+ } else {
311
+ const d = this.getPrefix();
312
+ this.append(`${d} `)
313
+ .bgColor('red')
314
+ .append('[ERROR]')
315
+ .reset()
316
+ .append(' ')
317
+ .color('red')
318
+ .log(...args);
319
+ }
320
+ }
321
+
322
+ warn(...args: any[]) {
323
+ if (!this.isAllowedLevel('warn')) return;
324
+
325
+ if (this.noColor) {
326
+ const d = this.getPrefix();
327
+ this.log(d, ' [WARN] ', ...args);
328
+ } else {
329
+ const d = this.getPrefix();
330
+ this.append(`${d} `)
331
+ .bgColor('yellow')
332
+ .color('black')
333
+ .append('[WARN]')
334
+ .reset()
335
+ .append(' ')
336
+ .color('yellow')
337
+ .log(...args);
338
+ }
339
+ }
340
+
341
+ info(...args: any[]) {
342
+ if (!this.isAllowedLevel('info')) return;
343
+
344
+ if (this.noColor) {
345
+ const d = this.getPrefix();
346
+ this.log(d, ' [INFO] ', ...args);
347
+ } else {
348
+ const d = this.getPrefix();
349
+ this.append(`${d} `)
350
+ .bgColor('green')
351
+ .color('black')
352
+ .append('[INFO]')
353
+ .reset()
354
+ .append(' ')
355
+ .color('green')
356
+ .log(...args);
357
+ }
358
+ }
359
+
360
+ debug(...args: any[]) {
361
+ if (!this.isAllowedLevel('debug')) return;
362
+
363
+ if (this.noColor) {
364
+ const d = this.getPrefix();
365
+ this.log(d, ' [DEBUG] ', ...args);
366
+ } else {
367
+ const d = this.getPrefix();
368
+ this.append(`${d} `)
369
+ .bgColor('cyan')
370
+ .color('black')
371
+ .append('[DEBUG]')
372
+ .reset()
373
+ .append(' ')
374
+ .color('cyan')
375
+ .log(...args);
376
+ }
377
+ }
378
+
379
+ success(...args: any[]) {
380
+ if (!this.isAllowedLevel('success')) return;
381
+
382
+ if (this.noColor) {
383
+ const d = this.getPrefix();
384
+ this.log(d, ' [SUCCESS] ', ...args);
385
+ } else {
386
+ const d = this.getPrefix();
387
+ this.append(`${d} `)
388
+ .bgColor('green')
389
+ .color('black')
390
+ .append('[SUCCESS]')
391
+ .reset()
392
+ .append(' ')
393
+ .color('green')
394
+ .log(...args);
395
+ }
396
+ }
397
+
398
+ checkSetting(setting: settingObject) {
399
+ const validSetting = [
400
+ 'bold',
401
+ 'italic',
402
+ 'dim',
403
+ 'underscore',
404
+ 'reverse',
405
+ 'strikethrough',
406
+ ];
407
+ let command = '';
408
+ for (const [k, s] of objectEntries(setting)) {
409
+ if (validSetting.indexOf(k) !== -1) {
410
+ if (s === true) {
411
+ command += CONFIG.SYSTEM[k];
412
+ } else if (s !== false) {
413
+ console.error(
414
+ `node-color-log warning: The value ${k} should be boolean.`
415
+ );
416
+ }
417
+ } else {
418
+ console.error(`node-color-log warning: ${k} is not valid in setting.`);
419
+ }
420
+ }
421
+ return command;
422
+ }
423
+
424
+ // helper function to output the the log to stream
425
+ _print(...args: any[]) {
426
+ this._customizedConsole.log(...args);
427
+ }
428
+
429
+ // helper function to append the command buffer
430
+ append(...args: any[]) {
431
+ for (let idx = 0; idx < args.length; idx++) {
432
+ const arg = args[idx];
433
+ if (typeof arg === 'string') {
434
+ this.command += arg;
435
+ } else {
436
+ try {
437
+ this.command += JSON.stringify(arg);
438
+ } catch {
439
+ this.command += arg;
440
+ }
441
+ }
442
+
443
+ if (args.length > 1 && idx < args.length - 1) {
444
+ this.command += ' ';
445
+ }
446
+ }
447
+ return this;
448
+ }
449
+
450
+ reset() {
451
+ this.command += CONFIG.SYSTEM.reset;
452
+ return this;
453
+ }
454
+ }
455
+
456
+ export const logger = new Logger();
package/src/common.ts ADDED
@@ -0,0 +1,84 @@
1
+ import type { RequestLike } from './type';
2
+
3
+ // Headers
4
+ export const CONNECTION_ID_HEADER = 'X-Connection-ID';
5
+ export const WORKFLOW_ID_HEADER = 'X-Workflow-ID';
6
+ export const TASK_QUEUE_HEADER = 'X-Task-Queue';
7
+ export const CORRELATION_ID_HEADER = 'X-Correlation-ID';
8
+ export const VERIFICATION_HEADER = 'X-Verification-Key';
9
+
10
+ // Env
11
+ export const AXIOM_AUTH_TOKEN_ENV = 'AXIOM_AUTH_TOKEN';
12
+ export const AXIOM_ORG_ID_ENV = 'AXIOM_ORG_ID';
13
+ export const AXIOM_DATASET_ENV = 'AXIOM_DATASET';
14
+ export const LOG_ENV = 'LOG';
15
+
16
+ export const requestHeadersToCapture = ['user-agent'];
17
+
18
+ export function getCorrelationId(request?: RequestLike) {
19
+ if (!request?.headers) return undefined;
20
+ return (
21
+ request.headers.get(CORRELATION_ID_HEADER) ||
22
+ request.headers.get('fi-correlation-id') ||
23
+ request.headers.get('x-correlation-id') ||
24
+ request.headers.get('correlation-id') ||
25
+ request.headers.get('x-transaction-id') ||
26
+ request.headers.get('transaction-id') ||
27
+ request.headers.get('cf-ray') ||
28
+ request.headers.get('cf-request-id') ||
29
+ request.headers.get('request-id') ||
30
+ undefined
31
+ );
32
+ }
33
+
34
+ export function isDev(request?: RequestLike) {
35
+ return request?.url?.startsWith('http://localhost:');
36
+ }
37
+
38
+ export function mergeFn<
39
+ TFn extends (...args: any[]) => any,
40
+ TObj extends Record<string, unknown>,
41
+ >(fn: TFn, obj: TObj): TFn & TObj {
42
+ return Object.assign(fn, obj);
43
+ }
44
+
45
+ export function getHeaderMap(
46
+ headers: any | undefined,
47
+ allowlist: string[] = []
48
+ ) {
49
+ if (!allowlist.length || !headers) {
50
+ return {};
51
+ }
52
+
53
+ return [...headers].reduce((acc, [headerKey, headerValue]) => {
54
+ if (allowlist.includes(headerKey)) {
55
+ acc[headerKey] = headerValue;
56
+ }
57
+
58
+ return acc;
59
+ }, {});
60
+ }
61
+
62
+ export function requestToContext(
63
+ request?: RequestLike & { cf?: any; body?: any },
64
+ body?: any
65
+ ) {
66
+ if (!request) return {};
67
+
68
+ const cf: Record<string, unknown> = {};
69
+ if (request?.cf) {
70
+ // delete does not work so we copy into a new object
71
+ for (const [key, value] of Object.entries(request.cf)) {
72
+ if (key !== 'tlsClientAuth' && key !== 'tlsExportedAuthenticator') {
73
+ cf[key] = value;
74
+ }
75
+ }
76
+ }
77
+ return {
78
+ url: request?.url,
79
+ headers: getHeaderMap(request?.headers, requestHeadersToCapture),
80
+ method: request?.method,
81
+ cloudflare: cf,
82
+ body: body || request.body || undefined,
83
+ };
84
+ }