Compare commits

...

1 Commits

Author SHA1 Message Date
Sasha
f4e56b2c43 feat: consolidates error handling 2025-02-18 22:23:12 +02:00
44 changed files with 270 additions and 111 deletions

View File

@@ -2,7 +2,7 @@ import type { ConnectOptions } from 'mongoose'
import type { Connect } from 'payload'
import mongoose from 'mongoose'
import { defaultBeginTransaction } from 'payload'
import { captureError, defaultBeginTransaction } from 'payload'
import type { MongooseAdapter } from './index.js'
@@ -70,10 +70,12 @@ export const connect: Connect = async function connect(
await this.migrate({ migrations: this.prodMigrations })
}
} catch (err) {
this.payload.logger.error({
await captureError({
err,
msg: `Error: cannot connect to MongoDB. Details: ${err.message}`,
payload: this.payload,
})
process.exit(1)
}
}

View File

@@ -1,6 +1,11 @@
import type { PayloadRequest } from 'payload'
import { commitTransaction, initTransaction, killTransaction, readMigrationFiles } from 'payload'
import {
captureError,
commitTransaction,
createLocalReq,
initTransaction,
killTransaction,
readMigrationFiles,
} from 'payload'
import prompts from 'prompts'
import type { MongooseAdapter } from './index.js'
@@ -45,7 +50,7 @@ export async function migrateFresh(
msg: `Found ${migrationFiles.length} migration files.`,
})
const req = { payload }
const req = await createLocalReq({}, payload)
// Run all migrate up
for (const migration of migrationFiles) {
@@ -68,10 +73,12 @@ export async function migrateFresh(
payload.logger.info({ msg: `Migrated: ${migration.name} (${Date.now() - start}ms)` })
} catch (err: unknown) {
await killTransaction(req)
payload.logger.error({
await captureError({
err,
msg: `Error running migration ${migration.name}. Rolling back.`,
req,
})
throw err
}
}

View File

@@ -1,8 +1,8 @@
import type { DrizzleAdapter } from '@payloadcms/drizzle/types'
import type { Connect, Payload } from 'payload'
import { pushDevSchema } from '@payloadcms/drizzle'
import { drizzle } from 'drizzle-orm/node-postgres'
import { captureError, type Connect, type Payload } from 'payload'
import pg from 'pg'
import type { PostgresAdapter } from './types.js'
@@ -87,16 +87,18 @@ export const connect: Connect = async function connect(
await this.connect(options)
return
}
} else {
this.payload.logger.error({
err,
msg: `Error: cannot connect to Postgres. Details: ${err.message}`,
})
}
if (typeof this.rejectInitializing === 'function') {
this.rejectInitializing()
}
await captureError({
err,
msg: `Error: cannot connect to Postgres. Details: ${err.message}`,
payload: this.payload,
})
process.exit(1)
}

View File

@@ -1,9 +1,9 @@
import type { DrizzleAdapter } from '@payloadcms/drizzle/types'
import type { Connect } from 'payload'
import { createClient } from '@libsql/client'
import { pushDevSchema } from '@payloadcms/drizzle'
import { drizzle } from 'drizzle-orm/libsql'
import { captureError, type Connect } from 'payload'
import type { SQLiteAdapter } from './types.js'
@@ -36,10 +36,14 @@ export const connect: Connect = async function connect(
}
}
} catch (err) {
this.payload.logger.error({ err, msg: `Error: cannot connect to SQLite: ${err.message}` })
if (typeof this.rejectInitializing === 'function') {
this.rejectInitializing()
}
await captureError({
err,
msg: `Error: cannot connect to SQLite: ${err.message}`,
payload: this.payload,
})
process.exit(1)
}

View File

@@ -1,9 +1,9 @@
import type { DrizzleAdapter } from '@payloadcms/drizzle/types'
import type { Connect } from 'payload'
import { pushDevSchema } from '@payloadcms/drizzle'
import { sql, VercelPool } from '@vercel/postgres'
import { drizzle } from 'drizzle-orm/node-postgres'
import { captureError, type Connect } from 'payload'
import pg from 'pg'
import type { VercelPostgresAdapter } from './types.js'
@@ -72,16 +72,18 @@ export const connect: Connect = async function connect(
await this.connect(options)
return
}
} else {
this.payload.logger.error({
err,
msg: `Error: cannot connect to Postgres. Details: ${err.message}`,
})
}
if (typeof this.rejectInitializing === 'function') {
this.rejectInitializing()
}
await captureError({
err,
msg: `Error: cannot connect to Postgres. Details: ${err.message}`,
payload: this.payload,
})
process.exit(1)
}

View File

