Skip to content

Commit d6f0d17

Browse files
authored
feat(debug): improve troubleshooting (#752)
* feat(debug): improve troubleshooting * fix(logger-plugin): improve log output
1 parent 1338ea0 commit d6f0d17

File tree

16 files changed

+123
-55
lines changed

16 files changed

+123
-55
lines changed

README.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ _All_ `http-proxy` [options](https://github.com/nodejitsu/node-http-proxy#option
8181
- [External WebSocket upgrade](#external-websocket-upgrade)
8282
- [Intercept and manipulate requests](#intercept-and-manipulate-requests)
8383
- [Intercept and manipulate responses](#intercept-and-manipulate-responses)
84+
- [Debugging](#debugging)
8485
- [Working examples](#working-examples)
8586
- [Recipes](#recipes)
8687
- [Compatible servers](#compatible-servers)
@@ -546,6 +547,19 @@ const proxy = createProxyMiddleware({
546547

547548
Check out [interception recipes](https://github.com/chimurai/http-proxy-middleware/blob/master/recipes/response-interceptor.md#readme) for more examples.
548549

550+
## Debugging
551+
552+
Configure the `DEBUG` environment variable enable debug logging.
553+
554+
See [`debug`](https://github.com/debug-js/debug#readme) project for more options.
555+
556+
```shell
557+
DEBUG=http-proxy-middleware* node server.js
558+
559+
$ http-proxy-middleware proxy created +0ms
560+
$ http-proxy-middleware proxying request to target: 'http://www.example.org' +359ms
561+
```
562+
549563
## Working examples
550564

551565
View and play around with [working examples](https://github.com/chimurai/http-proxy-middleware/tree/master/examples).

examples/browser-sync/index.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,3 +28,6 @@ browserSync.init({
2828

2929
console.log('[DEMO] Server: listening on port 3000');
3030
console.log('[DEMO] Opening: http://localhost:3000/users');
31+
32+
process.on('SIGINT', () => browserSync.exit());
33+
process.on('SIGTERM', () => browserSync.exit());

examples/connect/index.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,12 @@ const app = connect();
2121
*/
2222
app.use('/users', jsonPlaceholderProxy);
2323

24-
http.createServer(app).listen(3000);
24+
const server = http.createServer(app).listen(3000);
2525

2626
console.log('[DEMO] Server: listening on port 3000');
2727
console.log('[DEMO] Opening: http://localhost:3000/users');
2828

2929
require('open')('http://localhost:3000/users');
30+
31+
process.on('SIGINT', () => server.close());
32+
process.on('SIGTERM', () => server.close());

examples/express/index.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,12 @@ const app = express();
2121
*/
2222
app.use('/users', jsonPlaceholderProxy);
2323

24-
app.listen(3000);
24+
const server = app.listen(3000);
2525

2626
console.log('[DEMO] Server: listening on port 3000');
2727
console.log('[DEMO] Opening: http://localhost:3000/users');
2828

2929
require('open')('http://localhost:3000/users');
30+
31+
process.on('SIGINT', () => server.close());
32+
process.on('SIGTERM', () => server.close());

examples/response-interceptor/index.js

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -47,20 +47,16 @@ const jsonPlaceholderProxy = createProxyMiddleware({
4747
selfHandleResponse: true, // manually call res.end(); IMPORTANT: res.end() is called internally by responseInterceptor()
4848
on: {
4949
proxyRes: responseInterceptor(async (buffer, proxyRes, req, res) => {
50-
// log original request and proxied request info
51-
const exchange = `[DEBUG] ${req.method} ${req.path} -> ${proxyRes.req.protocol}//${proxyRes.req.host}${proxyRes.req.path} [${proxyRes.statusCode}]`;
52-
console.log(exchange);
53-
5450
// log original response
5551
// console.log(`[DEBUG] original response:\n${buffer.toString('utf8')}`);
5652

57-
// set response content-type
53+
console.log('change response content-type');
5854
res.setHeader('content-type', 'application/json; charset=utf-8');
5955

60-
// set response status code
56+
console.log('change response status code');
6157
res.statusCode = 418;
6258

63-
// return a complete different response
59+
console.log('return a complete different response');
6460
return JSON.stringify(favoriteFoods);
6561
}),
6662
},
@@ -74,7 +70,7 @@ const app = express();
7470
*/
7571
app.use(jsonPlaceholderProxy);
7672

77-
app.listen(3000);
73+
const server = app.listen(3000);
7874

7975
console.log('[DEMO] Server: listening on port 3000');
8076
console.log('[DEMO] Open: http://localhost:3000/users');
@@ -83,3 +79,6 @@ console.log('[DEMO] Open: http://localhost:3000/gzip');
8379
console.log('[DEMO] Open: http://localhost:3000/deflate');
8480

8581
require('open')('http://localhost:3000/users');
82+
83+
process.on('SIGINT', () => server.close());
84+
process.on('SIGTERM', () => server.close());

src/debug.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import * as createDebug from 'debug';
22

33
/**
4-
* Debug instance with the given namespace: hpm
4+
* Debug instance with the given namespace: http-proxy-middleware
55
*/
6-
export const debug = createDebug('hpm');
6+
export const Debug = createDebug('http-proxy-middleware');

src/handlers/response-interceptor.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
import type * as http from 'http';
22
import * as zlib from 'zlib';
3+
import { Debug } from '../debug';
4+
import { getFunctionName } from '../utils/function';
5+
6+
const debug = Debug.extend('response-interceptor');
37

48
type Interceptor = (
59
buffer: Buffer,
@@ -16,11 +20,12 @@ type Interceptor = (
1620
* NOTE: must set options.selfHandleResponse=true (prevent automatic call of res.end())
1721
*/
1822
export function responseInterceptor(interceptor: Interceptor) {
19-
return async function proxyRes(
23+
return async function proxyResResponseInterceptor(
2024
proxyRes: http.IncomingMessage,
2125
req: http.IncomingMessage,
2226
res: http.ServerResponse
2327
): Promise<void> {
28+
debug('intercept proxy response');
2429
const originalProxyRes = proxyRes;
2530
let buffer = Buffer.from('', 'utf8');
2631

@@ -35,11 +40,14 @@ export function responseInterceptor(interceptor: Interceptor) {
3540
copyHeaders(proxyRes, res);
3641

3742
// call interceptor with intercepted response (buffer)
43+
debug('call interceptor function: %s', getFunctionName(interceptor));
3844
const interceptedBuffer = Buffer.from(await interceptor(buffer, originalProxyRes, req, res));
3945

4046
// set correct content-length (with double byte character support)
47+
debug('set content-length: %s', Buffer.byteLength(interceptedBuffer, 'utf8'));
4148
res.setHeader('content-length', Buffer.byteLength(interceptedBuffer, 'utf8'));
4249

50+
debug('write intercepted response');
4351
res.write(interceptedBuffer);
4452
res.end();
4553
});
@@ -73,6 +81,7 @@ function decompress(proxyRes: http.IncomingMessage, contentEncoding: string) {
7381
}
7482

7583
if (decompress) {
84+
debug(`decompress proxy response with 'content-encoding': %s`, contentEncoding);
7685
_proxyRes.pipe(decompress);
7786
_proxyRes = decompress;
7887
}
@@ -85,6 +94,8 @@ function decompress(proxyRes: http.IncomingMessage, contentEncoding: string) {
8594
* https://github.com/apache/superset/blob/9773aba522e957ed9423045ca153219638a85d2f/superset-frontend/webpack.proxy-config.js#L78
8695
*/
8796
function copyHeaders(originalResponse, response) {
97+
debug('copy original response headers');
98+
8899
response.statusCode = originalResponse.statusCode;
89100
response.statusMessage = originalResponse.statusMessage;
90101

src/http-proxy-middleware.ts

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
import type * as https from 'https';
2-
import type { Request, RequestHandler, Options, Filter, Logger } from './types';
2+
import type { Request, RequestHandler, Options, Filter } from './types';
33
import * as httpProxy from 'http-proxy';
44
import { verifyConfig } from './configuration';
55
import { getPlugins } from './get-plugins';
66
import { matchPathFilter } from './path-filter';
7-
import { getLogger } from './logger';
87
import * as PathRewriter from './path-rewriter';
98
import * as Router from './router';
9+
import { Debug as debug } from './debug';
10+
import { getFunctionName } from './utils/function';
1011

1112
export class HttpProxyMiddleware {
12-
private logger: Logger;
1313
private wsInternalSubscribed = false;
1414
private serverOnCloseSubscribed = false;
1515
private proxyOptions: Options;
@@ -18,12 +18,10 @@ export class HttpProxyMiddleware {
1818

1919
constructor(options: Options) {
2020
verifyConfig(options);
21-
this.logger = getLogger(options);
2221
this.proxyOptions = options;
2322

24-
// create proxy
23+
debug(`create proxy server`);
2524
this.proxy = httpProxy.createProxyServer({});
26-
this.logger.info(`[HPM] Proxy created: %O`, options.target);
2725

2826
this.registerPlugins(this.proxy, this.proxyOptions);
2927

@@ -43,6 +41,7 @@ export class HttpProxyMiddleware {
4341
if (this.shouldProxy(this.proxyOptions.pathFilter, req)) {
4442
try {
4543
const activeProxyOptions = await this.prepareProxyRequest(req);
44+
debug(`proxy request to target: %O`, activeProxyOptions.target);
4645
this.proxy.web(req, res, activeProxyOptions);
4746
} catch (err) {
4847
next && next(err);
@@ -63,7 +62,7 @@ export class HttpProxyMiddleware {
6362

6463
if (server && !this.serverOnCloseSubscribed) {
6564
server.on('close', () => {
66-
this.logger.info('[HPM] server close signal received: closing proxy server');
65+
debug('server close signal received: closing proxy server');
6766
this.proxy.close();
6867
});
6968
this.serverOnCloseSubscribed = true;
@@ -77,11 +76,15 @@ export class HttpProxyMiddleware {
7776

7877
private registerPlugins(proxy: httpProxy, options: Options) {
7978
const plugins = getPlugins(options);
80-
plugins.forEach((plugin) => plugin(proxy, options));
79+
plugins.forEach((plugin) => {
80+
debug(`register plugin: "${getFunctionName(plugin)}"`);
81+
plugin(proxy, options);
82+
});
8183
}
8284

8385
private catchUpgradeRequest = (server: https.Server) => {
8486
if (!this.wsInternalSubscribed) {
87+
debug('subscribing to server upgrade event');
8588
server.on('upgrade', this.handleUpgrade);
8689
// prevent duplicate upgrade handling;
8790
// in case external upgrade is also configured
@@ -93,7 +96,7 @@ export class HttpProxyMiddleware {
9396
if (this.shouldProxy(this.proxyOptions.pathFilter, req)) {
9497
const activeProxyOptions = await this.prepareProxyRequest(req);
9598
this.proxy.ws(req, socket, head, activeProxyOptions);
96-
this.logger.info('[HPM] Upgrading to WebSocket');
99+
debug('server upgrade event received. Proxying WebSocket');
97100
}
98101
};
99102

@@ -132,7 +135,7 @@ export class HttpProxyMiddleware {
132135
newTarget = await Router.getTarget(req, options);
133136

134137
if (newTarget) {
135-
this.logger.info('[HPM] Router new target: %s -> "%s"', options.target, newTarget);
138+
debug('router new target: "%s"', newTarget);
136139
options.target = newTarget;
137140
}
138141
}
@@ -144,9 +147,10 @@ export class HttpProxyMiddleware {
144147
const path = await pathRewriter(req.url, req);
145148

146149
if (typeof path === 'string') {
150+
debug('pathRewrite new path: %s', req.url);
147151
req.url = path;
148152
} else {
149-
this.logger.info('[HPM] pathRewrite: No rewritten path found. (%s)', req.url);
153+
debug('pathRewrite: no rewritten path found: %s', req.url);
150154
}
151155
}
152156
};

src/path-rewriter.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import isPlainObj = require('is-plain-obj');
22
import { ERRORS } from './errors';
3-
import { debug } from './debug';
3+
import { Debug } from './debug';
44

5-
const log = debug.extend('path-rewriter');
5+
const debug = Debug.extend('path-rewriter');
66

77
/**
88
* Create rewrite function, to cache parsed rewrite rules.
@@ -31,7 +31,7 @@ export function createPathRewriter(rewriteConfig) {
3131
for (const rule of rulesCache) {
3232
if (rule.regex.test(path)) {
3333
result = result.replace(rule.regex, rule.value);
34-
log('rewriting path from "%s" to "%s"', path, result);
34+
debug('rewriting path from "%s" to "%s"', path, result);
3535
break;
3636
}
3737
}
@@ -56,12 +56,12 @@ function parsePathRewriteRules(rewriteConfig) {
5656
const rules = [];
5757

5858
if (isPlainObj(rewriteConfig)) {
59-
for (const [key] of Object.entries(rewriteConfig)) {
59+
for (const [key, value] of Object.entries(rewriteConfig)) {
6060
rules.push({
6161
regex: new RegExp(key),
62-
value: rewriteConfig[key],
62+
value: value,
6363
});
64-
log('rewrite rule created: "%s" ~> "%s"', key, rewriteConfig[key]);
64+
debug('rewrite rule created: "%s" ~> "%s"', key, value);
6565
}
6666
}
6767

src/plugins/default/debug-proxy-errors-plugin.ts

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
import { debug } from '../../debug';
1+
import { Debug } from '../../debug';
22
import { Plugin } from '../../types';
33

4-
const debugError = debug.extend('debug-proxy-errors-plugin');
4+
const debug = Debug.extend('debug-proxy-errors-plugin');
55

66
/**
77
* Subscribe to {@link https://www.npmjs.com/package/http-proxy#listening-for-proxy-events http-proxy error events} to prevent server from crashing.
@@ -13,12 +13,12 @@ export const debugProxyErrorsPlugin: Plugin = (proxyServer): void => {
1313
* Prevent server from crashing when http-proxy errors (uncaught errors)
1414
*/
1515
proxyServer.on('error', (error, req, res, target) => {
16-
debugError(`http-proxy error event: \n%O`, error);
16+
debug(`http-proxy error event: \n%O`, error);
1717
});
1818

1919
proxyServer.on('proxyReq', (proxyReq, req, socket) => {
2020
socket.on('error', (error) => {
21-
debugError('Socket error in proxyReq event: \n%O', error);
21+
debug('Socket error in proxyReq event: \n%O', error);
2222
});
2323
});
2424

@@ -30,7 +30,7 @@ export const debugProxyErrorsPlugin: Plugin = (proxyServer): void => {
3030
proxyServer.on('proxyRes', (proxyRes, req, res) => {
3131
res.on('close', () => {
3232
if (!res.writableEnded) {
33-
debugError('Destroying proxyRes in proxyRes close event');
33+
debug('Destroying proxyRes in proxyRes close event');
3434
proxyRes.destroy();
3535
}
3636
});
@@ -43,23 +43,23 @@ export const debugProxyErrorsPlugin: Plugin = (proxyServer): void => {
4343
*/
4444
proxyServer.on('proxyReqWs', (proxyReq, req, socket) => {
4545
socket.on('error', (error) => {
46-
debugError('Socket error in proxyReqWs event: \n%O', error);
46+
debug('Socket error in proxyReqWs event: \n%O', error);
4747
});
4848
});
4949

5050
proxyServer.on('open', (proxySocket) => {
5151
proxySocket.on('error', (error) => {
52-
debugError('Socket error in open event: \n%O', error);
52+
debug('Socket error in open event: \n%O', error);
5353
});
5454
});
5555

5656
proxyServer.on('close', (req, socket, head) => {
5757
socket.on('error', (error) => {
58-
debugError('Socket error in close event: \n%O', error);
58+
debug('Socket error in close event: \n%O', error);
5959
});
6060
});
6161

6262
proxyServer.on('econnreset', (error, req, res, target) => {
63-
debugError(`http-proxy econnreset event: \n%O`, error);
63+
debug(`http-proxy econnreset event: \n%O`, error);
6464
});
6565
};

src/plugins/default/logger-plugin.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,14 +23,23 @@ export const loggerPlugin: Plugin = (proxyServer, options) => {
2323
* ```
2424
*/
2525
proxyServer.on('proxyRes', (proxyRes: any, req: any, res) => {
26-
const exchange = `[HPM] ${req.method} ${req.baseUrl}${req.path} -> ${proxyRes.req.protocol}//${proxyRes.req.host}${proxyRes.req.path} [${proxyRes.statusCode}]`;
26+
// BrowserSync uses req.originalUrl
27+
const originalUrl = req.originalUrl ?? `${req.baseUrl}${req.path}`;
28+
const exchange = `[HPM] ${req.method} ${originalUrl} -> ${proxyRes.req.protocol}//${proxyRes.req.host}${proxyRes.req.path} [${proxyRes.statusCode}]`;
2729
logger.info(exchange);
2830
});
2931

32+
/**
33+
* When client opens WebSocket connection
34+
*/
35+
proxyServer.on('open', (socket) => {
36+
logger.info('[HPM] Client connected: %o', socket.address());
37+
});
38+
3039
/**
3140
* When client closes WebSocket connection
3241
*/
3342
proxyServer.on('close', (req, proxySocket, proxyHead) => {
34-
logger.info('[HPM] Client disconnected');
43+
logger.info('[HPM] Client disconnected: %o', proxySocket.address());
3544
});
3645
};

0 commit comments

Comments
 (0)