feat: customize log levels and downgrade common errors to info (#9156)
### What? Allows configuration of the log level based on the error being thrown and also downgrades common errors to be info instead of error by default. ### Why? Currently all errors result in logger.error being called which can polute the logs with junk that is normal and doesn't need attention. ### How? Adds a config property called `loggingLevels` that is used to override the default log levels based on the name of the error being thrown. Sanitize config will provide the defaulted 'info' level errors which can be overriden in the config. Before  After 
This commit is contained in:
@@ -76,6 +76,7 @@ The following options are available:
|
|||||||
| **`cors`** | Cross-origin resource sharing (CORS) is a mechanism that accept incoming requests from given domains. You can also customize the `Access-Control-Allow-Headers` header. [More details](#cors). |
|
| **`cors`** | Cross-origin resource sharing (CORS) is a mechanism that accept incoming requests from given domains. You can also customize the `Access-Control-Allow-Headers` header. [More details](#cors). |
|
||||||
| **`localization`** | Opt-in to translate your content into multiple locales. [More details](./localization). |
|
| **`localization`** | Opt-in to translate your content into multiple locales. [More details](./localization). |
|
||||||
| **`logger`** | Logger options, logger options with a destination stream, or an instantiated logger instance. [More details](https://getpino.io/#/docs/api?id=options). |
|
| **`logger`** | Logger options, logger options with a destination stream, or an instantiated logger instance. [More details](https://getpino.io/#/docs/api?id=options). |
|
||||||
|
| **`loggingLevels`** | An object to override the level to use in the logger for Payload's errors. |
|
||||||
| **`graphQL`** | Manage GraphQL-specific functionality, including custom queries and mutations, query complexity limits, etc. [More details](../graphql/overview#graphql-options). |
|
| **`graphQL`** | Manage GraphQL-specific functionality, including custom queries and mutations, query complexity limits, etc. [More details](../graphql/overview#graphql-options). |
|
||||||
| **`cookiePrefix`** | A string that will be prefixed to all cookies that Payload sets. |
|
| **`cookiePrefix`** | A string that will be prefixed to all cookies that Payload sets. |
|
||||||
| **`csrf`** | A whitelist array of URLs to allow Payload to accept cookies from. [More details](../authentication/overview#csrf-protection). |
|
| **`csrf`** | A whitelist array of URLs to allow Payload to accept cookies from. [More details](../authentication/overview#csrf-protection). |
|
||||||
|
|||||||
@@ -47,7 +47,10 @@ export const routeError = async ({
|
|||||||
|
|
||||||
let status = err.status || httpStatus.INTERNAL_SERVER_ERROR
|
let status = err.status || httpStatus.INTERNAL_SERVER_ERROR
|
||||||
|
|
||||||
logger.error(err.stack)
|
const level = payload.config.loggingLevels[err.name] ?? 'error'
|
||||||
|
if (level) {
|
||||||
|
logger[level](level === 'info' ? { msg: err.message } : { err })
|
||||||
|
}
|
||||||
|
|
||||||
// Internal server errors can contain anything, including potentially sensitive data.
|
// Internal server errors can contain anything, including potentially sensitive data.
|
||||||
// Therefore, error details will be hidden from the response unless `config.debug` is `true`
|
// Therefore, error details will be hidden from the response unless `config.debug` is `true`
|
||||||
|
|||||||
@@ -24,6 +24,16 @@ import { defaults } from './defaults.js'
|
|||||||
const sanitizeAdminConfig = (configToSanitize: Config): Partial<SanitizedConfig> => {
|
const sanitizeAdminConfig = (configToSanitize: Config): Partial<SanitizedConfig> => {
|
||||||
const sanitizedConfig = { ...configToSanitize }
|
const sanitizedConfig = { ...configToSanitize }
|
||||||
|
|
||||||
|
// default logging level will be 'error' if not provided
|
||||||
|
sanitizedConfig.loggingLevels = {
|
||||||
|
Forbidden: 'info',
|
||||||
|
Locked: 'info',
|
||||||
|
MissingFile: 'info',
|
||||||
|
NotFound: 'info',
|
||||||
|
ValidationError: 'info',
|
||||||
|
...(sanitizedConfig.loggingLevels || {}),
|
||||||
|
}
|
||||||
|
|
||||||
// add default user collection if none provided
|
// add default user collection if none provided
|
||||||
if (!sanitizedConfig?.admin?.user) {
|
if (!sanitizedConfig?.admin?.user) {
|
||||||
const firstCollectionWithAuth = sanitizedConfig.collections.find(({ auth }) => Boolean(auth))
|
const firstCollectionWithAuth = sanitizedConfig.collections.find(({ auth }) => Boolean(auth))
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import type { BusboyConfig } from 'busboy'
|
|||||||
import type GraphQL from 'graphql'
|
import type GraphQL from 'graphql'
|
||||||
import type { GraphQLFormattedError } from 'graphql'
|
import type { GraphQLFormattedError } from 'graphql'
|
||||||
import type { JSONSchema4 } from 'json-schema'
|
import type { JSONSchema4 } from 'json-schema'
|
||||||
import type { DestinationStream, pino } from 'pino'
|
import type { DestinationStream, Level, pino } from 'pino'
|
||||||
import type React from 'react'
|
import type React from 'react'
|
||||||
import type { default as sharp } from 'sharp'
|
import type { default as sharp } from 'sharp'
|
||||||
import type { DeepRequired } from 'ts-essentials'
|
import type { DeepRequired } from 'ts-essentials'
|
||||||
@@ -34,6 +34,7 @@ import type {
|
|||||||
} from '../collections/config/types.js'
|
} from '../collections/config/types.js'
|
||||||
import type { DatabaseAdapterResult } from '../database/types.js'
|
import type { DatabaseAdapterResult } from '../database/types.js'
|
||||||
import type { EmailAdapter, SendEmailOptions } from '../email/types.js'
|
import type { EmailAdapter, SendEmailOptions } from '../email/types.js'
|
||||||
|
import type { ErrorName } from '../errors/types.js'
|
||||||
import type { GlobalConfig, Globals, SanitizedGlobalConfig } from '../globals/config/types.js'
|
import type { GlobalConfig, Globals, SanitizedGlobalConfig } from '../globals/config/types.js'
|
||||||
import type { JobsConfig, Payload, RequestContext, TypedUser } from '../index.js'
|
import type { JobsConfig, Payload, RequestContext, TypedUser } from '../index.js'
|
||||||
import type { PayloadRequest, Where } from '../types/index.js'
|
import type { PayloadRequest, Where } from '../types/index.js'
|
||||||
@@ -980,6 +981,28 @@ export type Config = {
|
|||||||
*/
|
*/
|
||||||
logger?: 'sync' | { destination?: DestinationStream; options: pino.LoggerOptions } | PayloadLogger
|
logger?: 'sync' | { destination?: DestinationStream; options: pino.LoggerOptions } | PayloadLogger
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Override the log level of errors for Payload's error handler or disable logging with `false`.
|
||||||
|
* Levels can be any of the following: 'trace', 'debug', 'info', 'warn', 'error', 'fatal' or false.
|
||||||
|
*
|
||||||
|
* Default levels:
|
||||||
|
* {
|
||||||
|
`* APIError: 'error',
|
||||||
|
`* AuthenticationError: 'error',
|
||||||
|
`* ErrorDeletingFile: 'error',
|
||||||
|
`* FileRetrievalError: 'error',
|
||||||
|
`* FileUploadError: 'error',
|
||||||
|
`* Forbidden: 'info',
|
||||||
|
`* Locked: 'info',
|
||||||
|
`* LockedAuth: 'error',
|
||||||
|
`* MissingFile: 'info',
|
||||||
|
`* NotFound: 'info',
|
||||||
|
`* QueryError: 'error',
|
||||||
|
`* ValidationError: 'info',
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
loggingLevels?: Partial<Record<ErrorName, false | Level>>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The maximum allowed depth to be permitted application-wide. This setting helps prevent against malicious queries.
|
* The maximum allowed depth to be permitted application-wide. This setting helps prevent against malicious queries.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1 +1,18 @@
|
|||||||
export * from './index.js'
|
export * from './index.js'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Error names that can be thrown by Payload during runtime
|
||||||
|
*/
|
||||||
|
export type ErrorName =
|
||||||
|
| 'APIError'
|
||||||
|
| 'AuthenticationError'
|
||||||
|
| 'ErrorDeletingFile'
|
||||||
|
| 'FileRetrievalError'
|
||||||
|
| 'FileUploadError'
|
||||||
|
| 'Forbidden'
|
||||||
|
| 'Locked'
|
||||||
|
| 'LockedAuth'
|
||||||
|
| 'MissingFile'
|
||||||
|
| 'NotFound'
|
||||||
|
| 'QueryError'
|
||||||
|
| 'ValidationError'
|
||||||
|
|||||||
Reference in New Issue
Block a user