UNPKG

4.17 kBJavaScriptView Raw
1import { Transform } from 'node:stream'
2
3const defaults = {
4 logger: console.log,
5 awsContext: false,
6 omitPaths: [],
7 mask: undefined,
8 replacer: undefined
9}
10
11const inputOutputLoggerMiddleware = (opts = {}) => {
12 const { logger, awsContext, omitPaths, mask, replacer } = {
13 ...defaults,
14 ...opts
15 }
16
17 if (typeof logger !== 'function') {
18 throw new Error('logger must be a function', {
19 cause: {
20 package: '@middy/input-output-logger'
21 }
22 })
23 }
24
25 const omitPathTree = buildPathTree(omitPaths)
26 // needs `omitPathTree`, `logger`
27 const omitAndLog = (param, request) => {
28 const message = { [param]: request[param] }
29
30 if (awsContext) {
31 message.context = pick(request.context, awsContextKeys)
32 }
33
34 let cloneMessage = message
35 if (omitPaths.length) {
36 cloneMessage = structuredClone(message, replacer) // Full clone to prevent nested mutations
37 omit(cloneMessage, { [param]: omitPathTree[param] })
38 }
39 logger(cloneMessage)
40 }
41
42 // needs `mask`
43 const omit = (obj, pathTree = {}) => {
44 if (Array.isArray(obj) && pathTree['[]']) {
45 for (let i = 0, l = obj.length; i < l; i++) {
46 omit(obj[i], pathTree['[]'])
47 }
48 } else if (isObject(obj)) {
49 for (const key in pathTree) {
50 if (pathTree[key] === true) {
51 if (mask) {
52 obj[key] = mask
53 } else {
54 delete obj[key]
55 }
56 } else {
57 omit(obj[key], pathTree[key])
58 }
59 }
60 }
61 }
62
63 const inputOutputLoggerMiddlewareBefore = async (request) => {
64 omitAndLog('event', request)
65 }
66 const inputOutputLoggerMiddlewareAfter = async (request) => {
67 if (
68 request.response?._readableState ??
69 request.response?.body?._readableState
70 ) {
71 passThrough(request, omitAndLog)
72 } else {
73 omitAndLog('response', request)
74 }
75 }
76 const inputOutputLoggerMiddlewareOnError = async (request) => {
77 if (request.response === undefined) return
78 inputOutputLoggerMiddlewareAfter(request)
79 }
80
81 return {
82 before: inputOutputLoggerMiddlewareBefore,
83 after: inputOutputLoggerMiddlewareAfter,
84 onError: inputOutputLoggerMiddlewareOnError
85 }
86}
87
88// https://docs.aws.amazon.com/lambda/latest/dg/nodejs-context.html
89const awsContextKeys = [
90 'functionName',
91 'functionVersion',
92 'invokedFunctionArn',
93 'memoryLimitInMB',
94 'awsRequestId',
95 'logGroupName',
96 'logStreamName',
97 'identity',
98 'clientContext',
99 'callbackWaitsForEmptyEventLoop'
100]
101
102// move to util, if ever used elsewhere
103const pick = (originalObject = {}, keysToPick = []) => {
104 const newObject = {}
105 for (const path of keysToPick) {
106 // only supports first level
107 if (originalObject[path] !== undefined) {
108 newObject[path] = originalObject[path]
109 }
110 }
111 return newObject
112}
113
114const isObject = (value) =>
115 value && typeof value === 'object' && value.constructor === Object
116
117const buildPathTree = (paths) => {
118 const tree = {}
119 for (let path of paths.sort().reverse()) {
120 // reverse to ensure conflicting paths don't cause issues
121 if (!Array.isArray(path)) path = path.split('.')
122 if (path.includes('__proto__')) continue
123 path
124 .slice(0) // clone
125 .reduce((a, b, idx) => {
126 if (idx < path.length - 1) {
127 a[b] ??= {}
128 return a[b]
129 }
130 a[b] = true
131 return true
132 }, tree)
133 }
134 return tree
135}
136
137const passThrough = (request, omitAndLog) => {
138 // required because `core` remove body before `flush` is triggered
139 const hasBody = request.response?.body
140 let body = ''
141 const listen = new Transform({
142 objectMode: false,
143 transform (chunk, encoding, callback) {
144 body += chunk
145 this.push(chunk, encoding)
146 callback()
147 },
148 flush (callback) {
149 if (hasBody) {
150 omitAndLog('response', { response: { ...request.response, body } })
151 } else {
152 omitAndLog('response', { response: body })
153 }
154 callback()
155 }
156 })
157 if (hasBody) {
158 request.response.body = request.response.body.pipe(listen)
159 } else {
160 request.response = request.response.pipe(listen)
161 }
162}
163
164export default inputOutputLoggerMiddleware