fix: ensure errors returned from tasks are properly logged (#11443)

Fixes https://github.com/payloadcms/payload/issues/9767

We allow failing a job queue task by returning `{ state: 'failed' }` from the task, instead of throwing an error. However, previously, this threw an error when trying to update the task in the database. Additionally, it was not possible to customize the error message.

This PR fixes that by letting you return `errorMessage` alongside `{ state: 'failed' }`, and by ensuring the error is transformed into proper json before saving it to the `error` column.
This commit is contained in:
Alessio Gravili
2025-02-28 09:00:56 -07:00
committed by GitHub
parent dfddee2125
commit d53f166476
4 changed files with 160 additions and 40 deletions

View File

@@ -323,6 +323,44 @@ export default buildConfigWithDefaults({
],
handler: path.resolve(dirname, 'runners/externalTask.ts') + '#externalTaskHandler',
} as TaskConfig<'ExternalTask'>,
{
retries: 0,
slug: 'ThrowError',
inputSchema: [],
outputSchema: [],
handler: () => {
throw new Error('failed')
},
} as TaskConfig<'ThrowError'>,
{
retries: 0,
slug: 'ReturnError',
inputSchema: [],
outputSchema: [],
handler: () => {
return {
state: 'failed',
}
},
} as TaskConfig<'ReturnError'>,
{
retries: 0,
slug: 'ReturnCustomError',
inputSchema: [
{
name: 'errorMessage',
type: 'text',
required: true,
},
],
outputSchema: [],
handler: ({ input }) => {
return {
state: 'failed',
errorMessage: input.errorMessage,
}
},
} as TaskConfig<'ReturnCustomError'>,
],
workflows: [
updatePostWorkflow,

View File

@@ -1128,4 +1128,69 @@ describe('Queues', () => {
// @ts-expect-error
expect(jobAfterRun.input.amountTask1Retried).toBe(0)
})
it('can tasks throw error', async () => {
payload.config.jobs.deleteJobOnComplete = false
const job = await payload.jobs.queue({
task: 'ThrowError',
input: {},
})
await payload.jobs.run()
const jobAfterRun = await payload.findByID({
collection: 'payload-jobs',
id: job.id,
})
expect(jobAfterRun.hasError).toBe(true)
expect(jobAfterRun.log?.length).toBe(1)
expect(jobAfterRun.log[0].error.message).toBe('failed')
expect(jobAfterRun.log[0].state).toBe('failed')
})
it('can tasks return error', async () => {
payload.config.jobs.deleteJobOnComplete = false
const job = await payload.jobs.queue({
task: 'ReturnError',
input: {},
})
await payload.jobs.run()
const jobAfterRun = await payload.findByID({
collection: 'payload-jobs',
id: job.id,
})
expect(jobAfterRun.hasError).toBe(true)
expect(jobAfterRun.log?.length).toBe(1)
expect(jobAfterRun.log[0].error.message).toBe('failed')
expect(jobAfterRun.log[0].state).toBe('failed')
})
it('can tasks return error with custom error message', async () => {
payload.config.jobs.deleteJobOnComplete = false
const job = await payload.jobs.queue({
task: 'ReturnCustomError',
input: {
errorMessage: 'custom error message',
},
})
await payload.jobs.run()
const jobAfterRun = await payload.findByID({
collection: 'payload-jobs',
id: job.id,
})
expect(jobAfterRun.hasError).toBe(true)
expect(jobAfterRun.log?.length).toBe(1)
expect(jobAfterRun.log[0].error.message).toBe('custom error message')
expect(jobAfterRun.log[0].state).toBe('failed')
})
})