diff --git a/.changeset/wise-nails-hang.md b/.changeset/wise-nails-hang.md new file mode 100644 index 0000000000..cec8c1d397 --- /dev/null +++ b/.changeset/wise-nails-hang.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-tasks': patch +--- + +Add error logging when a background task throws an error rather than silently swallowing it. diff --git a/packages/backend-tasks/src/tasks/PluginTaskSchedulerImpl.ts b/packages/backend-tasks/src/tasks/PluginTaskSchedulerImpl.ts index e43d0c1be6..e57ec93c1c 100644 --- a/packages/backend-tasks/src/tasks/PluginTaskSchedulerImpl.ts +++ b/packages/backend-tasks/src/tasks/PluginTaskSchedulerImpl.ts @@ -57,7 +57,12 @@ export class PluginTaskSchedulerImpl implements PluginTaskScheduler { if (scope === 'global') { const knex = await this.databaseFactory(); - const worker = new TaskWorker(task.id, task.fn, knex, this.logger); + const worker = new TaskWorker( + task.id, + task.fn, + knex, + this.logger.child({ task: task.id }), + ); await worker.start( { diff --git a/packages/backend-tasks/src/tasks/TaskWorker.test.ts b/packages/backend-tasks/src/tasks/TaskWorker.test.ts index 14688d0f98..8a1be3e8a9 100644 --- a/packages/backend-tasks/src/tasks/TaskWorker.test.ts +++ b/packages/backend-tasks/src/tasks/TaskWorker.test.ts @@ -119,6 +119,31 @@ describe('TaskWorker', () => { 60_000, ); + it.each(databases.eachSupportedId())( + 'logs error when the task throws, %p', + async databaseId => { + const knex = await databases.init(databaseId); + await migrateBackendTasks(knex); + + jest.spyOn(logger, 'error'); + const fn = jest.fn().mockRejectedValue(new Error('failed')); + const settings: TaskSettingsV2 = { + version: 2, + initialDelayDuration: undefined, + cadence: '* * * * * *', + timeoutAfterDuration: Duration.fromMillis(60000).toISO(), + }; + const checkFrequency = Duration.fromObject({ milliseconds: 100 }); + const worker = new TaskWorker('task1', fn, knex, logger, checkFrequency); + worker.start(settings); + + await waitForExpect(() => { + expect(logger.error).toBeCalled(); + }); + }, + 60_000, + ); + it.each(databases.eachSupportedId())( 'runs tasks more than once even when the task throws, %p', async databaseId => { diff --git a/packages/backend-tasks/src/tasks/TaskWorker.ts b/packages/backend-tasks/src/tasks/TaskWorker.ts index 07e1cd90ec..121a717848 100644 --- a/packages/backend-tasks/src/tasks/TaskWorker.ts +++ b/packages/backend-tasks/src/tasks/TaskWorker.ts @@ -145,6 +145,7 @@ export class TaskWorker { await this.fn(taskAbortController.signal); taskAbortController.abort(); // releases resources } catch (e) { + this.logger.error(e); await this.tryReleaseTask(ticket, taskSettings); return { result: 'failed' }; } finally {