chore(eslint): payload logger usage (#8578)

Payload uses `pino` for a logger. When using the error logger
`payload.logger.error` it is possible to pass any number of arguments
like this: `payload.logger.error('Some error ocurred', err)`. However,
in this scenario, the full error will not be serialized by `pino`. It
must be passed as the `err` property inside of an object in order to be
properly serialized.

This rule ensures that a user is using this function call to properly serialize the error.
This commit is contained in:
Elliot DeNolf
2024-10-06 13:23:30 -07:00
committed by GitHub
parent d88e0617d6
commit 7aed0d7c2e
4 changed files with 167 additions and 1 deletions

View File

@@ -0,0 +1,89 @@
export const rule = {
meta: {
type: 'problem',
docs: {
description: 'Disallow improper usage of payload.logger.error',
recommended: 'error',
},
messages: {
improperUsage: 'Improper logger usage. Pass { msg, err } so full error stack is logged.',
wrongErrorField: 'Improper usage. Use { err } instead of { error }.',
wrongMessageField: 'Improper usage. Use { msg } instead of { message }.',
},
schema: [],
},
create(context) {
return {
CallExpression(node) {
const callee = node.callee
// Function to check if the expression ends with `payload.logger.error`
function isPayloadLoggerError(expression) {
return (
expression.type === 'MemberExpression' &&
expression.property.name === 'error' && // must be `.error`
expression.object.type === 'MemberExpression' &&
expression.object.property.name === 'logger' && // must be `.logger`
(expression.object.object.name === 'payload' || // handles just `payload`
(expression.object.object.type === 'MemberExpression' &&
expression.object.object.property.name === 'payload')) // handles `*.payload`
)
}
// Check if the function being called is `payload.logger.error` or `*.payload.logger.error`
if (isPayloadLoggerError(callee)) {
const args = node.arguments
// Case 1: Single string is passed as the argument
if (
args.length === 1 &&
args[0].type === 'Literal' &&
typeof args[0].value === 'string'
) {
return // Valid: single string argument
}
// Case 2: Object is passed as the first argument
if (args.length > 0 && args[0].type === 'ObjectExpression') {
const properties = args[0].properties
// Ensure no { error } key, only { err } is allowed
properties.forEach((prop) => {
if (prop.key.type === 'Identifier' && prop.key.name === 'error') {
context.report({
node: prop,
messageId: 'wrongErrorField',
})
}
// Ensure no { message } key, only { msg } is allowed
if (prop.key.type === 'Identifier' && prop.key.name === 'message') {
context.report({
node: prop,
messageId: 'wrongMessageField',
})
}
})
return // Valid object, checked for 'err'/'error' keys
}
// Case 3: Improper usage (string + error or additional err/error)
if (
args.length > 1 &&
args[0].type === 'Literal' &&
typeof args[0].value === 'string' &&
args[1].type === 'Identifier' &&
(args[1].name === 'err' || args[1].name === 'error')
) {
context.report({
node,
messageId: 'improperUsage',
})
}
}
},
}
},
}
export default rule