From 4bc66b916616367042fb8aa86bd4cb2598d1779f Mon Sep 17 00:00:00 2001 From: chimurai <655241+chimurai@users.noreply.github.com> Date: Fri, 15 Apr 2022 16:30:09 +0200 Subject: [PATCH] refactor: logging [BREAKING CHANGE] (#749) * feat(option): logger * refactor(router, path-rewriter): log with debug [BREAKING CHANGE] * refactor(logger): log request and response with logger plugin * refactor(logger): replace logLevel and logProvider with logger option [BREAKING CHANGE] --- README.md | 37 +--- cspell.json | 1 + examples/browser-sync/index.js | 2 +- examples/express/index.js | 1 + examples/response-interceptor/index.js | 2 +- examples/websocket/index.js | 2 +- recipes/logLevel.md | 42 +--- recipes/logProvider.md | 77 +------ recipes/logger.md | 87 ++++++++ src/configuration.ts | 15 -- src/http-proxy-middleware.ts | 34 +-- src/logger.ts | 169 ++------------- src/path-rewriter.ts | 9 +- src/plugins/default/logger-plugin.ts | 49 +++-- src/router.ts | 9 +- src/types.ts | 26 ++- test/e2e/express-router.spec.ts | 1 - test/e2e/http-proxy-middleware.spec.ts | 18 +- test/e2e/http-server.spec.ts | 1 - test/types.spec.ts | 33 +-- test/unit/logger.spec.ts | 279 ++----------------------- 21 files changed, 211 insertions(+), 683 deletions(-) create mode 100644 recipes/logger.md diff --git a/README.md b/README.md index b9d039cf..3c466d4f 100644 --- a/README.md +++ b/README.md @@ -73,8 +73,7 @@ _All_ `http-proxy` [options](https://github.com/nodejitsu/node-http-proxy#option - [`pathRewrite` (object/function)](#pathrewrite-objectfunction) - [`router` (object/function)](#router-objectfunction) - [`plugins` (Array)](#plugins-array) - - [`logLevel` (string)](#loglevel-string) - - [`logProvider` (function)](#logprovider-function) + - [`logger` (Object)](#logger-object) - [`http-proxy` events](#http-proxy-events) - [`http-proxy` options](#http-proxy-options) - [WebSocket](#websocket) @@ -287,38 +286,16 @@ const config = { }; ``` -### `logLevel` (string) +### `logger` (Object) -Default: `'info'` +Configure a logger to output information from http-proxy-middleware: ie. `console`, `winston`, `pino`, `bunyan`, `log4js`, etc... -Values: ['debug', 'info', 'warn', 'error', 'silent']. - -### `logProvider` (function) - -Modify or replace log provider. Default: `console`. - -```javascript -// simple replace -function logProvider(provider) { - // replace the default console log provider. - return require('winston'); -} -``` +See also logger recipes ([recipes/logger.md](https://github.com/chimurai/http-proxy-middleware/blob/master/recipes/logger.md)) for more details. ```javascript -// verbose replacement -function logProvider(provider) { - const logger = new (require('winston').Logger)(); - - const myCustomProvider = { - log: logger.log, - debug: logger.debug, - info: logger.info, - warn: logger.warn, - error: logger.error, - }; - return myCustomProvider; -} +createProxyMiddleware({ + logger: console, +}); ``` ## `http-proxy` events diff --git a/cspell.json b/cspell.json index ea4d61a2..477545c3 100644 --- a/cspell.json +++ b/cspell.json @@ -28,6 +28,7 @@ "nextjs", "Nodejitsu", "ntlm", + "pino", "proxied", "proxying", "rawbody", diff --git a/examples/browser-sync/index.js b/examples/browser-sync/index.js index 2342b3bd..f5171202 100644 --- a/examples/browser-sync/index.js +++ b/examples/browser-sync/index.js @@ -11,7 +11,7 @@ const jsonPlaceholderProxy = createProxyMiddleware({ target: 'http://jsonplaceholder.typicode.com', pathFilter: '/users', changeOrigin: true, // for vhosted sites, changes host header to match to target's host - logLevel: 'debug', + logger: console, }); /** diff --git a/examples/express/index.js b/examples/express/index.js index 1070ce26..7ac72467 100644 --- a/examples/express/index.js +++ b/examples/express/index.js @@ -11,6 +11,7 @@ const jsonPlaceholderProxy = createProxyMiddleware({ target: 'http://jsonplaceholder.typicode.com/users', changeOrigin: true, // for vhosted sites, changes host header to match to target's host logLevel: 'debug', + logger: console, }); const app = express(); diff --git a/examples/response-interceptor/index.js b/examples/response-interceptor/index.js index 024da92b..4ee5a0c0 100644 --- a/examples/response-interceptor/index.js +++ b/examples/response-interceptor/index.js @@ -64,7 +64,7 @@ const jsonPlaceholderProxy = createProxyMiddleware({ return JSON.stringify(favoriteFoods); }), }, - logLevel: 'debug', + logger: console, }); const app = express(); diff --git a/examples/websocket/index.js b/examples/websocket/index.js index 061f88ae..3d7b7c08 100644 --- a/examples/websocket/index.js +++ b/examples/websocket/index.js @@ -15,7 +15,7 @@ const wsProxy = createProxyMiddleware({ // }, changeOrigin: true, // for vhosted sites, changes host header to match to target's host ws: true, // enable websocket proxy - logLevel: 'debug', + logger: console, }); const app = express(); diff --git a/recipes/logLevel.md b/recipes/logLevel.md index b310a8f5..cd543171 100644 --- a/recipes/logLevel.md +++ b/recipes/logLevel.md @@ -1,41 +1,5 @@ -# Log Level +# [BREAKING CHANGE] -Control the amount of logging of http-proxy-middleware. +This functionality is removed in v3. -Possible values: - -- `debug` -- `info` -- `warn` (default) -- `error` -- `silent` - -## Level: debug - -Log everything. - -```javascript -const { createProxyMiddleware } = require('http-proxy-middleware'); - -const options = { - target: 'http://localhost:3000', - logLevel: 'debug', -}; - -const apiProxy = createProxyMiddleware(options); -``` - -## Level: silent - -Suppress all logging. - -```javascript -const { createProxyMiddleware } = require('http-proxy-middleware'); - -const options = { - target: 'http://localhost:3000', - logLevel: 'silent', -}; - -const apiProxy = createProxyMiddleware(options); -``` +See [logger.md](logger.md) for logging in v3. diff --git a/recipes/logProvider.md b/recipes/logProvider.md index adbe1fdd..cd543171 100644 --- a/recipes/logProvider.md +++ b/recipes/logProvider.md @@ -1,76 +1,5 @@ -# Log Provider +# [BREAKING CHANGE] -Configure your own logger with the `logProvider` option. +This functionality is removed in v3. -In this example [winston](https://www.npmjs.com/package/winston) is configured to do the actual logging. - -```javascript -const winston = require('winston'); -const { createProxyMiddleware } = require('http-proxy-middleware'); - -const options = { - target: 'http://localhost:3000', - logProvider: function (provider) { - return winston; - }, -}; - -const apiProxy = createProxyMiddleware(options); -``` - -## Winston - -Configure your own logger with the `logProvider` option. - -In this example [winston](https://www.npmjs.com/package/winston) is configured to do the actual logging. Map the logging api if needed. - -```javascript -const winston = require('winston'); -const { createProxyMiddleware } = require('http-proxy-middleware'); - -const logProvider = function (provider) { - return { - log: winston.log, - debug: winston.debug, - info: winston.info, - warn: winston.warn, - error: winston.error, - }; -}; - -const options = { - target: 'http://localhost:3000', - logProvider: logProvider, -}; - -const apiProxy = createProxyMiddleware(options); -``` - -# Winston Multi Transport - -Configure your own logger with the `logProvider` option. - -In this example [winston](https://www.npmjs.com/package/winston) is configured to do the actual logging. - -```javascript -const winston = require('winston'); -const { createProxyMiddleware } = require('http-proxy-middleware'); - -const logProvider = function (provider) { - const logger = new winston.Logger({ - transports: [ - new winston.transports.Console(), - new winston.transports.File({ filename: 'some-file.log' }), - ], - }); - - return logger; -}; - -const options = { - target: 'http://localhost:3000', - logProvider: logProvider, -}; - -const apiProxy = createProxyMiddleware(options); -``` +See [logger.md](logger.md) for logging in v3. diff --git a/recipes/logger.md b/recipes/logger.md new file mode 100644 index 00000000..28a16d92 --- /dev/null +++ b/recipes/logger.md @@ -0,0 +1,87 @@ +# Logger + +Configure a logger to output information from http-proxy-middleware: ie. `console`, `winston`, `pino`, `bunyan`, `log4js`, etc... + +## `console` + +```javascript +const { createProxyMiddleware } = require('http-proxy-middleware'); + +const proxy = createProxyMiddleware({ + target: 'http://localhost:3000', + logger: console, +}); +``` + +## `winston` + + ![GitHub Repo stars](https://img.shields.io/github/stars/winstonjs/winston?style=social) ![winston downloads](https://img.shields.io/npm/dm/winston) + +```javascript +const { createProxyMiddleware } = require('http-proxy-middleware'); +const winston = require('winston'); +const { format, transports } = require('winston'); + +// Enable interpolation in log messages +// https://github.com/winstonjs/winston#string-interpolation +const logger = winston.createLogger({ + format: format.combine(format.splat(), format.simple()), + transports: [new transports.Console()], +}); + +const proxy = createProxyMiddleware({ + target: 'http://localhost:3000', + logger, +}); +``` + +## `pino` + + ![GitHub Repo stars](https://img.shields.io/github/stars/pinojs/pino?style=social) ![winston downloads](https://img.shields.io/npm/dm/pino) + +```javascript +const { createProxyMiddleware } = require('http-proxy-middleware'); +const pino = require('pino'); + +const logger = pino(); + +const proxy = createProxyMiddleware({ + target: 'http://localhost:3000', + logger, +}); +``` + +## `log4js` + + ![GitHub Repo stars](https://img.shields.io/github/stars/log4js-node/log4js-node?style=social) ![winston downloads](https://img.shields.io/npm/dm/log4js) + +```javascript +const { createProxyMiddleware } = require('http-proxy-middleware'); +const log4js = require('log4js'); + +const logger = log4js.getLogger(); +logger.level = 'debug'; + +const proxy = createProxyMiddleware({ + target: 'http://localhost:3000', + logger, +}); +``` + +## `bunyan` + + ![GitHub Repo stars](https://img.shields.io/github/stars/trentm/node-bunyan?style=social) ![winston downloads](https://img.shields.io/npm/dm/bunyan) + +```javascript +const { createProxyMiddleware } = require('http-proxy-middleware'); +const bunyan = require('bunyan'); + +const logger = bunyan.createLogger({ + name: 'my-app', +}); + +const proxy = createProxyMiddleware({ + target: 'http://localhost:3000', + logger, +}); +``` diff --git a/src/configuration.ts b/src/configuration.ts index 54c55a08..64cb9ac0 100644 --- a/src/configuration.ts +++ b/src/configuration.ts @@ -1,23 +1,8 @@ import { ERRORS } from './errors'; -import { getInstance } from './logger'; import { Options } from './types'; -const logger = getInstance(); - export function verifyConfig(options: Options): void { - configureLogger(options); - if (!options.target && !options.router) { throw new Error(ERRORS.ERR_CONFIG_FACTORY_TARGET_MISSING); } } - -function configureLogger(options: Options): void { - if (options.logLevel) { - logger.setLevel(options.logLevel); - } - - if (options.logProvider) { - logger.setProvider(options.logProvider); - } -} diff --git a/src/http-proxy-middleware.ts b/src/http-proxy-middleware.ts index 0d273184..bea6c99d 100644 --- a/src/http-proxy-middleware.ts +++ b/src/http-proxy-middleware.ts @@ -1,20 +1,20 @@ import type * as https from 'https'; -import type { Request, RequestHandler, Options, Filter } from './types'; +import type { Request, RequestHandler, Options, Filter, Logger } from './types'; import * as httpProxy from 'http-proxy'; import { verifyConfig } from './configuration'; import { matchPathFilter } from './path-filter'; -import { getArrow, getInstance } from './logger'; +import { getLogger } from './logger'; import * as PathRewriter from './path-rewriter'; import * as Router from './router'; import { debugProxyErrorsPlugin, - createLoggerPlugin, + loggerPlugin, errorResponsePlugin, proxyEventsPlugin, } from './plugins/default'; export class HttpProxyMiddleware { - private logger = getInstance(); + private logger: Logger; private wsInternalSubscribed = false; private serverOnCloseSubscribed = false; private proxyOptions: Options; @@ -23,11 +23,12 @@ export class HttpProxyMiddleware { constructor(options: Options) { verifyConfig(options); + this.logger = getLogger(options); this.proxyOptions = options; // create proxy this.proxy = httpProxy.createProxyServer({}); - this.logger.info(`[HPM] Proxy created: ${options.pathFilter ?? '/'} -> ${options.target}`); + this.logger.info(`[HPM] Proxy created: %O`, options.target); this.registerPlugins(this.proxy, this.proxyOptions); @@ -83,7 +84,7 @@ export class HttpProxyMiddleware { const defaultPlugins = [ debugProxyErrorsPlugin, proxyEventsPlugin, - createLoggerPlugin(), + loggerPlugin, errorResponsePlugin, ]; const plugins = options.plugins ?? []; @@ -123,8 +124,6 @@ export class HttpProxyMiddleware { * @return {Object} proxy options */ private prepareProxyRequest = async (req: Request) => { - // store uri before it gets rewritten for logging - const originalPath = req.url; const newProxyOptions = Object.assign({}, this.proxyOptions); // Apply in order: @@ -133,23 +132,6 @@ export class HttpProxyMiddleware { await this.applyRouter(req, newProxyOptions); await this.applyPathRewrite(req, this.pathRewriter); - // debug logging for both http(s) and websockets - if (this.proxyOptions.logLevel === 'debug') { - const arrow = getArrow( - originalPath, - req.url, - this.proxyOptions.target, - newProxyOptions.target - ); - this.logger.debug( - '[HPM] %s %s %s %s', - req.method, - originalPath, - arrow, - newProxyOptions.target - ); - } - return newProxyOptions; }; @@ -161,7 +143,7 @@ export class HttpProxyMiddleware { newTarget = await Router.getTarget(req, options); if (newTarget) { - this.logger.debug('[HPM] Router new target: %s -> "%s"', options.target, newTarget); + this.logger.info('[HPM] Router new target: %s -> "%s"', options.target, newTarget); options.target = newTarget; } } diff --git a/src/logger.ts b/src/logger.ts index 846970b3..833fe63a 100644 --- a/src/logger.ts +++ b/src/logger.ts @@ -1,157 +1,26 @@ -/* eslint-disable prefer-rest-params */ +/* eslint-disable @typescript-eslint/no-empty-function */ -import * as util from 'util'; - -let loggerInstance; - -const defaultProvider = { - // tslint:disable: no-console - log: console.log, - debug: console.log, // use .log(); since console does not have .debug() - info: console.info, - warn: console.warn, - error: console.error, -}; - -// log level 'weight' -enum LEVELS { - debug = 10, - info = 20, - warn = 30, - error = 50, - silent = 80, -} - -export function getInstance() { - if (!loggerInstance) { - loggerInstance = new Logger(); - } - - return loggerInstance; -} - -class Logger { - public logLevel; - public provider; - - constructor() { - this.setLevel('info'); - this.setProvider(() => defaultProvider); - } - - // log will log messages, regardless of logLevels - public log() { - this.provider.log(this._interpolate.apply(null, arguments)); - } - - public debug() { - if (this._showLevel('debug')) { - this.provider.debug(this._interpolate.apply(null, arguments)); - } - } - - public info() { - if (this._showLevel('info')) { - this.provider.info(this._interpolate.apply(null, arguments)); - } - } - - public warn() { - if (this._showLevel('warn')) { - this.provider.warn(this._interpolate.apply(null, arguments)); - } - } - - public error() { - if (this._showLevel('error')) { - this.provider.error(this._interpolate.apply(null, arguments)); - } - } - - public setLevel(v) { - if (this.isValidLevel(v)) { - this.logLevel = v; - } - } - - public setProvider(fn) { - if (fn && this.isValidProvider(fn)) { - this.provider = fn(defaultProvider); - } - } - - public isValidProvider(fnProvider) { - const result = true; - - if (fnProvider && typeof fnProvider !== 'function') { - throw new Error('[HPM] Log provider config error. Expecting a function.'); - } - - return result; - } - - public isValidLevel(levelName) { - const validLevels = Object.keys(LEVELS); - const isValid = validLevels.includes(levelName); - - if (!isValid) { - throw new Error('[HPM] Log level error. Invalid logLevel.'); - } - - return isValid; - } - - /** - * Decide to log or not to log, based on the log levels 'weight' - * @param {String} showLevel [debug, info, warn, error, silent] - * @return {Boolean} - */ - private _showLevel(showLevel) { - let result = false; - const currentLogLevel = LEVELS[this.logLevel]; - - if (currentLogLevel && currentLogLevel <= LEVELS[showLevel]) { - result = true; - } - - return result; - } - - // make sure logged messages and its data are return interpolated - // make it possible for additional log data, such date/time or custom prefix. - private _interpolate(format, ...args) { - const result = util.format(format, ...args); - - return result; - } -} +import { Logger, Options } from './types'; /** - * -> normal proxy - * => router - * ~> pathRewrite - * ≈> router + pathRewrite + * Compatibility matrix + * + | Library | log | info | warn | error | \ | + |----------|:------|:-------|:------|:--------|:------------------| + | console | ✅ | ✅ | ✅ | ✅ | ✅ (%s %o %O) | + | bunyan | ❌ | ✅ | ✅ | ✅ | ✅ (%s %o %O) | + | pino | ❌ | ✅ | ✅ | ✅ | ✅ (%s %o %O) | + | winston | ❌ | ✅ | ✅ | ✅ | ✅ (%s %o %O)^1 | + | log4js | ❌ | ✅ | ✅ | ✅ | ✅ (%s %o %O) | * - * @param {String} originalPath - * @param {String} newPath - * @param {String} originalTarget - * @param {String} newTarget - * @return {String} + * ^1: https://github.com/winstonjs/winston#string-interpolation */ -export function getArrow(originalPath, newPath, originalTarget, newTarget) { - const arrow = ['>']; - const isNewTarget = originalTarget !== newTarget; // router - const isNewPath = originalPath !== newPath; // pathRewrite - - if (isNewPath && !isNewTarget) { - arrow.unshift('~'); - } else if (!isNewPath && isNewTarget) { - arrow.unshift('='); - } else if (isNewPath && isNewTarget) { - arrow.unshift('≈'); - } else { - arrow.unshift('-'); - } +const noopLogger: Logger = { + info: () => {}, + warn: () => {}, + error: () => {}, +}; - return arrow.join(''); +export function getLogger(options: Options): Logger { + return (options.logger as Logger) || noopLogger; } diff --git a/src/path-rewriter.ts b/src/path-rewriter.ts index 755e7a17..b56094bd 100644 --- a/src/path-rewriter.ts +++ b/src/path-rewriter.ts @@ -1,7 +1,8 @@ import isPlainObj = require('is-plain-obj'); import { ERRORS } from './errors'; -import { getInstance } from './logger'; -const logger = getInstance(); +import { debug } from './debug'; + +const log = debug.extend('path-rewriter'); /** * Create rewrite function, to cache parsed rewrite rules. @@ -30,7 +31,7 @@ export function createPathRewriter(rewriteConfig) { for (const rule of rulesCache) { if (rule.regex.test(path)) { result = result.replace(rule.regex, rule.value); - logger.debug('[HPM] Rewriting path from "%s" to "%s"', path, result); + log('rewriting path from "%s" to "%s"', path, result); break; } } @@ -60,7 +61,7 @@ function parsePathRewriteRules(rewriteConfig) { regex: new RegExp(key), value: rewriteConfig[key], }); - logger.info('[HPM] Proxy rewrite rule created: "%s" ~> "%s"', key, rewriteConfig[key]); + log('rewrite rule created: "%s" ~> "%s"', key, rewriteConfig[key]); } } diff --git a/src/plugins/default/logger-plugin.ts b/src/plugins/default/logger-plugin.ts index fdc37e2b..6a75db3e 100644 --- a/src/plugins/default/logger-plugin.ts +++ b/src/plugins/default/logger-plugin.ts @@ -1,25 +1,36 @@ import { Plugin } from '../../types'; +import { getLogger } from '../../logger'; -export function createLoggerPlugin(logger: Console = console): Plugin { - const loggerPlugin: Plugin = (proxyServer, options) => { - proxyServer.on('error', (err, req, res, target?) => { - const hostname = req?.headers?.host; - const requestHref = `${hostname}${req?.url}`; - const targetHref = `${(target as unknown as any)?.href}`; // target is undefined when websocket errors +export const loggerPlugin: Plugin = (proxyServer, options) => { + const logger = getLogger(options); - const errorMessage = '[HPM] Error occurred while proxying request %s to %s [%s] (%s)'; - const errReference = 'https://nodejs.org/api/errors.html#errors_common_system_errors'; // link to Node Common Systems Errors page + proxyServer.on('error', (err, req, res, target?) => { + const hostname = req?.headers?.host; + const requestHref = `${hostname}${req?.url}`; + const targetHref = `${(target as unknown as any)?.href}`; // target is undefined when websocket errors - logger.error(errorMessage, requestHref, targetHref, (err as any).code || err, errReference); - }); + const errorMessage = '[HPM] Error occurred while proxying request %s to %s [%s] (%s)'; + const errReference = 'https://nodejs.org/api/errors.html#errors_common_system_errors'; // link to Node Common Systems Errors page - /** - * When client closes WebSocket connection - */ - proxyServer.on('close', (req, proxySocket, proxyHead) => { - logger.log('[HPM] Client disconnected'); - }); - }; + logger.error(errorMessage, requestHref, targetHref, (err as any).code || err, errReference); + }); - return loggerPlugin; -} + /** + * Log request and response + * @example + * ```shell + * [HPM] GET /users/ -> http://jsonplaceholder.typicode.com/users/ [304] + * ``` + */ + proxyServer.on('proxyRes', (proxyRes: any, req: any, res) => { + const exchange = `[HPM] ${req.method} ${req.baseUrl}${req.path} -> ${proxyRes.req.protocol}//${proxyRes.req.host}${proxyRes.req.path} [${proxyRes.statusCode}]`; + logger.info(exchange); + }); + + /** + * When client closes WebSocket connection + */ + proxyServer.on('close', (req, proxySocket, proxyHead) => { + logger.info('[HPM] Client disconnected'); + }); +}; diff --git a/src/router.ts b/src/router.ts index d2e63505..5dcaf659 100644 --- a/src/router.ts +++ b/src/router.ts @@ -1,6 +1,7 @@ import isPlainObj = require('is-plain-obj'); -import { getInstance } from './logger'; -const logger = getInstance(); +import { debug } from './debug'; + +const log = debug.extend('router'); export async function getTarget(req, config) { let newTarget; @@ -27,14 +28,14 @@ function getTargetFromProxyTable(req, table) { if (hostAndPath.indexOf(key) > -1) { // match 'localhost:3000/api' result = table[key]; - logger.debug('[HPM] Router table match: "%s"', key); + log('router table match: "%s"', key); break; } } else { if (key === host) { // match 'localhost:3000' result = table[key]; - logger.debug('[HPM] Router table match: "%s"', host); + log('router table match: "%s"', host); break; } } diff --git a/src/types.ts b/src/types.ts index 9edaccf6..63441e03 100644 --- a/src/types.ts +++ b/src/types.ts @@ -34,6 +34,8 @@ export type OnProxyEvent = { econnreset?: httpProxy.EconnresetCallback; }; +export type Logger = Pick; + export interface Options extends httpProxy.ServerOptions { /** * Narrow down requests to proxy or not. @@ -79,18 +81,14 @@ export interface Options extends httpProxy.ServerOptions { | { [hostOrPath: string]: httpProxy.ServerOptions['target'] } | ((req: Request) => httpProxy.ServerOptions['target']) | ((req: Request) => Promise); - logLevel?: 'debug' | 'info' | 'warn' | 'error' | 'silent'; - logProvider?: LogProviderCallback; -} - -interface LogProvider { - log: Logger; - debug?: Logger; - info?: Logger; - warn?: Logger; - error?: Logger; + /** + * Log information from http-proxy-middleware + * @example + * ```js + * createProxyMiddleware({ + * logger: console + * }); + * ``` + */ + logger?: Logger | any; } - -type Logger = (...args: any[]) => void; - -export type LogProviderCallback = (provider: LogProvider) => LogProvider; diff --git a/test/e2e/express-router.spec.ts b/test/e2e/express-router.spec.ts index 225e4555..64ac70da 100644 --- a/test/e2e/express-router.spec.ts +++ b/test/e2e/express-router.spec.ts @@ -28,7 +28,6 @@ describe('Usage in Express', () => { */ const proxyConfig: Options = { changeOrigin: true, - logLevel: 'silent', target: 'http://jsonplaceholder.typicode.com', pathFilter: filter, }; diff --git a/test/e2e/http-proxy-middleware.spec.ts b/test/e2e/http-proxy-middleware.spec.ts index ff491b7f..9b2725eb 100644 --- a/test/e2e/http-proxy-middleware.spec.ts +++ b/test/e2e/http-proxy-middleware.spec.ts @@ -401,13 +401,15 @@ describe('E2E http-proxy-middleware', () => { }); }); - describe('option.logLevel & option.logProvider', () => { + describe('option.logger', () => { let logMessages: string[]; beforeEach(() => { logMessages = []; - const customLogger = (message: string) => { - logMessages.push(message); + const customLogger = { + info: (message: string) => logMessages.push(message), + warn: (message: string) => logMessages.push(message), + error: (message: string) => logMessages.push(message), }; agent = request( @@ -415,10 +417,7 @@ describe('E2E http-proxy-middleware', () => { createProxyMiddleware({ target: `http://localhost:${mockTargetServer.port}`, pathFilter: '/api', - logLevel: 'info', - logProvider(provider) { - return { ...provider, debug: customLogger, info: customLogger }; - }, + logger: customLogger, }) ) ); @@ -429,9 +428,10 @@ describe('E2E http-proxy-middleware', () => { await agent.get(`/api/foo/bar`).expect(200); expect(logMessages).not.toBeUndefined(); - expect(logMessages.length).toBe(2); + expect(logMessages.length).toBe(3); expect(logMessages[0]).toContain('[HPM] Proxy created:'); - expect(logMessages[1]).toBe('[HPM] server close signal received: closing proxy server'); + expect(logMessages[1]).toBe('[HPM] GET /api/foo/bar -> http://localhost/api/foo/bar [200]'); + expect(logMessages[2]).toBe('[HPM] server close signal received: closing proxy server'); }); }); }); diff --git a/test/e2e/http-server.spec.ts b/test/e2e/http-server.spec.ts index 75dc480c..cfbf6dec 100644 --- a/test/e2e/http-server.spec.ts +++ b/test/e2e/http-server.spec.ts @@ -6,7 +6,6 @@ describe('http integration', () => { it('should work with raw node http RequestHandler', async () => { const handler = createProxyMiddleware({ changeOrigin: true, - logLevel: 'silent', target: 'http://httpbin.org', }); diff --git a/test/types.spec.ts b/test/types.spec.ts index c057a89c..0e644a5d 100644 --- a/test/types.spec.ts +++ b/test/types.spec.ts @@ -95,36 +95,9 @@ describe('http-proxy-middleware TypeScript Types', () => { }); }); - describe('logLevel', () => { - it('should have logLevel Type', () => { - options = { logLevel: 'info' }; - expect(options).toBeDefined(); - }); - }); - - describe('logProvider', () => { - it('should have logProvider Type', () => { - options = { - logProvider: (currentProvider) => { - return { - log: () => { - return; - }, - debug: () => { - return; - }, - info: () => { - return; - }, - warn: () => { - return; - }, - error: () => { - return; - }, - }; - }, - }; + describe('logger', () => { + it('should have logger option', () => { + options = { logger: console }; expect(options).toBeDefined(); }); }); diff --git a/test/unit/logger.spec.ts b/test/unit/logger.spec.ts index f70f7153..8b28be64 100644 --- a/test/unit/logger.spec.ts +++ b/test/unit/logger.spec.ts @@ -1,268 +1,19 @@ -import { getArrow, getInstance } from '../../src/logger'; +import { getLogger } from '../../src/logger'; describe('Logger', () => { - let logger; - let logMessage; - let debugMessage; - let infoMessage; - let warnMessage; - let errorMessage; - - beforeEach(() => { - logMessage = undefined; - debugMessage = undefined; - infoMessage = undefined; - warnMessage = undefined; - errorMessage = undefined; - }); - - beforeEach(() => { - logger = getInstance(); - }); - - beforeEach(() => { - logger.setProvider((provider) => { - provider.log = (message) => { - logMessage = message; - }; - provider.debug = (message) => { - debugMessage = message; - }; - provider.info = (message) => { - infoMessage = message; - }; - provider.warn = (message) => { - warnMessage = message; - }; - provider.error = (message) => { - errorMessage = message; - }; - - return provider; - }); - }); - - describe('logging with different levels', () => { - beforeEach(() => { - logger.log('log'); - logger.debug('debug'); - logger.info('info'); - logger.warn('warn'); - logger.error('error'); - }); - - describe('level: debug', () => { - beforeEach(() => { - logger.setLevel('debug'); - }); - - it('should log .log() messages', () => { - expect(logMessage).toBe('log'); - }); - it('should log .debug() messages', () => { - expect(debugMessage).toBe('debug'); - }); - it('should log .info() messages', () => { - expect(infoMessage).toBe('info'); - }); - it('should log .warn() messages', () => { - expect(warnMessage).toBe('warn'); - }); - it('should log .error() messages', () => { - expect(errorMessage).toBe('error'); - }); - }); - - describe('level: info', () => { - beforeEach(() => { - logger.setLevel('info'); - }); - - it('should log .log() messages', () => { - expect(logMessage).toBe('log'); - }); - it('should not log .debug() messages', () => { - expect(debugMessage).toBeUndefined(); - }); - it('should log .info() messages', () => { - expect(infoMessage).toBe('info'); - }); - it('should log .warn() messages', () => { - expect(warnMessage).toBe('warn'); - }); - it('should log .error() messages', () => { - expect(errorMessage).toBe('error'); - }); - }); - - describe('level: warn', () => { - beforeEach(() => { - logger.setLevel('warn'); - }); - - it('should log .log() messages', () => { - expect(logMessage).toBe('log'); - }); - it('should not log .debug() messages', () => { - expect(debugMessage).toBeUndefined(); - }); - it('should not log .info() messages', () => { - expect(infoMessage).toBeUndefined(); - }); - it('should log .warn() messages', () => { - expect(warnMessage).toBe('warn'); - }); - it('should log .error() messages', () => { - expect(errorMessage).toBe('error'); - }); - }); - - describe('level: error', () => { - beforeEach(() => { - logger.setLevel('error'); - }); - - it('should log .log() messages', () => { - expect(logMessage).toBe('log'); - }); - it('should not log .debug() messages', () => { - expect(debugMessage).toBeUndefined(); - }); - it('should not log .info() messages', () => { - expect(infoMessage).toBeUndefined(); - }); - it('should log .warn() messages', () => { - expect(warnMessage).toBeUndefined(); - }); - it('should log .error() messages', () => { - expect(errorMessage).toBe('error'); - }); - }); - - describe('level: silent', () => { - beforeEach(() => { - logger.setLevel('silent'); - }); - - it('should log .log() messages', () => { - expect(logMessage).toBe('log'); - }); - it('should not log .debug() messages', () => { - expect(debugMessage).toBeUndefined(); - }); - it('should not log .info() messages', () => { - expect(infoMessage).toBeUndefined(); - }); - it('should not log .warn() messages', () => { - expect(warnMessage).toBeUndefined(); - }); - it('should not log .error() messages', () => { - expect(errorMessage).toBeUndefined(); - }); - }); - - describe('Interpolation', () => { - // make sure all messages are logged - beforeEach(() => { - logger.setLevel('debug'); - }); - - beforeEach(() => { - logger.log('log %s %s', 123, 456); - logger.debug('debug %s %s', 123, 456); - logger.info('info %s %s', 123, 456); - logger.warn('warn %s %s', 123, 456); - logger.error('error %s %s', 123, 456); - }); - - it('should interpolate .log() messages', () => { - expect(logMessage).toBe('log 123 456'); - }); - it('should interpolate .debug() messages', () => { - expect(debugMessage).toBe('debug 123 456'); - }); - it('should interpolate .info() messages', () => { - expect(infoMessage).toBe('info 123 456'); - }); - it('should interpolate .warn() messages', () => { - expect(warnMessage).toBe('warn 123 456'); - }); - it('should interpolate .error() messages', () => { - expect(errorMessage).toBe('error 123 456'); - }); - }); - }); - - describe('Erroneous usage.', () => { - let fn; - - describe('Log provider is not a function', () => { - beforeEach(() => { - fn = () => { - logger.setProvider({}); - }; - }); - - it('should throw an error', () => { - expect(fn).toThrowError(Error); - }); - }); - - describe('Invalid logLevel', () => { - beforeEach(() => { - fn = () => { - logger.setLevel('foo'); - }; - }); - - it('should throw an error', () => { - expect(fn).toThrowError(Error); - }); - }); - }); -}); - -describe('getArrow', () => { - let arrow; - // scenario = [originalPath, newPath, originalTarget, newTarget] - - describe('default arrow', () => { - beforeEach(() => { - arrow = getArrow('/api', '/api', 'localhost:1337', 'localhost:1337'); - }); - - it('should return arrow: "->"', () => { - expect(arrow).toBe('->'); - }); - }); - - describe('"pathRewrite" arrow', () => { - beforeEach(() => { - arrow = getArrow('/api', '/rest', 'localhost:1337', 'localhost:1337'); - }); - - it('should return arrow: "~>"', () => { - expect(arrow).toBe('~>'); - }); - }); - - describe('"router" arrow', () => { - beforeEach(() => { - arrow = getArrow('/api', '/api', 'localhost:1337', 'localhost:8888'); - }); - - it('should return arrow: "=>"', () => { - expect(arrow).toBe('=>'); - }); - }); - - describe('"pathRewrite" + "router" arrow', () => { - beforeEach(() => { - arrow = getArrow('/api', '/rest', 'localhost:1337', 'localhost:8888'); - }); - - it('should return arrow: "≈>"', () => { - expect(arrow).toBe('≈>'); - }); + it('should return global "console" logger when configured in Options', () => { + const logger = getLogger({ logger: console }); + expect(logger).toBe(console); + }); + + it('should return noop logger when not configured in Options', () => { + const logger = getLogger({}); + expect(Object.keys(logger)).toMatchInlineSnapshot(` + Array [ + "info", + "warn", + "error", + ] + `); }); });