1 | import { jsonSafeParse, jsonSafeStringify } from '@middy/util';
|
2 | const defaults = {
|
3 | logger: console.log,
|
4 | awsContext: false,
|
5 | omitPaths: [],
|
6 | mask: undefined,
|
7 | replacer: undefined
|
8 | };
|
9 | const 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))
|
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 |
|
64 | const awsContextKeys = [
|
65 | 'functionName',
|
66 | 'functionVersion',
|
67 | 'invokedFunctionArn',
|
68 | 'memoryLimitInMB',
|
69 | 'awsRequestId',
|
70 | 'logGroupName',
|
71 | 'logStreamName',
|
72 | 'identity',
|
73 | 'clientContext',
|
74 | 'callbackWaitsForEmptyEventLoop'
|
75 | ];
|
76 |
|
77 | const pick = (originalObject = {}, keysToPick = [])=>{
|
78 | const newObject = {};
|
79 | for (const path of keysToPick){
|
80 |
|
81 | if (originalObject[path] !== undefined) {
|
82 | newObject[path] = originalObject[path];
|
83 | }
|
84 | }
|
85 | return newObject;
|
86 | };
|
87 | const buildPathTree = (paths)=>{
|
88 | const tree = {};
|
89 | for (let path of paths.sort().reverse()){
|
90 |
|
91 | if (!Array.isArray(path)) path = path.split('.');
|
92 | if (path.includes('__proto__')) continue;
|
93 | path.slice(0)
|
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 | };
|
105 | const isObject = (value)=>value && typeof value === 'object' && value.constructor === Object;
|
106 | export default inputOutputLoggerMiddleware;
|
107 |
|