@verdaccio/logger 9.0.0-next-9.3 → 9.0.0-next-9.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/CHANGELOG.md +1121 -0
- package/build/_virtual/_rolldown/runtime.js +23 -0
- package/build/_virtual/_rolldown/runtime.mjs +7 -0
- package/build/colors.d.ts +1 -0
- package/build/colors.js +11 -0
- package/build/colors.js.map +1 -0
- package/build/colors.mjs +10 -0
- package/build/colors.mjs.map +1 -0
- package/build/formatter.d.ts +10 -0
- package/build/formatter.js +47 -0
- package/build/formatter.js.map +1 -0
- package/build/formatter.mjs +45 -0
- package/build/formatter.mjs.map +1 -0
- package/build/index.d.ts +13 -2
- package/build/index.js +43 -15
- package/build/index.js.map +1 -1
- package/build/index.mjs +21 -0
- package/build/index.mjs.map +1 -0
- package/build/levels.d.ts +35 -0
- package/build/levels.js +53 -0
- package/build/levels.js.map +1 -0
- package/build/levels.mjs +50 -0
- package/build/levels.mjs.map +1 -0
- package/build/logger.d.ts +9 -0
- package/build/logger.js +86 -0
- package/build/logger.js.map +1 -0
- package/build/logger.mjs +82 -0
- package/build/logger.mjs.map +1 -0
- package/build/prettify.d.ts +20 -0
- package/build/prettify.js +91 -0
- package/build/prettify.js.map +1 -0
- package/build/prettify.mjs +86 -0
- package/build/prettify.mjs.map +1 -0
- package/build/transport.d.ts +6 -0
- package/build/transport.js +33 -0
- package/build/transport.js.map +1 -0
- package/build/transport.mjs +31 -0
- package/build/transport.mjs.map +1 -0
- package/build/types.d.ts +5 -0
- package/build/utils.d.ts +5 -0
- package/build/utils.js +20 -0
- package/build/utils.js.map +1 -0
- package/build/utils.mjs +16 -0
- package/build/utils.mjs.map +1 -0
- package/package.json +30 -11
- package/src/colors.ts +8 -0
- package/src/formatter.ts +90 -0
- package/src/index.ts +28 -0
- package/src/levels.ts +60 -0
- package/src/logger.ts +126 -0
- package/src/prettify.ts +119 -0
- package/src/transport.ts +39 -0
- package/src/types.ts +6 -0
- package/src/utils.ts +18 -0
- package/test/__snapshots__/formatter.spec.ts.snap +21 -0
- package/test/createLogger.spec.ts +22 -0
- package/test/formatter.spec.ts +214 -0
- package/test/index.spec.ts +49 -0
- package/test/logger.spec.ts +368 -0
- package/test/prettify.spec.ts +387 -0
- package/test/transport.spec.ts +108 -0
- package/test/utils.test.ts +19 -0
- package/tsconfig.build.json +9 -0
- package/tsconfig.json +16 -0
- package/vite.config.mjs +3 -0
package/src/index.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import pino from 'pino';
|
|
2
|
+
|
|
3
|
+
import type { Logger, LoggerConfigItem } from '@verdaccio/types';
|
|
4
|
+
|
|
5
|
+
import { prepareSetup } from './logger';
|
|
6
|
+
|
|
7
|
+
export { createLogger, prepareSetup, willUseTransport } from './logger';
|
|
8
|
+
export type { LogPlugin, LoggerConfig } from './logger';
|
|
9
|
+
export { default as prettifyTransport, autoEnd, buildPretty, buildSafeSonicBoom } from './prettify';
|
|
10
|
+
export { hasColors } from './colors';
|
|
11
|
+
export { fillInMsgTemplate, printMessage } from './formatter';
|
|
12
|
+
export { createPrettyTransport, isPrettyFormat } from './transport';
|
|
13
|
+
export type { LevelCode } from './levels';
|
|
14
|
+
export { formatLoggingDate, padRight } from './utils';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Initialize the logger singleton.
|
|
18
|
+
*/
|
|
19
|
+
export async function setup(options: LoggerConfigItem): Promise<Logger> {
|
|
20
|
+
if (typeof logger !== 'undefined') {
|
|
21
|
+
return logger;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
logger = await prepareSetup(options, pino);
|
|
25
|
+
return logger;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export let logger: Logger;
|
package/src/levels.ts
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { black, blue, cyan, green, magenta, red, white, yellow } from 'colorette';
|
|
2
|
+
|
|
3
|
+
export type LogLevel = 'trace' | 'debug' | 'info' | 'http' | 'warn' | 'error' | 'fatal';
|
|
4
|
+
|
|
5
|
+
export type LevelCode = number;
|
|
6
|
+
|
|
7
|
+
export function calculateLevel(levelCode: LevelCode): LogLevel {
|
|
8
|
+
switch (true) {
|
|
9
|
+
case levelCode === 10:
|
|
10
|
+
return 'trace';
|
|
11
|
+
case levelCode === 20:
|
|
12
|
+
return 'debug';
|
|
13
|
+
case levelCode === 25:
|
|
14
|
+
return 'http';
|
|
15
|
+
case levelCode === 30:
|
|
16
|
+
return 'info';
|
|
17
|
+
case levelCode === 40:
|
|
18
|
+
return 'warn';
|
|
19
|
+
case levelCode === 50:
|
|
20
|
+
return 'error';
|
|
21
|
+
case levelCode === 60:
|
|
22
|
+
return 'fatal';
|
|
23
|
+
default:
|
|
24
|
+
return 'fatal';
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export const levelsColors = {
|
|
29
|
+
fatal: red,
|
|
30
|
+
error: red,
|
|
31
|
+
warn: yellow,
|
|
32
|
+
http: magenta,
|
|
33
|
+
info: cyan,
|
|
34
|
+
debug: green,
|
|
35
|
+
trace: white,
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
enum ARROWS {
|
|
39
|
+
LEFT = '<--',
|
|
40
|
+
RIGHT = '-->',
|
|
41
|
+
EQUAL = '-=-',
|
|
42
|
+
NEUTRAL = '---',
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export const subSystemLevels = {
|
|
46
|
+
color: {
|
|
47
|
+
in: green(ARROWS.LEFT),
|
|
48
|
+
out: yellow(ARROWS.RIGHT),
|
|
49
|
+
auth: blue(ARROWS.NEUTRAL),
|
|
50
|
+
fs: black(ARROWS.EQUAL),
|
|
51
|
+
default: blue(ARROWS.NEUTRAL),
|
|
52
|
+
},
|
|
53
|
+
white: {
|
|
54
|
+
in: ARROWS.LEFT,
|
|
55
|
+
out: ARROWS.RIGHT,
|
|
56
|
+
auth: ARROWS.NEUTRAL,
|
|
57
|
+
fs: ARROWS.EQUAL,
|
|
58
|
+
default: ARROWS.NEUTRAL,
|
|
59
|
+
},
|
|
60
|
+
};
|
package/src/logger.ts
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
// <reference types="node" />
|
|
2
|
+
import buildDebug from 'debug';
|
|
3
|
+
import type { LoggerOptions } from 'pino';
|
|
4
|
+
|
|
5
|
+
import type { Logger, LoggerConfigItem, LoggerFormat } from '@verdaccio/types';
|
|
6
|
+
|
|
7
|
+
import { fillInMsgTemplate } from './formatter';
|
|
8
|
+
import { createPrettyTransport, isPrettyFormat } from './transport';
|
|
9
|
+
|
|
10
|
+
const debug = buildDebug('verdaccio:logger');
|
|
11
|
+
|
|
12
|
+
function isProd() {
|
|
13
|
+
return process.env.NODE_ENV === 'production';
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const DEFAULT_LOG_FORMAT = isProd() ? 'json' : 'pretty';
|
|
17
|
+
debug('default log format: %s', DEFAULT_LOG_FORMAT);
|
|
18
|
+
|
|
19
|
+
export type LogPlugin = {
|
|
20
|
+
dest: string;
|
|
21
|
+
options?: any[];
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export function createLogger(
|
|
25
|
+
options: LoggerConfigItem = { level: 'http' },
|
|
26
|
+
destination: NodeJS.WritableStream | undefined,
|
|
27
|
+
format: LoggerFormat = DEFAULT_LOG_FORMAT,
|
|
28
|
+
pino
|
|
29
|
+
): any {
|
|
30
|
+
debug('setup logger');
|
|
31
|
+
let pinoConfig: LoggerOptions = {
|
|
32
|
+
customLevels: {
|
|
33
|
+
http: 25,
|
|
34
|
+
},
|
|
35
|
+
level: options.level,
|
|
36
|
+
serializers: {
|
|
37
|
+
err: pino.stdSerializers.err,
|
|
38
|
+
req: pino.stdSerializers.req,
|
|
39
|
+
res: pino.stdSerializers.res,
|
|
40
|
+
},
|
|
41
|
+
redact: options.redact,
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
debug('has prettifier? %o', !isProd());
|
|
45
|
+
let logger;
|
|
46
|
+
// pretty logs are not allowed in production for performance reasons
|
|
47
|
+
if (isPrettyFormat(format) && isProd() === false) {
|
|
48
|
+
const transport = createPrettyTransport(pino, options, format);
|
|
49
|
+
logger = pino(pinoConfig, transport);
|
|
50
|
+
} else {
|
|
51
|
+
pinoConfig = {
|
|
52
|
+
...pinoConfig,
|
|
53
|
+
// https://getpino.io/#/docs/api?id=hooks-object
|
|
54
|
+
hooks: {
|
|
55
|
+
logMethod(args: [obj: unknown, msg?: string, ...rest: unknown[]], method, _level) {
|
|
56
|
+
const [templateObject, message, ...otherArgs] = args;
|
|
57
|
+
const templateVars =
|
|
58
|
+
!!templateObject && typeof templateObject === 'object'
|
|
59
|
+
? Object.getOwnPropertyNames(templateObject)
|
|
60
|
+
: [];
|
|
61
|
+
if (!message || !templateVars.length) return method.apply(this, args);
|
|
62
|
+
const hydratedMessage = fillInMsgTemplate(message, templateObject as any, false);
|
|
63
|
+
return method.apply(this, [templateObject, hydratedMessage, ...otherArgs] as any);
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
};
|
|
67
|
+
logger = pino(pinoConfig, destination);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (process.env.DEBUG) {
|
|
71
|
+
logger.on('level-change', (lvl, val, prevLvl, prevVal, instance) => {
|
|
72
|
+
if (logger !== instance) {
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
debug('%s (%d) was changed to %s (%d)', lvl, val, prevLvl, prevVal);
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return logger;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const DEFAULT_LOGGER_CONF: LoggerConfigItem = {
|
|
83
|
+
type: 'stdout',
|
|
84
|
+
format: 'pretty',
|
|
85
|
+
level: 'http',
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
export type LoggerConfig = LoggerConfigItem;
|
|
89
|
+
|
|
90
|
+
export function willUseTransport(format: LoggerFormat | undefined): boolean {
|
|
91
|
+
const resolvedFormat = format ?? (isProd() ? 'json' : 'pretty');
|
|
92
|
+
return isPrettyFormat(resolvedFormat) && isProd() === false;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export async function prepareSetup(
|
|
96
|
+
options: LoggerConfigItem = DEFAULT_LOGGER_CONF,
|
|
97
|
+
pino
|
|
98
|
+
): Promise<Logger> {
|
|
99
|
+
let loggerConfig = options;
|
|
100
|
+
if (!loggerConfig?.level) {
|
|
101
|
+
loggerConfig = {
|
|
102
|
+
...loggerConfig,
|
|
103
|
+
level: 'http',
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
if (loggerConfig.type === 'file') {
|
|
107
|
+
debug('logging file enabled');
|
|
108
|
+
// Pretty format uses a pino transport that creates its own destination stream,
|
|
109
|
+
// so no destination is needed here.
|
|
110
|
+
if (willUseTransport(loggerConfig.format)) {
|
|
111
|
+
return createLogger(loggerConfig, undefined, loggerConfig.format, pino);
|
|
112
|
+
}
|
|
113
|
+
// For file destinations (json format), wait for the fd to be ready
|
|
114
|
+
// so we fail fast on bad paths / permissions instead of losing early logs
|
|
115
|
+
const destination = pino.destination(loggerConfig.path);
|
|
116
|
+
await new Promise<void>((resolve, reject) => {
|
|
117
|
+
destination.once('ready', resolve);
|
|
118
|
+
destination.once('error', reject);
|
|
119
|
+
});
|
|
120
|
+
debug('file destination ready: %s', loggerConfig.path);
|
|
121
|
+
process.on('SIGUSR2', () => destination.reopen());
|
|
122
|
+
return createLogger(loggerConfig, destination, loggerConfig.format, pino);
|
|
123
|
+
}
|
|
124
|
+
debug('logging stdout enabled');
|
|
125
|
+
return createLogger(loggerConfig, pino.destination(1), loggerConfig.format, pino);
|
|
126
|
+
}
|
package/src/prettify.ts
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import type { WriteStream } from 'node:fs';
|
|
2
|
+
import { Transform, pipeline } from 'node:stream';
|
|
3
|
+
import { isMainThread } from 'node:worker_threads';
|
|
4
|
+
import build from 'pino-abstract-transport';
|
|
5
|
+
import type { SonicBoomOpts } from 'sonic-boom';
|
|
6
|
+
import SonicBoom from 'sonic-boom';
|
|
7
|
+
|
|
8
|
+
import { hasColors } from './colors';
|
|
9
|
+
import { fillInMsgTemplate, printMessage } from './formatter';
|
|
10
|
+
import type { PrettyOptionsExtended } from './types';
|
|
11
|
+
|
|
12
|
+
export { fillInMsgTemplate };
|
|
13
|
+
|
|
14
|
+
function noop() {}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Creates a safe SonicBoom instance
|
|
18
|
+
*
|
|
19
|
+
* @param {object} opts Options for SonicBoom
|
|
20
|
+
*
|
|
21
|
+
* @returns {object} A new SonicBoom stream
|
|
22
|
+
*/
|
|
23
|
+
export function buildSafeSonicBoom(opts: SonicBoomOpts) {
|
|
24
|
+
const stream = new SonicBoom(opts);
|
|
25
|
+
stream.on('error', filterBrokenPipe);
|
|
26
|
+
if (!opts.sync && isMainThread) {
|
|
27
|
+
setupOnExit(stream);
|
|
28
|
+
}
|
|
29
|
+
return stream;
|
|
30
|
+
|
|
31
|
+
function filterBrokenPipe(err) {
|
|
32
|
+
if (err.code === 'EPIPE') {
|
|
33
|
+
// @ts-ignore
|
|
34
|
+
stream.write = noop;
|
|
35
|
+
stream.end = noop;
|
|
36
|
+
stream.flushSync = noop;
|
|
37
|
+
stream.destroy = noop;
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
stream.removeListener('error', filterBrokenPipe);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function autoEnd(stream: SonicBoom & { destroyed?: boolean }, eventName: string) {
|
|
45
|
+
if (stream.destroyed) {
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (eventName === 'beforeExit') {
|
|
50
|
+
stream.flush();
|
|
51
|
+
stream.on('drain', function () {
|
|
52
|
+
stream.end();
|
|
53
|
+
});
|
|
54
|
+
} else {
|
|
55
|
+
// Guard against SonicBoom not being ready (file not yet opened)
|
|
56
|
+
// to prevent "sonic boom is not ready yet" crash on early process exit
|
|
57
|
+
try {
|
|
58
|
+
stream.flushSync();
|
|
59
|
+
} catch (err: unknown) {
|
|
60
|
+
if (err instanceof Error && err.message?.includes('not ready')) {
|
|
61
|
+
// Stream not ready, nothing to flush
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
// Re-throw real I/O errors (disk full, permission denied, etc.)
|
|
65
|
+
throw err;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function setupOnExit(stream) {
|
|
71
|
+
// WeakRef/FinalizationRegistry are guaranteed available in Node 20+ (pino v10 minimum)
|
|
72
|
+
const onExit = require('on-exit-leak-free');
|
|
73
|
+
onExit.register(stream, autoEnd);
|
|
74
|
+
stream.on('close', function () {
|
|
75
|
+
onExit.unregister(stream);
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export { hasColors } from './colors';
|
|
80
|
+
|
|
81
|
+
export function buildPretty(opts: PrettyOptionsExtended) {
|
|
82
|
+
return (chunk) => {
|
|
83
|
+
const colors = hasColors(opts.colors);
|
|
84
|
+
return printMessage(chunk, { prettyStamp: opts.prettyStamp }, colors);
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export default function (opts) {
|
|
89
|
+
const pretty = buildPretty(opts);
|
|
90
|
+
// @ts-ignore
|
|
91
|
+
return build(function (source) {
|
|
92
|
+
const stream = new Transform({
|
|
93
|
+
objectMode: true,
|
|
94
|
+
autoDestroy: true,
|
|
95
|
+
transform(chunk, enc, cb) {
|
|
96
|
+
const line = pretty(chunk) + '\n';
|
|
97
|
+
cb(null, line);
|
|
98
|
+
},
|
|
99
|
+
});
|
|
100
|
+
const destination = buildSafeSonicBoom({
|
|
101
|
+
dest: opts.destination || 1,
|
|
102
|
+
// Defaults to async (false). The transport runs in a worker thread,
|
|
103
|
+
// so sync only blocks the worker, not the main thread.
|
|
104
|
+
// Can be set to true via config for deterministic log ordering (like console.log).
|
|
105
|
+
sync: opts.sync ?? false,
|
|
106
|
+
}) as unknown as WriteStream;
|
|
107
|
+
|
|
108
|
+
source.on('unknown', function (line) {
|
|
109
|
+
destination.write(line + '\n');
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
pipeline(source, stream, destination, (err) => {
|
|
113
|
+
if (err) {
|
|
114
|
+
console.error('prettify pipeline error ', err);
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
return stream;
|
|
118
|
+
});
|
|
119
|
+
}
|
package/src/transport.ts
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { dirname, join } from 'node:path';
|
|
2
|
+
import { fileURLToPath } from 'node:url';
|
|
3
|
+
|
|
4
|
+
import type { LoggerConfigItem, LoggerFormat } from '@verdaccio/types';
|
|
5
|
+
|
|
6
|
+
import { hasColors } from './colors';
|
|
7
|
+
|
|
8
|
+
// Pino transports run in a worker thread and require an absolute path to a built JS file.
|
|
9
|
+
// __dirname works in CJS; import.meta.url works in ESM (Node 20+).
|
|
10
|
+
function getCurrentDir(): string {
|
|
11
|
+
if (typeof __dirname !== 'undefined') {
|
|
12
|
+
return __dirname;
|
|
13
|
+
}
|
|
14
|
+
// @ts-ignore -- import.meta.url requires module: es2020+ but vite preserves it for ESM output
|
|
15
|
+
return dirname(fileURLToPath(import.meta.url));
|
|
16
|
+
}
|
|
17
|
+
const prettifyPath = join(getCurrentDir(), '..', 'build', 'prettify.js');
|
|
18
|
+
|
|
19
|
+
export function isPrettyFormat(format: LoggerFormat | undefined): boolean {
|
|
20
|
+
return ['pretty-timestamped', 'pretty'].includes(format ?? 'pretty');
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Create a pino pretty transport for non-production environments.
|
|
25
|
+
*/
|
|
26
|
+
export function createPrettyTransport(pino: any, options: LoggerConfigItem, format: LoggerFormat) {
|
|
27
|
+
return pino.transport({
|
|
28
|
+
target: prettifyPath,
|
|
29
|
+
options: {
|
|
30
|
+
destination: options.path || 1,
|
|
31
|
+
colors: hasColors(options.colors),
|
|
32
|
+
prettyStamp: format === 'pretty-timestamped',
|
|
33
|
+
sync: options.sync ?? false,
|
|
34
|
+
},
|
|
35
|
+
worker: {
|
|
36
|
+
name: 'verdaccio-logger-prettify',
|
|
37
|
+
},
|
|
38
|
+
});
|
|
39
|
+
}
|
package/src/types.ts
ADDED
package/src/utils.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import dayjs from 'dayjs';
|
|
2
|
+
|
|
3
|
+
export const FORMAT_DATE = 'YYYY-MM-DD HH:mm:ss';
|
|
4
|
+
export const CUSTOM_PAD_LENGTH = 1;
|
|
5
|
+
|
|
6
|
+
export function isObject(obj: unknown): boolean {
|
|
7
|
+
return typeof obj === 'object' && obj !== null && !Array.isArray(obj);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function padRight(message: string, max = message.length + CUSTOM_PAD_LENGTH) {
|
|
11
|
+
return message.padEnd(max, ' ');
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function formatLoggingDate(time: number, message: string): string {
|
|
15
|
+
const timeFormatted = dayjs(time).format(FORMAT_DATE);
|
|
16
|
+
|
|
17
|
+
return `[${timeFormatted}] ${message}`;
|
|
18
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
|
2
|
+
|
|
3
|
+
exports[`formatter > printMessage > should display a bytes request 1`] = `"fatal<-- 200, user: null(127.0.0.1), req: 'GET /verdaccio', bytes: 0/150186"`;
|
|
4
|
+
|
|
5
|
+
exports[`formatter > printMessage > should display a resource request 1`] = `"info <-- 127.0.0.1 requested 'GET /verdaccio'"`;
|
|
6
|
+
|
|
7
|
+
exports[`formatter > printMessage > should display a streaming request 1`] = `"fatal--> 304, req: 'GET https://registry.npmjs.org/verdaccio' (streaming)"`;
|
|
8
|
+
|
|
9
|
+
exports[`formatter > printMessage > should display an error request 1`] = `"fatal--> ERR, req: 'GET https://registry.fake.org/aaa', error: getaddrinfo ENOTFOUND registry.fake.org"`;
|
|
10
|
+
|
|
11
|
+
exports[`formatter > printMessage > should display an fatal request 1`] = `"fatal--> ERR, req: 'GET https://registry.fake.org/aaa', error: fatal error"`;
|
|
12
|
+
|
|
13
|
+
exports[`formatter > printMessage > should display config file 1`] = `"warn --- config file - /Users/user/.config/verdaccio/config/config.yaml"`;
|
|
14
|
+
|
|
15
|
+
exports[`formatter > printMessage > should display custom log message 1`] = `"fatal--- custom - foo - undefined"`;
|
|
16
|
+
|
|
17
|
+
exports[`formatter > printMessage > should display trace level 1`] = `"trace--- [trace] - foo"`;
|
|
18
|
+
|
|
19
|
+
exports[`formatter > printMessage > should display trace level with pretty stamp 1`] = `"[formatted-date] trace--- [trace] - foo"`;
|
|
20
|
+
|
|
21
|
+
exports[`formatter > printMessage > should display version and http address 1`] = `"warn --- http address - http://localhost:4873/ - verdaccio/5.0.0"`;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { Writable } from 'node:stream';
|
|
2
|
+
import pino from 'pino';
|
|
3
|
+
import { describe, expect, test } from 'vitest';
|
|
4
|
+
|
|
5
|
+
import { createLogger } from '../src';
|
|
6
|
+
|
|
7
|
+
describe('logger test', () => {
|
|
8
|
+
describe('json format', () => {
|
|
9
|
+
test('should write json to a stream', () => {
|
|
10
|
+
const stream = new Writable({
|
|
11
|
+
write(chunk, encoding, callback) {
|
|
12
|
+
expect(JSON.parse(chunk.toString())).toEqual(
|
|
13
|
+
expect.objectContaining({ level: 30, msg: 'test' })
|
|
14
|
+
);
|
|
15
|
+
callback();
|
|
16
|
+
},
|
|
17
|
+
});
|
|
18
|
+
const logger = createLogger({ level: 'http' }, stream, 'json', pino);
|
|
19
|
+
logger.info('test');
|
|
20
|
+
});
|
|
21
|
+
});
|
|
22
|
+
});
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import { describe, expect, test, vi } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import { printMessage } from '../src';
|
|
4
|
+
import type { LevelCode } from '../src';
|
|
5
|
+
|
|
6
|
+
vi.mock('dayjs', () => ({
|
|
7
|
+
__esModule: true,
|
|
8
|
+
default: () => ({
|
|
9
|
+
format: () => 'formatted-date',
|
|
10
|
+
}),
|
|
11
|
+
}));
|
|
12
|
+
|
|
13
|
+
describe('formatter', () => {
|
|
14
|
+
const prettyfierOptions = { messageKey: 'msg', levelFirst: true, prettyStamp: false };
|
|
15
|
+
describe('printMessage', () => {
|
|
16
|
+
test('should display config file', () => {
|
|
17
|
+
const log = {
|
|
18
|
+
level: 40,
|
|
19
|
+
time: 1585410824129,
|
|
20
|
+
v: 1,
|
|
21
|
+
pid: 27029,
|
|
22
|
+
hostname: 'localhost',
|
|
23
|
+
file: '/Users/user/.config/verdaccio/config/config.yaml',
|
|
24
|
+
msg: 'config file - @{file}',
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
expect(printMessage(log, prettyfierOptions, false)).toMatchSnapshot();
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
test('should display trace level', () => {
|
|
31
|
+
const log = {
|
|
32
|
+
level: 10,
|
|
33
|
+
foo: 'foo',
|
|
34
|
+
msg: '[trace] - @{foo}',
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
expect(printMessage(log, prettyfierOptions, false)).toMatchSnapshot();
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
test('should display trace level with pretty stamp', () => {
|
|
41
|
+
const log = {
|
|
42
|
+
level: 10,
|
|
43
|
+
foo: 'foo',
|
|
44
|
+
time: 1585411248203,
|
|
45
|
+
msg: '[trace] - @{foo}',
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
expect(
|
|
49
|
+
printMessage(
|
|
50
|
+
log,
|
|
51
|
+
Object.assign({}, prettyfierOptions, {
|
|
52
|
+
prettyStamp: true,
|
|
53
|
+
}),
|
|
54
|
+
false
|
|
55
|
+
)
|
|
56
|
+
).toMatchSnapshot();
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
test('should display a bytes request', () => {
|
|
60
|
+
const log = {
|
|
61
|
+
level: 35,
|
|
62
|
+
time: 1585411248203,
|
|
63
|
+
v: 1,
|
|
64
|
+
pid: 27029,
|
|
65
|
+
hostname: 'macbook-touch',
|
|
66
|
+
sub: 'in',
|
|
67
|
+
request: { method: 'GET', url: '/verdaccio' },
|
|
68
|
+
user: null,
|
|
69
|
+
remoteIP: '127.0.0.1',
|
|
70
|
+
status: 200,
|
|
71
|
+
error: undefined,
|
|
72
|
+
bytes: { in: 0, out: 150186 },
|
|
73
|
+
msg:
|
|
74
|
+
"@{status}, user: @{user}(@{remoteIP}), req: '@{request.method} @{request.url}', " +
|
|
75
|
+
'bytes: @{bytes.in}/@{bytes.out}',
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
expect(printMessage(log, prettyfierOptions, false)).toMatchSnapshot();
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
test('should display an error request', () => {
|
|
82
|
+
const log = {
|
|
83
|
+
level: 54,
|
|
84
|
+
time: 1585416029123,
|
|
85
|
+
v: 1,
|
|
86
|
+
pid: 30032,
|
|
87
|
+
hostname: 'macbook-touch',
|
|
88
|
+
sub: 'out',
|
|
89
|
+
err: {
|
|
90
|
+
type: 'Error',
|
|
91
|
+
message: 'getaddrinfo ENOTFOUND registry.fake.org',
|
|
92
|
+
stack:
|
|
93
|
+
'Error: getaddrinfo ENOTFOUND registry.fake.org\n' +
|
|
94
|
+
' at GetAddrInfoReqWrap.onlookup [as oncomplete] (dns.js:60:26)',
|
|
95
|
+
errno: -3008,
|
|
96
|
+
code: 'ENOTFOUND',
|
|
97
|
+
syscall: 'getaddrinfo',
|
|
98
|
+
hostname: 'registry.fake.org',
|
|
99
|
+
},
|
|
100
|
+
request: { method: 'GET', url: 'https://registry.fake.org/aaa' },
|
|
101
|
+
status: 'ERR',
|
|
102
|
+
error: 'getaddrinfo ENOTFOUND registry.fake.org',
|
|
103
|
+
bytes: { in: 0, out: 0 },
|
|
104
|
+
msg: "@{!status}, req: '@{request.method} @{request.url}', error: @{!error}",
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
expect(printMessage(log, prettyfierOptions, false)).toMatchSnapshot();
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
test('should display an fatal request', () => {
|
|
111
|
+
const log = {
|
|
112
|
+
level: 60,
|
|
113
|
+
time: 1585416029123,
|
|
114
|
+
v: 1,
|
|
115
|
+
pid: 30032,
|
|
116
|
+
hostname: 'macbook-touch',
|
|
117
|
+
sub: 'out',
|
|
118
|
+
err: {
|
|
119
|
+
type: 'Error',
|
|
120
|
+
message: 'fatal error',
|
|
121
|
+
stack: '....',
|
|
122
|
+
errno: -3008,
|
|
123
|
+
code: 'ENOTFOUND',
|
|
124
|
+
},
|
|
125
|
+
request: { method: 'GET', url: 'https://registry.fake.org/aaa' },
|
|
126
|
+
status: 'ERR',
|
|
127
|
+
error: 'fatal error',
|
|
128
|
+
bytes: { in: 0, out: 0 },
|
|
129
|
+
msg: "@{!status}, req: '@{request.method} @{request.url}', error: @{!error}",
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
expect(printMessage(log, prettyfierOptions, false)).toMatchSnapshot();
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
test('should display a streaming request', () => {
|
|
136
|
+
const log = {
|
|
137
|
+
level: 35,
|
|
138
|
+
time: 1585411247920,
|
|
139
|
+
v: 1,
|
|
140
|
+
pid: 27029,
|
|
141
|
+
hostname: 'macbook-touch',
|
|
142
|
+
sub: 'out',
|
|
143
|
+
request: { method: 'GET', url: 'https://registry.npmjs.org/verdaccio' },
|
|
144
|
+
status: 304,
|
|
145
|
+
msg: "@{!status}, req: '@{request.method} @{request.url}' (streaming)",
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
expect(printMessage(log, prettyfierOptions, false)).toMatchSnapshot();
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
test('should display version and http address', () => {
|
|
152
|
+
const log = {
|
|
153
|
+
level: 40,
|
|
154
|
+
time: 1585410824322,
|
|
155
|
+
v: 1,
|
|
156
|
+
pid: 27029,
|
|
157
|
+
hostname: 'macbook-touch',
|
|
158
|
+
addr: 'http://localhost:4873/',
|
|
159
|
+
version: 'verdaccio/5.0.0',
|
|
160
|
+
msg: 'http address - @{addr} - @{version}',
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
expect(printMessage(log, prettyfierOptions, false)).toMatchSnapshot();
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
test('should display custom log message', () => {
|
|
167
|
+
const level: LevelCode = 15;
|
|
168
|
+
const log = {
|
|
169
|
+
level,
|
|
170
|
+
something: 'foo',
|
|
171
|
+
msg: 'custom - @{something} - @{missingParam}',
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
expect(printMessage(log, prettyfierOptions, false)).toMatchSnapshot();
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
test('should display a resource request', () => {
|
|
178
|
+
const log = {
|
|
179
|
+
level: 30,
|
|
180
|
+
time: 1585411247622,
|
|
181
|
+
v: 1,
|
|
182
|
+
pid: 27029,
|
|
183
|
+
hostname: 'macbook-touch',
|
|
184
|
+
sub: 'in',
|
|
185
|
+
req: {
|
|
186
|
+
id: undefined,
|
|
187
|
+
method: 'GET',
|
|
188
|
+
url: '/verdaccio',
|
|
189
|
+
headers: {
|
|
190
|
+
connection: 'keep-alive',
|
|
191
|
+
'user-agent': 'npm/6.13.2 node/v13.1.0 darwin x64',
|
|
192
|
+
'npm-in-ci': 'false',
|
|
193
|
+
'npm-scope': '',
|
|
194
|
+
'npm-session': 'afebb4748178bd4b',
|
|
195
|
+
referer: 'view verdaccio',
|
|
196
|
+
'pacote-req-type': 'packument',
|
|
197
|
+
'pacote-pkg-id': 'registry:verdaccio',
|
|
198
|
+
accept: 'application/json',
|
|
199
|
+
authorization: '<Classified>',
|
|
200
|
+
'if-none-match': '"fd6440ba2ad24681077664d8f969e5c3"',
|
|
201
|
+
'accept-encoding': 'gzip,deflate',
|
|
202
|
+
host: 'localhost:4873',
|
|
203
|
+
},
|
|
204
|
+
remoteAddress: '127.0.0.1',
|
|
205
|
+
remotePort: 57968,
|
|
206
|
+
},
|
|
207
|
+
ip: '127.0.0.1',
|
|
208
|
+
msg: "@{ip} requested '@{req.method} @{req.url}'",
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
expect(printMessage(log, prettyfierOptions, false)).toMatchSnapshot();
|
|
212
|
+
});
|
|
213
|
+
});
|
|
214
|
+
});
|