@@ -1,6 +1,7 @@
import type { Payload } from 'payload'
import {
captureError,
commitTransaction,
createLocalReq,
initTransaction,
@@ -106,10 +107,12 @@ async function runMigrationFile(payload: Payload, migration: Migration, batch: n
await commitTransaction(req)
} catch (err: unknown) {
await killTransaction(req)
payload.logger.error({
await captureError({
err,
msg: parseError(err, `Error running migration ${migration.name}`),
req,
})
process.exit(1)
}
}

View File

@@ -1,4 +1,5 @@
import {
captureError,
commitTransaction,
createLocalReq,
getMigrations,
@@ -63,9 +64,10 @@ export async function migrateDown(this: DrizzleAdapter): Promise<void> {
} catch (err: unknown) {
await killTransaction(req)
payload.logger.error({
await captureError({
err,
msg: parseError(err, `Error migrating down ${migrationFile.name}. Rolling back.`),
req,
})
process.exit(1)
}

View File

@@ -1,4 +1,5 @@
import {
captureError,
commitTransaction,
createLocalReq,
initTransaction,
@@ -79,9 +80,10 @@ export async function migrateFresh(
payload.logger.info({ msg: `Migrated: ${migration.name} (${Date.now() - start}ms)` })
} catch (err: unknown) {
await killTransaction(req)
payload.logger.error({
await captureError({
err,
msg: parseError(err, `Error running migration ${migration.name}. Rolling back`),
req,
})
process.exit(1)
}

View File

@@ -1,4 +1,5 @@
import {
captureError,
commitTransaction,
createLocalReq,
getMigrations,
@@ -69,9 +70,10 @@ export async function migrateRefresh(this: DrizzleAdapter) {
await commitTransaction(req)
} catch (err: unknown) {
await killTransaction(req)
payload.logger.error({
await captureError({
err,
msg: parseError(err, `Error running migration ${migration.name}. Rolling back.`),
req,
})
process.exit(1)
}
@@ -97,9 +99,10 @@ export async function migrateRefresh(this: DrizzleAdapter) {
payload.logger.info({ msg: `Migrated: ${migration.name} (${Date.now() - start}ms)` })
} catch (err: unknown) {
await killTransaction(req)
payload.logger.error({
await captureError({
err,
msg: parseError(err, `Error running migration ${migration.name}. Rolling back.`),
req,
})
process.exit(1)
}

View File

@@ -1,4 +1,5 @@
import {
captureError,
commitTransaction,
createLocalReq,
getMigrations,
@@ -63,10 +64,7 @@ export async function migrateReset(this: DrizzleAdapter): Promise<void> {
}
await killTransaction(req)
payload.logger.error({
err,
msg,
})
await captureError({ err, msg, req })
process.exit(1)
}
}
@@ -85,7 +83,7 @@ export async function migrateReset(this: DrizzleAdapter): Promise<void> {
},
})
} catch (err: unknown) {
payload.logger.error({ err, msg: 'Error deleting dev migration' })
await captureError({ err, msg: 'Error deleting dev migration', req })
}
}
}

View File

@@ -1,5 +1,7 @@
import type { ClientConfig } from 'pg'
import { captureError } from 'payload'
import type { BasePostgresAdapter } from './types.js'
const setConnectionStringDatabase = ({
@@ -88,9 +90,10 @@ export const createDatabase = async function (this: BasePostgresAdapter, args: A
await createdDatabaseClient.query(`CREATE SCHEMA ${schemaName}`)
this.payload.logger.info(`Created schema "${dbName}.${schemaName}"`)
} catch (err) {
this.payload.logger.error({
await captureError({
err,
msg: `Error: failed to create schema "${dbName}.${schemaName}". Details: ${err.message}`,
payload: this.payload,
})
} finally {
await createdDatabaseClient.end()
@@ -99,9 +102,10 @@ export const createDatabase = async function (this: BasePostgresAdapter, args: A
return true
} catch (err) {
this.payload.logger.error({
await captureError({
err,
msg: `Error: failed to create database ${dbName}. Details: ${err.message}`,
payload: this.payload,
})
return false

View File

@@ -1,3 +1,5 @@
import { captureError } from 'payload'
import type { BasePostgresAdapter } from './types.js'
export const createExtensions = async function (this: BasePostgresAdapter): Promise<void> {
@@ -6,7 +8,11 @@ export const createExtensions = async function (this: BasePostgresAdapter): Prom
try {
await this.drizzle.execute(`CREATE EXTENSION IF NOT EXISTS "${extension}"`)
} catch (err) {
this.payload.logger.error({ err, msg: `Failed to create extension ${extension}` })
await captureError({
err,
msg: `Failed to create extension ${extension}`,
payload: this.payload,
})
}
}
}

View File

@@ -1,5 +1,4 @@
import type { BeginTransaction } from 'payload'
import { type BeginTransaction, captureError } from 'payload'
import { v4 as uuid } from 'uuid'
import type { DrizzleAdapter, DrizzleTransaction } from '../types.js'
@@ -58,7 +57,11 @@ export const beginTransaction: BeginTransaction = async function beginTransactio
resolve,
}
} catch (err) {
this.payload.logger.error({ err, msg: `Error: cannot begin transaction: ${err.message}` })
await captureError({
err,
msg: `Error: cannot begin transaction: ${err.message}`,
payload: this.payload,
})
process.exit(1)
}

View File

@@ -1,5 +1,6 @@
import { sanitizeID } from '@payloadcms/ui/shared'
import {
captureError,
type Locale,
logError,
type Payload,
@@ -66,7 +67,13 @@ export const getDocumentData = async ({
})
}
} catch (err) {
logError({ err, payload })
let msg: string
if (globalSlug) {
msg = `Failed to retrieve global ${globalSlug} data `
} else {
msg = `Failed to retrieve collection ${collectionSlug} document with ID-${idArg} data`
}
await captureError({ err, msg, req })
}
return resolvedData

View File

@@ -10,7 +10,7 @@ import {
hasSavePermission as getHasSavePermission,
isEditing as getIsEditing,
} from '@payloadcms/ui/shared'
import { docAccessOperation, docAccessOperationGlobal, logError } from 'payload'
import { captureError, docAccessOperation, docAccessOperationGlobal, logError } from 'payload'
export const getDocumentPermissions = async (args: {
collectionConfig?: SanitizedCollectionConfig
@@ -60,7 +60,11 @@ export const getDocumentPermissions = async (args: {
}).then((permissions) => permissions.update)
}
} catch (err) {
logError({ err, payload: req.payload })
await captureError({
err,
msg: `Failed to retrieve collection ${collectionConfig.slug} document with ID-${id} permissions`,
req,
})
}
}
@@ -87,7 +91,11 @@ export const getDocumentPermissions = async (args: {
}).then((permissions) => permissions.update)
}
} catch (err) {
logError({ err, payload: req.payload })
await captureError({
err,
msg: `Failed to retrieve global ${globalConfig.slug} permissions`,
req,
})
}
}

View File

@@ -12,7 +12,7 @@ import { RenderServerComponent } from '@payloadcms/ui/elements/RenderServerCompo
import { formatAdminURL, isEditing as getIsEditing } from '@payloadcms/ui/shared'
import { buildFormState } from '@payloadcms/ui/utilities/buildFormState'
import { notFound, redirect } from 'next/navigation.js'
import { logError } from 'payload'
import { captureError, logError } from 'payload'
import React from 'react'
import type { GenerateEditViewMetadata } from './getMetaBySegment.js'
@@ -405,7 +405,12 @@ export async function Document(props: AdminViewServerProps) {
throw error
}
logError({ err: error, payload: props.initPageResult.req.payload })
await captureError({
collectionSlug: props?.initPageResult?.collectionConfig?.slug,
err: error,
msg: `Failed to render document on - ${props.initPageResult.req?.url}`,
req: props.initPageResult.req,
})
if (error.message === 'not-found') {
notFound()

View File

@@ -1,6 +1,6 @@
import type { Payload, PayloadRequest, Where } from 'payload'
import { logError } from 'payload'
import { captureError, logError } from 'payload'
type ReturnType = {
id: string
@@ -69,7 +69,19 @@ export async function getLatestVersion(args: Args): Promise<ReturnType> {
updatedAt: response.docs[0].updatedAt,
}
} catch (err) {
logError({ err, payload })
let msg: string
if (type === 'collection') {
msg = `Failed to retrieve latest version of collection ${args.slug}, ID-${args.parentID}`
} else {
msg = `Failed to retrieve latest version of global ${args.slug}`
}
await captureError({
collectionSlug: type === 'collection' ? args.slug : undefined,
err,
msg,
req,
})
return null
}

View File

@@ -1,6 +1,8 @@
import type { DocumentViewServerProps, PaginatedDocs } from 'payload'
import { Gutter, ListQueryProvider, SetDocumentStepNav } from '@payloadcms/ui'
import { notFound } from 'next/navigation.js'
import { type DocumentViewServerProps, logError, type PaginatedDocs } from 'payload'
import { logError } from 'payload'
import { isNumber } from 'payload/shared'
import React from 'react'

View File

@@ -713,6 +713,8 @@ export type AfterErrorHookArgs = {
error: Error
/** The GraphQL result object, available if the hook is executed within a GraphQL context. */
graphqlResult?: GraphQLFormattedError
/** Extra message that provides additional context about where an error was occured. */
message?: string
/** The Request object containing the currently authenticated user. */
req: PayloadRequest
/** The formatted error result object, available if the hook is executed from a REST context. */

View File

@@ -1,6 +1,7 @@
// @ts-strict-ignore
import type { BaseDatabaseAdapter } from '../types.js'
import { captureError } from '../../utilities/captureError.js'
import { commitTransaction } from '../../utilities/commitTransaction.js'
import { createLocalReq } from '../../utilities/createLocalReq.js'
import { initTransaction } from '../../utilities/initTransaction.js'
@@ -50,7 +51,7 @@ export const migrate: BaseDatabaseAdapter['migrate'] = async function migrate(
await commitTransaction(req)
} catch (err: unknown) {
await killTransaction(req)
payload.logger.error({ err, msg: `Error running migration ${migration.name}` })
await captureError({ err, msg: `Error running migration ${migration.name}`, req })
throw err
}
}

View File

@@ -1,6 +1,7 @@
// @ts-strict-ignore
import type { BaseDatabaseAdapter } from '../types.js'
import { captureError } from '../../utilities/captureError.js'
import { commitTransaction } from '../../utilities/commitTransaction.js'
import { createLocalReq } from '../../utilities/createLocalReq.js'
import { initTransaction } from '../../utilities/initTransaction.js'
@@ -54,10 +55,12 @@ export async function migrateDown(this: BaseDatabaseAdapter): Promise<void> {
await commitTransaction(req)
} catch (err: unknown) {
await killTransaction(req)
payload.logger.error({
await captureError({
err,
msg: `Error running migration ${migrationFile.name}`,
req,
})
process.exit(1)
}
}

View File

@@ -1,6 +1,7 @@
// @ts-strict-ignore
import type { BaseDatabaseAdapter } from '../types.js'
import { captureError } from '../../utilities/captureError.js'
import { commitTransaction } from '../../utilities/commitTransaction.js'
import { createLocalReq } from '../../utilities/createLocalReq.js'
import { initTransaction } from '../../utilities/initTransaction.js'
@@ -58,10 +59,9 @@ export async function migrateRefresh(this: BaseDatabaseAdapter) {
if (err instanceof Error) {
msg += ` ${err.message}`
}
payload.logger.error({
err,
msg,
})
await captureError({ err, msg, req })
process.exit(1)
}
}
@@ -93,10 +93,7 @@ export async function migrateRefresh(this: BaseDatabaseAdapter) {
if (err instanceof Error) {
msg += ` ${err.message}`
}
payload.logger.error({
err,
msg,
})
await captureError({ err, msg, req })
process.exit(1)
}
}

View File

@@ -1,6 +1,7 @@
// @ts-strict-ignore
import type { BaseDatabaseAdapter } from '../types.js'
import { captureError } from '../../utilities/captureError.js'
import { commitTransaction } from '../../utilities/commitTransaction.js'
import { createLocalReq } from '../../utilities/createLocalReq.js'
import { initTransaction } from '../../utilities/initTransaction.js'
@@ -47,7 +48,7 @@ export async function migrateReset(this: BaseDatabaseAdapter): Promise<void> {
payload.logger.info({ msg: `Migrated down: ${migration.name} (${Date.now() - start}ms)` })
} catch (err: unknown) {
await killTransaction(req)
payload.logger.error({ err, msg: `Error running migration ${migration.name}` })
await captureError({ err, msg: `Error running migration ${migration.name}`, req })
throw err
}
}
@@ -64,6 +65,6 @@ export async function migrateReset(this: BaseDatabaseAdapter): Promise<void> {
},
})
} catch (err: unknown) {
payload.logger.error({ err, msg: 'Error deleting dev migration' })
await captureError({ err, msg: 'Error deleting dev migration', req })
}
}

View File

@@ -34,6 +34,7 @@ import type {
ManyOptions as DeleteManyOptions,
Options as DeleteOptions,
} from './collections/operations/local/delete.js'
export type { FieldState } from './admin/forms/Form.js'
import type { Options as DuplicateOptions } from './collections/operations/local/duplicate.js'
import type { Options as FindOptions } from './collections/operations/local/find.js'
import type { Options as FindByIDOptions } from './collections/operations/local/findByID.js'
@@ -63,7 +64,7 @@ import type {
TransformGlobalWithSelect,
} from './types/index.js'
import type { TraverseFieldsCallback } from './utilities/traverseFields.js'
export type { FieldState } from './admin/forms/Form.js'
export type * from './admin/types.js'
import { Cron } from 'croner'
import type { TypeWithVersion } from './versions/types.js'
@@ -83,8 +84,8 @@ import { getLogger } from './utilities/logger.js'
import { serverInit as serverInitTelemetry } from './utilities/telemetry/events/serverInit.js'
import { traverseFields } from './utilities/traverseFields.js'
export type * from './admin/types.js'
export { default as executeAccess } from './auth/executeAccess.js'
export { executeAuthStrategies } from './auth/executeAuthStrategies.js'
export interface GeneratedTypes {
authUntyped: {
@@ -967,7 +968,6 @@ interface RequestContext {
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
export interface DatabaseAdapter extends BaseDatabaseAdapter {}
export type { Payload, RequestContext }
export { executeAuthStrategies } from './auth/executeAuthStrategies.js'
export { getAccessResults } from './auth/getAccessResults.js'
export { getFieldsToSign } from './auth/getFieldsToSign.js'
export * from './auth/index.js'
@@ -1002,12 +1002,11 @@ export type {
User,
VerifyConfig,
} from './auth/types.js'
export { generateImportMap } from './bin/generateImportMap/index.js'
export type { ImportMap } from './bin/generateImportMap/index.js'
export { genImportMapIterateFields } from './bin/generateImportMap/iterateFields.js'
export { genImportMapIterateFields } from './bin/generateImportMap/iterateFields.js'
export {
type ClientCollectionConfig,
createClientCollectionConfig,
@@ -1054,6 +1053,7 @@ export type {
} from './collections/config/types.js'
export { createDataloaderCacheKey, getDataLoader } from './collections/dataloader.js'
export { countOperation } from './collections/operations/count.js'
export { createOperation } from './collections/operations/create.js'
export { deleteOperation } from './collections/operations/delete.js'
@@ -1076,8 +1076,8 @@ export {
type UnsanitizedClientConfig,
} from './config/client.js'
export { defaults } from './config/defaults.js'
export { sanitizeConfig } from './config/sanitize.js'
export type * from './config/types.js'
export { combineQueries } from './database/combineQueries.js'
export { createDatabaseAdapter } from './database/createDatabaseAdapter.js'
@@ -1187,8 +1187,8 @@ export {
} from './errors/index.js'
export type { ValidationFieldError } from './errors/index.js'
export { baseBlockFields } from './fields/baseFields/baseBlockFields.js'
export { baseIDField } from './fields/baseFields/baseIDField.js'
export {
createClientField,
createClientFields,
@@ -1196,7 +1196,6 @@ export {
type ServerOnlyFieldProperties,
} from './fields/config/client.js'
export { sanitizeFields } from './fields/config/sanitize.js'
export type {
AdminClient,
ArrayField,
@@ -1299,16 +1298,16 @@ export type {
ValidateOptions,
ValueWithRelation,
} from './fields/config/types.js'
export { getDefaultValue } from './fields/getDefaultValue.js'
export { getDefaultValue } from './fields/getDefaultValue.js'
export { traverseFields as afterChangeTraverseFields } from './fields/hooks/afterChange/traverseFields.js'
export { promise as afterReadPromise } from './fields/hooks/afterRead/promise.js'
export { traverseFields as afterReadTraverseFields } from './fields/hooks/afterRead/traverseFields.js'
export { traverseFields as beforeChangeTraverseFields } from './fields/hooks/beforeChange/traverseFields.js'
export { traverseFields as beforeValidateTraverseFields } from './fields/hooks/beforeValidate/traverseFields.js'
export { default as sortableFieldTypes } from './fields/sortableFieldTypes.js'
export { validations } from './fields/validations.js'
export type {
ArrayFieldValidation,
BlocksFieldValidation,
@@ -1340,6 +1339,7 @@ export type {
UploadFieldValidation,
UsernameFieldValidation,
} from './fields/validations.js'
export {
type ClientGlobalConfig,
createClientGlobalConfig,
@@ -1347,7 +1347,6 @@ export {
type ServerOnlyGlobalAdminProperties,
type ServerOnlyGlobalProperties,
} from './globals/config/client.js'
export type {
AfterChangeHook as GlobalAfterChangeHook,
AfterReadHook as GlobalAfterReadHook,
@@ -1363,6 +1362,7 @@ export type {
export { docAccessOperation as docAccessOperationGlobal } from './globals/operations/docAccess.js'
export { findOneOperation } from './globals/operations/findOne.js'
export { findVersionByIDOperation as findVersionByIDOperationGlobal } from './globals/operations/findVersionByID.js'
export { findVersionsOperation as findVersionsOperationGlobal } from './globals/operations/findVersions.js'
export { restoreVersionOperation as restoreVersionOperationGlobal } from './globals/operations/restoreVersion.js'
@@ -1406,9 +1406,10 @@ export { getLocalI18n } from './translations/getLocalI18n.js'
export * from './types/index.js'
export { getFileByPath } from './uploads/getFileByPath.js'
export type * from './uploads/types.js'
export { addDataAndFileToRequest } from './utilities/addDataAndFileToRequest.js'
export { addLocalesToRequestFromData, sanitizeLocales } from './utilities/addLocalesToRequest.js'
export { captureError } from './utilities/captureError.js'
export { commitTransaction } from './utilities/commitTransaction.js'
export {
configToJSONSchema,

View File

@@ -11,6 +11,7 @@ import type {
import type { RunJobResult } from './runJob/index.js'
import { Forbidden } from '../../../errors/Forbidden.js'
import { captureError } from '../../../utilities/captureError.js'
import isolateObjectProperty from '../../../utilities/isolateObjectProperty.js'
import { getUpdateJobFunction } from './runJob/getUpdateJobFunction.js'
import { importHandlerPath } from './runJob/importHandlerPath.js'
@@ -203,7 +204,12 @@ export const runJobs = async ({
if (!workflowHandler) {
const jobLabel = job.workflowSlug || `Task: ${job.taskSlug}`
const errorMessage = `Can't find runner while importing with the path ${workflowConfig.handler} in job type ${jobLabel}.`
req.payload.logger.error(errorMessage)
await captureError({
err: new Error(
`Can't find runner while importing with the path ${workflowConfig.handler} in job type ${jobLabel}.`,
),
req,
})
await updateJob({
error: {
@@ -258,9 +264,10 @@ export const runJobs = async ({
where: { id: { in: jobsToDelete } },
})
} catch (err) {
req.payload.logger.error({
await captureError({
err,
msg: `failed to delete jobs ${jobsToDelete.join(', ')} on complete`,
req,
})
}
}

View File

@@ -80,7 +80,7 @@ export const runJSONJob = async ({
}),
)
} catch (err) {
const errorResult = handleWorkflowError({
const errorResult = await handleWorkflowError({
error: err,
job,
req,

View File

@@ -19,6 +19,7 @@ import type {
} from '../../../config/types/workflowTypes.js'
import type { UpdateJobFunction } from './getUpdateJobFunction.js'
import { captureError } from '../../../../utilities/captureError.js'
import { calculateBackoffWaitUntil } from './calculateBackoffWaitUntil.js'
import { importHandlerPath } from './importHandlerPath.js'
@@ -73,7 +74,8 @@ export async function handleTaskFailed({
taskStatus: null | SingleTaskStatus<string>
updateJob: UpdateJobFunction
}): Promise<never> {
req.payload.logger.error({ err: error, job, msg: `Error running task ${taskID}`, taskSlug })
await captureError({ err: error, msg: `Error running task ${taskID}`, req })
req.payload.logger.error({ job, taskSlug })
if (taskConfig?.onFail) {
await taskConfig.onFail()

View File

@@ -3,13 +3,14 @@ import type { PayloadRequest } from '../../../../types/index.js'
import type { BaseJob, WorkflowConfig, WorkflowTypes } from '../../../config/types/workflowTypes.js'
import type { RunTaskFunctionState } from './getRunTaskFunction.js'
import { captureError } from '../../../../utilities/captureError.js'
import { calculateBackoffWaitUntil } from './calculateBackoffWaitUntil.js'
/**
* This is called if a workflow catches an error. It determines if it's a final error
* or not and handles logging.
*/
export function handleWorkflowError({
export async function handleWorkflowError({
error,
job,
req,
@@ -21,9 +22,9 @@ export function handleWorkflowError({
req: PayloadRequest
state: RunTaskFunctionState
workflowConfig: WorkflowConfig<WorkflowTypes>
}): {
}): Promise<{
hasFinalError: boolean
} {
}> {
const jobLabel = job.workflowSlug || `Task: ${job.taskSlug}`
let hasFinalError = state.reachedMaxRetries // If any TASK reached max retries, the job has an error
@@ -64,9 +65,10 @@ export function handleWorkflowError({
}
}
req.payload.logger.error({
await captureError({
err: error,
msg: `Error running job ${jobLabel} id: ${job.id} attempt ${job.totalTried + 1}${maxWorkflowRetries !== undefined ? '/' + (maxWorkflowRetries + 1) : ''}`,
req,
})
return {

View File

@@ -49,7 +49,7 @@ export const runJob = async ({
tasks: getRunTaskFunction(state, job, workflowConfig, req, false, updateJob),
})
} catch (err) {
const { hasFinalError } = handleWorkflowError({
const { hasFinalError } = await handleWorkflowError({
error: err,
job,
req,

View File

@@ -1,6 +1,7 @@
// @ts-strict-ignore
import type { Endpoint, SanitizedConfig } from '../config/types.js'
import { captureError } from '../utilities/captureError.js'
import { runJobs, type RunJobsArgs } from './operations/runJobs/index.js'
const configHasJobs = (config: SanitizedConfig): boolean => {
@@ -64,10 +65,10 @@ export const runJobsEndpoint: Endpoint = {
noJobsRemaining = result.noJobsRemaining
remainingJobsFromQueried = result.remainingJobsFromQueried
} catch (err) {
req.payload.logger.error({
await captureError({
err,
msg: 'There was an error running jobs:',
queue: runJobsArgs.queue,
msg: `There was an error running jobs, queue: ${runJobsArgs.queue}`,
req,
})
return Response.json(

View File

@@ -12,6 +12,7 @@ import type { PayloadRequest } from '../types/index.js'
import type { FileData, FileToSave, ProbedImageSize, UploadEdits } from './types.js'
import { FileRetrievalError, FileUploadError, MissingFile } from '../errors/index.js'
import { captureError } from '../utilities/captureError.js'
import { canResizeImage } from './canResizeImage.js'
import { cropImage } from './cropImage.js'
import { getExternalFile } from './getExternalFile.js'
@@ -343,7 +344,7 @@ export const generateFileData = async <T>({
filesToSave.push(...sizesToSave)
}
} catch (err) {
req.payload.logger.error(err)
await captureError({ err, req })
throw new FileUploadError(req.t)
}

View File

@@ -1,8 +1,8 @@
import type { Payload } from '../index.js'
import type { PayloadRequest } from '../types/index.js'
import type { FileToSave } from './types.js'
import { FileUploadError } from '../errors/index.js'
import { captureError, type Payload } from '../index.js'
import saveBufferToFile from './saveBufferToFile.js'
export const uploadFiles = async (
@@ -17,7 +17,7 @@ export const uploadFiles = async (
}),
)
} catch (err) {
payload.logger.error(err)
await captureError({ err, req })
throw new FileUploadError(req.t)
}
}

View File

@@ -0,0 +1,55 @@
import type { Payload, PayloadRequest } from '../types/index.js'
import { type CollectionSlug, createLocalReq, logError } from '../index.js'
export const captureError = async ({
collectionSlug,
err,
msg,
...reqOrPayload
}: {
collectionSlug?: CollectionSlug
err: unknown
msg?: string
} & (
| { payload: Payload }
| {
req: PayloadRequest
}
)) => {
let payload: Payload
let req: PayloadRequest | undefined = undefined
if ('req' in reqOrPayload) {
payload = reqOrPayload.req.payload
req = reqOrPayload.req
} else {
payload = reqOrPayload.payload
}
if (msg) {
payload.logger.error(msg)
logError({ err, payload })
} else {
logError({ err, payload })
}
if (Array.isArray(payload.config.hooks?.afterError)) {
for (const hook of payload.config.hooks.afterError) {
if (!req) {
req = await createLocalReq({}, payload)
}
try {
await hook({
collection: collectionSlug ? req.payload.collections[collectionSlug]?.config : undefined,
context: req.context,
error: err as Error,
message: msg,
req,
})
// eslint-disable-next-line no-empty
} catch {}
}
}
}

View File

@@ -1,6 +1,6 @@
import type { PayloadRequest } from '../types/index.js'
import { type Payload } from '../index.js'
import { captureError, type Payload } from '../index.js'
type Args = {
id?: number | string
@@ -21,9 +21,11 @@ export const deleteCollectionVersions = async ({ id, slug, payload, req }: Args)
},
})
} catch (err) {
payload.logger.error({
await captureError({
err,
msg: `There was an error removing versions for the deleted ${slug} document with ID ${id}.`,
payload,
req,
})
}
}

View File

@@ -1,11 +1,11 @@
import type { PayloadRequest } from '../types/index.js'
import { type Payload } from '../index.js'
import { captureError, type Payload } from '../index.js'
type Args = {
id?: number | string
payload: Payload
req?: PayloadRequest
req: PayloadRequest
slug: string
}
@@ -52,9 +52,10 @@ export const deleteScheduledPublishJobs = async ({
},
})
} catch (err) {
payload.logger.error({
await captureError({
err,
msg: `There was an error deleting scheduled publish jobs from the queue for ${slug} document with ID ${id}.`,
req,
})
}
}

View File

@@ -3,6 +3,8 @@ import type { SanitizedCollectionConfig } from '../collections/config/types.js'
import type { SanitizedGlobalConfig } from '../globals/config/types.js'
import type { Payload, PayloadRequest, Where } from '../types/index.js'
import { captureError } from '../utilities/captureError.js'
type Args = {
collection?: SanitizedCollectionConfig
global?: SanitizedGlobalConfig
@@ -77,8 +79,16 @@ export const enforceMaxVersions = async ({
})
}
} catch (err) {
payload.logger.error(
`There was an error cleaning up old versions for the ${entityType} ${slug}`,
)
let msg: string
if (entityType === 'global') {
msg = `There was an error cleaning up old versions for the global ${slug}`
} else {
msg = `There was an error cleaning up old versions for the collection ${slug} document ID-${id}`
}
await captureError({
err,
msg,
req,
})
}
}

View File

@@ -4,7 +4,7 @@ import type { SanitizedGlobalConfig } from '../globals/config/types.js'
import type { Payload } from '../index.js'
import type { PayloadRequest, SelectType } from '../types/index.js'
import { deepCopyObjectSimple } from '../index.js'
import { captureError, deepCopyObjectSimple } from '../index.js'
import sanitizeInternalFields from '../utilities/sanitizeInternalFields.js'
import { getQueryDraftsSelect } from './drafts/getQueryDraftsSelect.js'
import { enforceMaxVersions } from './enforceMaxVersions.js'
@@ -181,7 +181,8 @@ export const saveVersion = async ({
if (global) {
errorMessage = `There was an error while saving a version for the global ${typeof global.label === 'string' ? global.label : global.slug}.`
}
payload.logger.error({ err, msg: errorMessage })
await captureError({ err, msg: errorMessage, req })
return
}

View File

@@ -53,11 +53,12 @@ export const sentryPlugin =
afterError: [
...(config.hooks?.afterError ?? []),
async (args) => {
const status = (args.error as APIError).status ?? 500
const status = (args.error as APIError)?.status ?? 500
if (status >= 500 || captureErrors.includes(status)) {
let context: Partial<ScopeContext> = {
extra: {
errorCollectionSlug: args.collection?.slug,
message: args.message,
},
...(args.req.user && {
user: {

View File

@@ -15,6 +15,7 @@ import type {
} from 'payload'
import ObjectIdImport from 'bson-objectid'
import { captureError } from 'payload'
import {
deepCopyObjectSimple,
fieldAffectsData,
@@ -214,11 +215,7 @@ export const addFieldStatePromise = async (args: AddFieldStatePromiseArgs): Prom
})
} catch (err) {
validationResult = `Error validating field at path: ${path}`
req.payload.logger.error({
err,
msg: validationResult,
})
await captureError({ err, msg: validationResult, req })
}
}

View File

@@ -1,6 +1,6 @@
import type { Data, Field, FlattenedBlock, PayloadRequest, TabAsField, User } from 'payload'
import { getDefaultValue } from 'payload'
import { captureError, getDefaultValue } from 'payload'
import { fieldAffectsData, tabHasName } from 'payload/shared'
import { iterateFields } from './iterateFields.js'
@@ -39,9 +39,10 @@ export const defaultValuePromise = async <T>({
value: siblingData[field.name],
})
} catch (err) {
req.payload.logger.error({
await captureError({
err,
msg: `Error calculating default value for field: ${field.name}`,
req,
})
}
}

View File

@@ -1,6 +1,6 @@
import type { BuildFormStateArgs, ClientConfig, ClientUser, ErrorResult, FormState } from 'payload'
import { formatErrors } from 'payload'
import { captureError, formatErrors } from 'payload'
import { reduceFieldsToValues } from 'payload/shared'
import { fieldSchemasToFormState } from '../forms/fieldSchemasToFormState/index.js'
@@ -77,7 +77,7 @@ export const buildFormStateHandler = async (
const res = await buildFormState(args)
return res
} catch (err) {
req.payload.logger.error({ err, msg: `There was an error building form state` })
await captureError({ err, msg: `There was an error building form state`, req })
if (err.message === 'Could not find field schema for given path') {
return {

View File

@@ -10,7 +10,7 @@ import type {
Where,
} from 'payload'
import { APIError, formatErrors } from 'payload'
import { APIError, captureError, formatErrors } from 'payload'
import { isNumber } from 'payload/shared'
import { getClientConfig } from './getClientConfig.js'
@@ -50,7 +50,7 @@ export const buildTableStateHandler = async (
const res = await buildTableState(args)
return res
} catch (err) {
req.payload.logger.error({ err, msg: `There was an error building form state` })
await captureError({ err, msg: `There was an error building form state`, req })
if (err.message === 'Could not find field schema for given path') {
return {

View File

@@ -1,5 +1,6 @@
import ObjectIdImport from 'bson-objectid'
import {
captureError,
type CollectionSlug,
type Data,
type Field,
@@ -189,9 +190,10 @@ export const copyDataFromLocaleHandler = async (args: CopyDataFromLocaleArgs) =>
try {
return await copyDataFromLocale(args)
} catch (err) {
req.payload.logger.error({
await captureError({
err,
msg: `There was an error copying data from "${args.fromLocale}" to "${args.toLocale}"`,
req,
})
if (err.message === 'Unauthorized') {

View File

@@ -1,4 +1,4 @@
import type { PayloadRequest, SchedulePublishTaskInput } from 'payload'
import { captureError, type PayloadRequest, type SchedulePublishTaskInput } from 'payload'
export type SchedulePublishHandlerArgs = {
date?: Date
@@ -77,8 +77,7 @@ export const schedulePublishHandler = async ({
}
}
payload.logger.error(error)
payload.logger.error(err)
await captureError({ err, msg: error, req })
return {
error,