@middy/input-output-logger
Advanced tools
Comparing version
@@ -1,12 +0,12 @@ | ||
import middy from '@middy/core' | ||
import type middy from "@middy/core"; | ||
interface Options { | ||
logger?: (message: any) => void | ||
awsContext?: boolean | ||
omitPaths?: string[] | ||
mask?: string | ||
logger?: (message: any) => undefined; | ||
awsContext?: boolean; | ||
omitPaths?: string[]; | ||
mask?: string; | ||
} | ||
declare function inputOutputLogger (options?: Options): middy.MiddlewareObj | ||
declare function inputOutputLogger(options?: Options): middy.MiddlewareObj; | ||
export default inputOutputLogger | ||
export default inputOutputLogger; |
282
index.js
@@ -1,165 +0,165 @@ | ||
import { Transform } from 'node:stream' | ||
import { Transform } from "node:stream"; | ||
const defaults = { | ||
logger: (message) => { | ||
console.log(JSON.stringify(message)) | ||
}, | ||
awsContext: false, | ||
omitPaths: [], | ||
mask: undefined | ||
} | ||
logger: (message) => { | ||
console.log(JSON.stringify(message)); | ||
}, | ||
awsContext: false, | ||
omitPaths: [], | ||
mask: undefined, | ||
}; | ||
const inputOutputLoggerMiddleware = (opts = {}) => { | ||
const { logger, awsContext, omitPaths, mask } = { | ||
...defaults, | ||
...opts | ||
} | ||
const { logger, awsContext, omitPaths, mask } = { | ||
...defaults, | ||
...opts, | ||
}; | ||
if (typeof logger !== 'function') { | ||
throw new Error('logger must be a function', { | ||
cause: { | ||
package: '@middy/input-output-logger' | ||
} | ||
}) | ||
} | ||
if (typeof logger !== "function") { | ||
throw new Error("logger must be a function", { | ||
cause: { | ||
package: "@middy/input-output-logger", | ||
}, | ||
}); | ||
} | ||
const omitPathTree = buildPathTree(omitPaths) | ||
// needs `omitPathTree`, `logger` | ||
const omitAndLog = (param, request) => { | ||
const message = { [param]: request[param] } | ||
const omitPathTree = buildPathTree(omitPaths); | ||
// needs `omitPathTree`, `logger` | ||
const omitAndLog = (param, request) => { | ||
const message = { [param]: request[param] }; | ||
if (awsContext) { | ||
message.context = pick(request.context, awsContextKeys) | ||
} | ||
if (awsContext) { | ||
message.context = pick(request.context, awsContextKeys); | ||
} | ||
let cloneMessage = message | ||
if (omitPaths.length) { | ||
cloneMessage = structuredClone(message) // Full clone to prevent nested mutations | ||
omit(cloneMessage, { [param]: omitPathTree[param] }) | ||
} | ||
logger(cloneMessage) | ||
} | ||
let cloneMessage = message; | ||
if (omitPaths.length) { | ||
cloneMessage = structuredClone(message); // Full clone to prevent nested mutations | ||
omit(cloneMessage, { [param]: omitPathTree[param] }); | ||
} | ||
logger(cloneMessage); | ||
}; | ||
// needs `mask` | ||
const omit = (obj, pathTree = {}) => { | ||
if (Array.isArray(obj) && pathTree['[]']) { | ||
for (let i = 0, l = obj.length; i < l; i++) { | ||
omit(obj[i], pathTree['[]']) | ||
} | ||
} else if (isObject(obj)) { | ||
for (const key in pathTree) { | ||
if (pathTree[key] === true) { | ||
if (mask) { | ||
obj[key] = mask | ||
} else { | ||
delete obj[key] | ||
} | ||
} else { | ||
omit(obj[key], pathTree[key]) | ||
} | ||
} | ||
} | ||
} | ||
// needs `mask` | ||
const omit = (obj, pathTree = {}) => { | ||
if (Array.isArray(obj) && pathTree["[]"]) { | ||
for (let i = 0, l = obj.length; i < l; i++) { | ||
omit(obj[i], pathTree["[]"]); | ||
} | ||
} else if (isObject(obj)) { | ||
for (const key in pathTree) { | ||
if (pathTree[key] === true) { | ||
if (mask) { | ||
obj[key] = mask; | ||
} else { | ||
delete obj[key]; | ||
} | ||
} else { | ||
omit(obj[key], pathTree[key]); | ||
} | ||
} | ||
} | ||
}; | ||
const inputOutputLoggerMiddlewareBefore = async (request) => { | ||
omitAndLog('event', request) | ||
} | ||
const inputOutputLoggerMiddlewareAfter = async (request) => { | ||
if ( | ||
request.response?._readableState ?? | ||
request.response?.body?._readableState | ||
) { | ||
passThrough(request, omitAndLog) | ||
} else { | ||
omitAndLog('response', request) | ||
} | ||
} | ||
const inputOutputLoggerMiddlewareOnError = async (request) => { | ||
if (request.response === undefined) return | ||
await inputOutputLoggerMiddlewareAfter(request) | ||
} | ||
const inputOutputLoggerMiddlewareBefore = async (request) => { | ||
omitAndLog("event", request); | ||
}; | ||
const inputOutputLoggerMiddlewareAfter = async (request) => { | ||
if ( | ||
request.response?._readableState ?? | ||
request.response?.body?._readableState | ||
) { | ||
passThrough(request, omitAndLog); | ||
} else { | ||
omitAndLog("response", request); | ||
} | ||
}; | ||
const inputOutputLoggerMiddlewareOnError = async (request) => { | ||
if (request.response === undefined) return; | ||
await inputOutputLoggerMiddlewareAfter(request); | ||
}; | ||
return { | ||
before: inputOutputLoggerMiddlewareBefore, | ||
after: inputOutputLoggerMiddlewareAfter, | ||
onError: inputOutputLoggerMiddlewareOnError | ||
} | ||
} | ||
return { | ||
before: inputOutputLoggerMiddlewareBefore, | ||
after: inputOutputLoggerMiddlewareAfter, | ||
onError: inputOutputLoggerMiddlewareOnError, | ||
}; | ||
}; | ||
// https://docs.aws.amazon.com/lambda/latest/dg/nodejs-context.html | ||
const awsContextKeys = [ | ||
'functionName', | ||
'functionVersion', | ||
'invokedFunctionArn', | ||
'memoryLimitInMB', | ||
'awsRequestId', | ||
'logGroupName', | ||
'logStreamName', | ||
'identity', | ||
'clientContext', | ||
'callbackWaitsForEmptyEventLoop' | ||
] | ||
"functionName", | ||
"functionVersion", | ||
"invokedFunctionArn", | ||
"memoryLimitInMB", | ||
"awsRequestId", | ||
"logGroupName", | ||
"logStreamName", | ||
"identity", | ||
"clientContext", | ||
"callbackWaitsForEmptyEventLoop", | ||
]; | ||
// move to util, if ever used elsewhere | ||
const pick = (originalObject = {}, keysToPick = []) => { | ||
const newObject = {} | ||
for (const path of keysToPick) { | ||
// only supports first level | ||
if (originalObject[path] !== undefined) { | ||
newObject[path] = originalObject[path] | ||
} | ||
} | ||
return newObject | ||
} | ||
const newObject = {}; | ||
for (const path of keysToPick) { | ||
// only supports first level | ||
if (originalObject[path] !== undefined) { | ||
newObject[path] = originalObject[path]; | ||
} | ||
} | ||
return newObject; | ||
}; | ||
const isObject = (value) => | ||
value && typeof value === 'object' && value.constructor === Object | ||
value && typeof value === "object" && value.constructor === Object; | ||
const buildPathTree = (paths) => { | ||
const tree = {} | ||
for (let path of paths.sort().reverse()) { | ||
// reverse to ensure conflicting paths don't cause issues | ||
if (!Array.isArray(path)) path = path.split('.') | ||
if (path.includes('__proto__')) continue | ||
path | ||
.slice(0) // clone | ||
.reduce((a, b, idx) => { | ||
if (idx < path.length - 1) { | ||
a[b] ??= {} | ||
return a[b] | ||
} | ||
a[b] = true | ||
return true | ||
}, tree) | ||
} | ||
return tree | ||
} | ||
const tree = {}; | ||
for (let path of paths.sort().reverse()) { | ||
// reverse to ensure conflicting paths don't cause issues | ||
if (!Array.isArray(path)) path = path.split("."); | ||
if (path.includes("__proto__")) continue; | ||
path | ||
.slice(0) // clone | ||
.reduce((a, b, idx) => { | ||
if (idx < path.length - 1) { | ||
a[b] ??= {}; | ||
return a[b]; | ||
} | ||
a[b] = true; | ||
return true; | ||
}, tree); | ||
} | ||
return tree; | ||
}; | ||
const passThrough = (request, omitAndLog) => { | ||
// required because `core` remove body before `flush` is triggered | ||
const hasBody = request.response?.body | ||
let body = '' | ||
const listen = new Transform({ | ||
objectMode: false, | ||
transform (chunk, encoding, callback) { | ||
body += chunk | ||
this.push(chunk, encoding) | ||
callback() | ||
}, | ||
flush (callback) { | ||
if (hasBody) { | ||
omitAndLog('response', { response: { ...request.response, body } }) | ||
} else { | ||
omitAndLog('response', { response: body }) | ||
} | ||
callback() | ||
} | ||
}) | ||
if (hasBody) { | ||
request.response.body = request.response.body.pipe(listen) | ||
} else { | ||
request.response = request.response.pipe(listen) | ||
} | ||
} | ||
// required because `core` remove body before `flush` is triggered | ||
const hasBody = request.response?.body; | ||
let body = ""; | ||
const listen = new Transform({ | ||
objectMode: false, | ||
transform(chunk, encoding, callback) { | ||
body += chunk; | ||
this.push(chunk, encoding); | ||
callback(); | ||
}, | ||
flush(callback) { | ||
if (hasBody) { | ||
omitAndLog("response", { response: { ...request.response, body } }); | ||
} else { | ||
omitAndLog("response", { response: body }); | ||
} | ||
callback(); | ||
}, | ||
}); | ||
if (hasBody) { | ||
request.response.body = request.response.body.pipe(listen); | ||
} else { | ||
request.response = request.response.pipe(listen); | ||
} | ||
}; | ||
export default inputOutputLoggerMiddleware | ||
export default inputOutputLoggerMiddleware; |
137
package.json
{ | ||
"name": "@middy/input-output-logger", | ||
"version": "6.1.6", | ||
"description": "Input and output logger middleware for the middy framework", | ||
"type": "module", | ||
"engines": { | ||
"node": ">=20" | ||
}, | ||
"engineStrict": true, | ||
"publishConfig": { | ||
"access": "public" | ||
}, | ||
"module": "./index.js", | ||
"exports": { | ||
".": { | ||
"import": { | ||
"types": "./index.d.ts", | ||
"default": "./index.js" | ||
}, | ||
"require": { | ||
"default": "./index.js" | ||
} | ||
} | ||
}, | ||
"types": "index.d.ts", | ||
"files": [ | ||
"index.js", | ||
"index.d.ts" | ||
], | ||
"scripts": { | ||
"test": "npm run test:unit && npm run test:fuzz", | ||
"test:unit": "node --test __tests__/index.js", | ||
"test:fuzz": "node --test __tests__/fuzz.js", | ||
"test:benchmark": "node __benchmarks__/index.js" | ||
}, | ||
"license": "MIT", | ||
"keywords": [ | ||
"Lambda", | ||
"Middleware", | ||
"Serverless", | ||
"Framework", | ||
"AWS", | ||
"AWS Lambda", | ||
"Middy", | ||
"Input", | ||
"Output", | ||
"Logger" | ||
], | ||
"author": { | ||
"name": "Middy contributors", | ||
"url": "https://github.com/middyjs/middy/graphs/contributors" | ||
}, | ||
"repository": { | ||
"type": "git", | ||
"url": "git+https://github.com/middyjs/middy.git", | ||
"directory": "packages/input-output-logger" | ||
}, | ||
"bugs": { | ||
"url": "https://github.com/middyjs/middy/issues" | ||
}, | ||
"homepage": "https://middy.js.org", | ||
"funding": { | ||
"type": "github", | ||
"url": "https://github.com/sponsors/willfarrell" | ||
}, | ||
"devDependencies": { | ||
"@datastream/core": "0.0.40", | ||
"@middy/core": "6.1.6", | ||
"@types/node": "^20.0.0" | ||
}, | ||
"gitHead": "7a6c0fbb8ab71d6a2171e678697de9f237568431" | ||
"name": "@middy/input-output-logger", | ||
"version": "6.2.0", | ||
"description": "Input and output logger middleware for the middy framework", | ||
"type": "module", | ||
"engines": { | ||
"node": ">=20" | ||
}, | ||
"engineStrict": true, | ||
"publishConfig": { | ||
"access": "public" | ||
}, | ||
"module": "./index.js", | ||
"exports": { | ||
".": { | ||
"import": { | ||
"types": "./index.d.ts", | ||
"default": "./index.js" | ||
}, | ||
"require": { | ||
"default": "./index.js" | ||
} | ||
} | ||
}, | ||
"types": "index.d.ts", | ||
"files": ["index.js", "index.d.ts"], | ||
"scripts": { | ||
"test": "npm run test:unit && npm run test:fuzz", | ||
"test:unit": "node --test", | ||
"test:fuzz": "node --test index.fuzz.js", | ||
"test:perf": "node --test index.perf.js" | ||
}, | ||
"license": "MIT", | ||
"keywords": [ | ||
"Lambda", | ||
"Middleware", | ||
"Serverless", | ||
"Framework", | ||
"AWS", | ||
"AWS Lambda", | ||
"Middy", | ||
"Input", | ||
"Output", | ||
"Logger" | ||
], | ||
"author": { | ||
"name": "Middy contributors", | ||
"url": "https://github.com/middyjs/middy/graphs/contributors" | ||
}, | ||
"repository": { | ||
"type": "git", | ||
"url": "git+https://github.com/middyjs/middy.git", | ||
"directory": "packages/input-output-logger" | ||
}, | ||
"bugs": { | ||
"url": "https://github.com/middyjs/middy/issues" | ||
}, | ||
"homepage": "https://middy.js.org", | ||
"funding": { | ||
"type": "github", | ||
"url": "https://github.com/sponsors/willfarrell" | ||
}, | ||
"devDependencies": { | ||
"@datastream/core": "0.0.40", | ||
"@middy/core": "6.2.0", | ||
"@types/node": "^20.0.0" | ||
}, | ||
"gitHead": "7a6c0fbb8ab71d6a2171e678697de9f237568431" | ||
} |
70578
5.21%226
0.44%6
20%7
16.67%27
3.85%8654
-4.09%