@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.
Files changed (65) hide show
  1. package/CHANGELOG.md +1121 -0
  2. package/build/_virtual/_rolldown/runtime.js +23 -0
  3. package/build/_virtual/_rolldown/runtime.mjs +7 -0
  4. package/build/colors.d.ts +1 -0
  5. package/build/colors.js +11 -0
  6. package/build/colors.js.map +1 -0
  7. package/build/colors.mjs +10 -0
  8. package/build/colors.mjs.map +1 -0
  9. package/build/formatter.d.ts +10 -0
  10. package/build/formatter.js +47 -0
  11. package/build/formatter.js.map +1 -0
  12. package/build/formatter.mjs +45 -0
  13. package/build/formatter.mjs.map +1 -0
  14. package/build/index.d.ts +13 -2
  15. package/build/index.js +43 -15
  16. package/build/index.js.map +1 -1
  17. package/build/index.mjs +21 -0
  18. package/build/index.mjs.map +1 -0
  19. package/build/levels.d.ts +35 -0
  20. package/build/levels.js +53 -0
  21. package/build/levels.js.map +1 -0
  22. package/build/levels.mjs +50 -0
  23. package/build/levels.mjs.map +1 -0
  24. package/build/logger.d.ts +9 -0
  25. package/build/logger.js +86 -0
  26. package/build/logger.js.map +1 -0
  27. package/build/logger.mjs +82 -0
  28. package/build/logger.mjs.map +1 -0
  29. package/build/prettify.d.ts +20 -0
  30. package/build/prettify.js +91 -0
  31. package/build/prettify.js.map +1 -0
  32. package/build/prettify.mjs +86 -0
  33. package/build/prettify.mjs.map +1 -0
  34. package/build/transport.d.ts +6 -0
  35. package/build/transport.js +33 -0
  36. package/build/transport.js.map +1 -0
  37. package/build/transport.mjs +31 -0
  38. package/build/transport.mjs.map +1 -0
  39. package/build/types.d.ts +5 -0
  40. package/build/utils.d.ts +5 -0
  41. package/build/utils.js +20 -0
  42. package/build/utils.js.map +1 -0
  43. package/build/utils.mjs +16 -0
  44. package/build/utils.mjs.map +1 -0
  45. package/package.json +30 -11
  46. package/src/colors.ts +8 -0
  47. package/src/formatter.ts +90 -0
  48. package/src/index.ts +28 -0
  49. package/src/levels.ts +60 -0
  50. package/src/logger.ts +126 -0
  51. package/src/prettify.ts +119 -0
  52. package/src/transport.ts +39 -0
  53. package/src/types.ts +6 -0
  54. package/src/utils.ts +18 -0
  55. package/test/__snapshots__/formatter.spec.ts.snap +21 -0
  56. package/test/createLogger.spec.ts +22 -0
  57. package/test/formatter.spec.ts +214 -0
  58. package/test/index.spec.ts +49 -0
  59. package/test/logger.spec.ts +368 -0
  60. package/test/prettify.spec.ts +387 -0
  61. package/test/transport.spec.ts +108 -0
  62. package/test/utils.test.ts +19 -0
  63. package/tsconfig.build.json +9 -0
  64. package/tsconfig.json +16 -0
  65. 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
+ }
@@ -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
+ }
@@ -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
@@ -0,0 +1,6 @@
1
+ import type { LoggerOptions } from 'pino';
2
+
3
+ export interface PrettyOptionsExtended extends LoggerOptions {
4
+ prettyStamp: boolean;
5
+ colors?: boolean;
6
+ }
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
+ });