@workablehr/riviere
Version:
log inbound/outbound HTTP traffic
177 lines (148 loc) • 4.53 kB
JavaScript
const url = require('url');
const { randomUUID } = require('crypto');
const { mapError, mapInReq, mapInRes, mapOutReq, mapOutRes } = require('../transformers/transformers');
const context = (ctx, traceHeaderName) => {
return {
requestId: ctx.req.headers[traceHeaderName]
};
};
function requestProxy(options = {}) {
let { logger, level, traceHeaderName, opts = {} } = options;
traceHeaderName = traceHeaderName && traceHeaderName.toLowerCase();
const requestHandler = {
apply: (target, thisArg, argumentsList) => {
let reqObj;
let reqId;
try {
const requestOptions = argumentsList[0];
requestOptions.headers = requestOptions.headers || {};
if (!requestOptions.headers[traceHeaderName]) {
requestOptions.headers[traceHeaderName] = randomUUID();
}
reqId = requestOptions.headers[traceHeaderName];
reqObj = mapOutReq(requestOptions, reqId, opts);
const blacklistedPathRegex = opts.blacklistedPathRegex;
if (!reqObj.protocol || (blacklistedPathRegex && blacklistedPathRegex.test(reqObj.path))) {
// do not log custom requests (for example requests started by the newrelic agent)
return target.apply(thisArg, argumentsList);
}
if (opts.request && opts.request.enabled) {
if (shouldObfuscatePath(requestOptions.headers, opts)) {
reqObj.href = `${new URL(reqObj.href).origin}/***`;
reqObj.path = '/***';
requestOptions.pathname = '/***';
}
logger[level](reqObj, options);
}
} catch (err) {
logger.error(err);
return target.apply(thisArg, argumentsList);
}
const startedAt = new Date().getTime();
const req = target.apply(thisArg, argumentsList);
req.on('response', function() {
try {
const res = arguments[0];
const resObj = mapInRes(res, reqObj, startedAt, reqId, opts);
if (shouldObfuscatePath(reqObj.metaHeaders?.headers, opts)) {
resObj.path = '/***';
resObj.href = `${new URL(resObj.href).origin}/***`;
}
logger[level](resObj, options);
} catch (err) {
logger.error(err);
}
});
return req;
}
};
return requestHandler;
}
function shouldObfuscatePath(headers, options) {
return (
headers &&
Object.keys(headers)
.filter(h => h)
.includes(options.obfuscateHrefIfHeaderExists)
);
}
function extractLogCtx(ctx) {
const traceHeaderName = this.traceHeaderName.toLowerCase();
if (this.forceIds === true && !ctx.req.headers[traceHeaderName]) {
ctx.req.headers[traceHeaderName] = randomUUID();
}
ctx.logCtx = Object.assign(this.context(ctx), context(ctx, traceHeaderName));
const parsedUrl = ctx.request.req._parsedUrl || url.parse(ctx.request.req.url);
const path = (this.inbound.includeHost ? ctx.request.origin : '') + parsedUrl.pathname;
const query = parsedUrl.query;
Object.assign(ctx.logCtx, {
method: ctx.request.method.toUpperCase(),
protocol: ctx.request.protocol,
path,
query
});
}
function onInboundRequest({ ctx }) {
if (this.inbound.blacklistedPaths?.includes(ctx.request?.path)) {
return;
}
extractLogCtx.call(this, ctx);
const {
health,
bodyKeys,
bodyKeysRegex,
bodyKeysCallback,
headersRegex,
headerValueCallback,
inbound: { maxBodyValueChars }
} = this;
const transformedReq = mapInReq({
ctx,
health,
bodyKeys,
bodyKeysRegex,
bodyKeysCallback,
headersRegex,
headerValueCallback,
maxBodyValueChars
});
this.logger[this.inbound.level](transformedReq, this);
}
function onOutboundResponse({ ctx }) {
if (this.inbound.blacklistedPaths?.includes(ctx.request?.path)) {
return;
}
if (!ctx.logCtx) {
extractLogCtx.call(this, ctx);
}
const {
health,
bodyKeys,
bodyKeysRegex,
bodyKeysCallback,
headersRegex,
headerValueCallback,
outbound: { maxBodyValueChars }
} = this;
const transformedRes = mapOutRes({
ctx,
health,
bodyKeys,
bodyKeysRegex,
bodyKeysCallback,
headersRegex,
headerValueCallback,
maxBodyValueChars
});
this.logger[this.inbound.level](transformedRes, this);
}
function onError({ ctx, err }) {
const transformedErr = mapError({ ctx, err });
this.logger.error(transformedErr);
}
module.exports = {
onInboundRequest,
onOutboundResponse,
onError,
requestProxy
};