UNPKG

3.49 kBJavaScriptView Raw
1import { jsonSafeParse, jsonSafeStringify } from '@middy/util';
2const defaults = {
3 logger: console.log,
4 awsContext: false,
5 omitPaths: [],
6 mask: undefined,
7 replacer: undefined
8};
9const inputOutputLoggerMiddleware = (opts = {})=>{
10 const { logger, awsContext, omitPaths, mask, replacer } = {
11 ...defaults,
12 ...opts
13 };
14 if (typeof logger !== 'function') {
15 throw new Error('[input-output-logger-middleware]: logger must be a function');
16 }
17 const omitPathTree = buildPathTree(omitPaths);
18 const omitAndLog = (param, request)=>{
19 const message = {
20 [param]: request[param]
21 };
22 if (awsContext) {
23 message.context = pick(request.context, awsContextKeys);
24 }
25 const cloneMessage = jsonSafeParse(jsonSafeStringify(message, replacer)) // Full clone to prevent nested mutations
26 ;
27 omit(cloneMessage, {
28 [param]: omitPathTree[param]
29 });
30 logger(cloneMessage);
31 };
32 const omit = (obj, pathTree = {})=>{
33 if (Array.isArray(obj) && pathTree['[]']) {
34 for (const value of obj){
35 omit(value, pathTree['[]']);
36 }
37 } else if (isObject(obj)) {
38 for(const key in pathTree){
39 if (pathTree[key] === true) {
40 if (mask) {
41 obj[key] = mask;
42 } else {
43 delete obj[key];
44 }
45 } else {
46 omit(obj[key], pathTree[key]);
47 }
48 }
49 }
50 };
51 const inputOutputLoggerMiddlewareBefore = async (request)=>omitAndLog('event', request);
52 const inputOutputLoggerMiddlewareAfter = async (request)=>omitAndLog('response', request);
53 const inputOutputLoggerMiddlewareOnError = async (request)=>{
54 if (request.response === undefined) return;
55 omitAndLog('response', request);
56 };
57 return {
58 before: inputOutputLoggerMiddlewareBefore,
59 after: inputOutputLoggerMiddlewareAfter,
60 onError: inputOutputLoggerMiddlewareOnError
61 };
62};
63// https://docs.aws.amazon.com/lambda/latest/dg/nodejs-context.html
64const awsContextKeys = [
65 'functionName',
66 'functionVersion',
67 'invokedFunctionArn',
68 'memoryLimitInMB',
69 'awsRequestId',
70 'logGroupName',
71 'logStreamName',
72 'identity',
73 'clientContext',
74 'callbackWaitsForEmptyEventLoop'
75];
76// move to util, if ever used elsewhere
77const pick = (originalObject = {}, keysToPick = [])=>{
78 const newObject = {};
79 for (const path of keysToPick){
80 // only supports first level
81 if (originalObject[path] !== undefined) {
82 newObject[path] = originalObject[path];
83 }
84 }
85 return newObject;
86};
87const buildPathTree = (paths)=>{
88 const tree = {};
89 for (let path of paths.sort().reverse()){
90 // reverse to ensure conflicting paths don't cause issues
91 if (!Array.isArray(path)) path = path.split('.');
92 if (path.includes('__proto__')) continue;
93 path.slice(0) // clone
94 .reduce((a, b, idx)=>{
95 if (idx < path.length - 1) {
96 a[b] ??= {};
97 return a[b];
98 }
99 a[b] = true;
100 return true;
101 }, tree);
102 }
103 return tree;
104};
105const isObject = (value)=>value && typeof value === 'object' && value.constructor === Object;
106export default inputOutputLoggerMiddleware;
107