UNPKG

@workablehr/riviere

Version:

log inbound/outbound HTTP traffic

177 lines (148 loc) 4.53 kB
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 };