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
![Screenshot 2024-11-12
144459](https://github.com/user-attachments/assets/47318329-23b7-4627-afc4-a0bcf4dc3d58)

After

![image](https://github.com/user-attachments/assets/85b06be4-0ab8-4ca2-b237-d6a4d54add3a)
This commit is contained in:
Dan Ribbens
2024-11-13 09:24:53 -05:00
committed by GitHub
parent f264c8087a
commit d6282221db
5 changed files with 56 additions and 2 deletions

View File

@@ -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). |
| **`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). |
| **`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). |
| **`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). |

View File

@@ -47,7 +47,10 @@ export const routeError = async ({
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.
// Therefore, error details will be hidden from the response unless `config.debug` is `true`

View File

@@ -24,6 +24,16 @@ import { defaults } from './defaults.js'
const sanitizeAdminConfig = (configToSanitize: Config): Partial<SanitizedConfig> => {
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
if (!sanitizedConfig?.admin?.user) {
const firstCollectionWithAuth = sanitizedConfig.collections.find(({ auth }) => Boolean(auth))

View File

@@ -8,7 +8,7 @@ import type { BusboyConfig } from 'busboy'
import type GraphQL from 'graphql'
import type { GraphQLFormattedError } from 'graphql'
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 { default as sharp } from 'sharp'
import type { DeepRequired } from 'ts-essentials'
@@ -34,6 +34,7 @@ import type {
} from '../collections/config/types.js'
import type { DatabaseAdapterResult } from '../database/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 { JobsConfig, Payload, RequestContext, TypedUser } from '../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
/**
* 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.
*

View File

@@ -1 +1,18 @@
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'