Docs/Gateway: Logging & Error Handling (#6544)

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
This commit is contained in:
Arda TANRIKULU 2025-02-24 15:39:39 +03:00 committed by GitHub
parent f60ae6e83e
commit cae3f5caef
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 311 additions and 36 deletions

View file

@ -40,7 +40,7 @@ const terms = new Map<string[], string /* href */>([
[['Rate Limiting'], '/docs/gateway/other-features/security/rate-limiting'],
[['Cost Limit'], '/docs/gateway/other-features/security/cost-limit'],
[['Security'], '/docs/gateway/other-features/security'],
[['maskedErrors'], '/docs/gateway/other-features/security/error-masking'],
[['maskedErrors'], '/docs/gateway/logging-and-error-handling'],
]);
export function GatewayMarqueeRows({

View file

@ -537,6 +537,8 @@ export const gatewayConfig = defineConfig({
[Hive Gateway uses the same logging mechanism of GraphQL Yoga](https://the-guild.dev/graphql/yoga-server/docs/features/logging-and-debugging)
[Learn more about Logging](/docs/gateway/logging)
### `graphqlEndpoint`
This is the option to provide a custom GraphQL endpoint for the server. By default, it is
@ -554,7 +556,7 @@ export const gatewayConfig = defineConfig({
This is enabled by default for security reasons.
[Learn more about Error Masking](/docs/gateway/other-features/security/error-masking)
[Learn more about Error Masking](/docs/gateway/logging-and-error-handling)
### `cache`

View file

@ -7,6 +7,7 @@ export default {
'monitoring-tracing': 'Monitoring / Tracing',
'defer-stream': 'Incremental Delivery (Defer & Stream)',
subscriptions: 'Subscriptions',
'logging-and-error-handling': 'Logging & Error Handling',
'other-features': 'Other Features',
deployment: 'Deployment',
};

View file

@ -0,0 +1,305 @@
---
title: Logging & Error Handling
description:
Learn how to log information about the Gateway's lifecycle, errors, and other events. Also, learn
how to handle errors and mask them to prevent leaking sensitive information to the client.
---
# Logging & Error Handling
import { Callout } from '@theguild/components'
Hive Gateway provides a built-in logger that allows you to log information about the Gateway's
lifecycle, errors, and other events. The default logger uses JavaScript's
[`console`](https://developer.mozilla.org/en-US/docs/Web/API/console) API, but you can also provide
a custom logger implementation. By default, Hive Gateway logs the critical masked errors so that the
sensitive information is not exposed to the client.
## Logging
Hive Gateway provides a built-in logging system that allows you to log information about the
Gateway's lifecycle, errors, and other events. The default logger uses JavaScript's
[`console`](https://developer.mozilla.org/en-US/docs/Web/API/console) API, but you can also provide
a custom logger implementation.
### Logging in JSON format
By default without any production environment variable, Hive Gateway prints the logs in human
readable format. However, in production (when `NODE_ENV` is `production`) Hive Gateway prints the
logs in JSON format, but if you want to enable it in regular mode, you can pass `LOG_LEVEL=json` as
an environment variable.
### Log Levels
Hive Gateway uses 4 log levels `debug`, `info`, `warn` and `error`. By default, Hive Gateway will
only log info, warn and error messages.
#### `error`
- Only log unexpected errors including masked errors
#### `warn`
- All prior log levels
- Deprecation notices
- Potential issues that could lead to errors
#### `info`
- All prior log levels
- Information about the current state of the system
#### `debug`
- All prior log levels
- Processing of GraphQL parameters
- Parsing of GraphQL parameters
- Execution or subscription start
- Received GraphQL operation variables
- Execution or subscription end
- Health checks
- Subgraph requests
- All HTTP requests and responses
- Supergraph fetching
- Any caching operations
<Callout>
If you want to learn more about the life-cycle of the Gateway, you can enable debug logs. Setting
the `DEBUG=1` environment variable or passing `debug` to `logging` parameter will enable debug
logs that include all operations done to the upstream services, query plans etc.
</Callout>
### Integration with Winston (only Node.js)
By default, Hive Gateway uses the built-in `console` logger. However, you can also integrate Hive
Gateway with [Winston](https://github.com/winstonjs/winston) on Node.js environments.
You need to install `winston` and `@graphql-hive/winston` packages to use Winston with Hive Gateway.
```sh npm2yarn
npm i winston @graphql-hive/winston
```
```ts
import { createLogger, format, transports } from 'winston'
import { createLoggerFromWinston } from '@graphql-hive/winston'
// Create a Winston logger
const winstonLogger = createLogger({
level: 'info',
format: format.combine(format.timestamp(), format.json()),
transports: [new transports.Console()]
})
export const gatewayConfig = defineConfig({
// Create an adapter for Winston
logging: createLoggerFromWinston(winstonLogger)
})
```
### Custom Logger
If you want to implement your own logger, you can use the interface `Logger` from
`@graphql-hive/gateway`. The logger should implement the following methods:
- `log(...args: any[]): void`
- `error(...args: any[]): void`
- `warn(...args: any[]): void`
- `info(...args: any[]): void`
- `debug(lazyMessageArgs: ...(() => any | any)[]): void`
- `child(nameOrMeta: string | Record<string, string>): Logger`
Keep on mind that, all methods can receive `any` type of variables, and serializing them for the
output is up to the logger implementation. `JSON.stringify` might not be the best option for all
cases.
Also please notice that `debug` can receive functions that will be invoked only if the log level is
enabled.
Here is an example of a custom logger implementation, that logs to the console. But keep in mind
that this is a very basic example and you shouldn't use it in production directly!
```ts
import { Logger } from '@graphql-hive/gateway'
class CustomLogger implements Logger {
constructor(
public name: string,
public meta: Record<string, string>,
public isDebugEnabled: boolean
) {}
log(...args: any[]): void {
console.log(this.name, this.meta, ...args)
}
error(...args: any[]): void {
console.error(this.name, this.meta, ...args)
}
warn(...args: any[]): void {
console.warn(this.name, this.meta, ...args)
}
info(...args: any[]): void {
console.info(this.name, this.meta, ...args)
}
debug(...lazyMessageArgs: (() => any | any)[]): void {
if (this.isDebugEnabled) {
console.debug(
this.name,
this.meta,
...lazyMessageArgs.map(arg => (typeof arg === 'function' ? arg() : arg))
)
}
}
child(nameOrMeta: string | Record<string, string>): Logger {
let newName: string
let newMeta: Record<string, string>
if (typeof nameOrMeta === 'string') {
newName = `${this.name}.${nameOrMeta}`
newMeta = this.meta
} else {
newName = this.name
newMeta = { ...this.meta, ...nameOrMeta }
}
return new CustomLogger(newName, newMeta)
}
}
```
## Error Handling
### Error Codes
To help with debugging and improve error understanding for consumers, Hive Gateway uses error codes
for the following specific types of errors:
| Code | Description |
| --------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `GRAPHQL_PARSE_FAILED` | Sent GraphQL Operation cannot be parsed |
| `GRAPHQL_VALIDATION_FAILED` | Sent GraphQL Operation is not validated against the schema |
| `BAD_USER_INPUT` | Variable or argument values are not valid in the GraphQL parameters |
| `TIMEOUT_ERROR` | Indicates a timeout in the subgraph execution. Keep in mind that this timeout is not always an HTTP timeout or a timeout specified by you. It might be the subgraph server that timed out. Learn more about upstream reliability to configure timeout based on your needs. |
| `SCHEMA_RELOAD` | When Hive Gateway updates the schema by polling or any other way, all ongoing requests are terminated, including subscriptions and long-running defer/stream operations. In this case, this error is sent to the client to indicate a schema change. Usually, a retry is expected in this case. |
| `SHUTTING_DOWN` | When Hive Gateway is shutting down or restarting, like `SCHEMA_RELOAD`, it aborts all requests and notifies the client with this error code. After a certain amount of time, a retry can be sent. |
| `UNAUTHENTICATED` | The given auth credentials are not valid. Check the logs and documentation of the used auth plugin to learn more. |
| `PERSISTED_QUERY_NOT_FOUND` | Indicates that persisted operation information is not found in the store. Check the related persisted operation plugin docs to learn more about this error. |
| `INTERNAL_SERVER_ERROR` | Indicates that the error is unexpected or unspecified and masked by the gateway. It is probably caused by an unexpected network, connection, or other runtime error. You can see the details of this error in the logs. |
| `DOWNSTREAM_SERVICE_ERROR` | Indicates the error is subgraph-related, and generated by the subgraph, not the gateway |
### Error Masking
Hive Gateway masks internal server errors by default to prevent leaking sensitive information to the
client. But without any codes all errors in the result are considered safe from the subgraph.
Understanding this concept is crucial for building secure applications.
So any HTTP errors, network errors, or any other errors that are not related to the subgraph are
masked by default. But any errors sent by the subgraph are not masked by default.
All masked errors are replaced with a generic error message and the original error is not exposed to
the client.
```json
{
"errors": [
{
"message": "Unexpected error.",
"code": "INTERNAL_SERVER_ERROR",
"locations": [
{
"line": 2,
"column": 3
}
],
"path": ["greeting"]
}
],
"data": null
}
```
But if the subgraph sends an error in the result, it is forwarded with `DOWNSTREAM_SERVICE_ERROR` if
`INTERNAL_SERVER_ERROR` code isn't passed.
```json
{
"errors": [
{
"message": "This error is from subgraph",
"code": "DOWNSTREAM_SERVICE_ERROR",
"locations": [
{
"line": 2,
"column": 3
}
],
"path": ["greeting"]
}
],
"data": null
}
```
<Callout>
When `INTERNAL_SERVER_ERROR` is passed from the subgraph, it is masked by the gateway and sent to
the client as `INTERNAL_SERVER_ERROR`. But the gateway will still log the original error.
</Callout>
#### Disabling masking for debugging
For debugging purpose, exposing errors to the client can be needed depending on your architecture.
Error masking can be disabled using the `maskedErrors` option:
```ts filename="gateway.config.ts"
import { defineConfig } from '@graphql-hive/gateway'
export const gatewayConfig = defineConfig({
maskedErrors: false
})
```
#### Receive original error in development mode
When developing locally seeing the original error within your Chrome Dev Tools might be handy for
debugging. You might be tempted to disable the masked errors via the `maskedErrors` config option,
however, **we do not recommend that at all**.
Maintaining consistent behavior between development and production is crucial for not having any
surprises in production. Instead, we recommend enabling the Hive Gateway development mode.
To do this you need to start Hive with the `NODE_ENV` environment variable set to `"development"`.
On unix and windows systems the environment variable can be set when starting the server.
```sh
NODE_ENV=development hive-gateway supergraph MY_SUPERGRAPH
```
```json filename="GraphQL Error Response with original error extensions"
{
"errors": [
{
"message": "Unexpected error.",
"locations": [
{
"line": 2,
"column": 3
}
],
"path": ["greeting"],
"extensions": {
"originalError": {
"message": "request to http://localhost:9876/greeting failed, reason: connect ECONNREFUSED 127.0.0.1:9876",
"stack": "FetchError: request to http://localhost:9876/greeting failed, reason: connect ECONNREFUSED 127.0.0.1:9876\n at ClientRequest.<anonymous> ***"
}
}
}
],
"data": null
}
```

View file

@ -1,6 +1,5 @@
export default {
index: 'Overview',
'error-masking': 'Error Masking',
cors: 'CORS',
'csrf-prevention': 'CSRF Prevention',
'rate-limiting': 'Rate Limiting',

View file

@ -1,32 +0,0 @@
---
searchable: false
---
import { Callout } from '@theguild/components'
# Error Masking
Hive Gateway automatically masks unexpected errors and prevents leaking sensitive information to
clients.
Unexpected errors can be caused by failed connections to remote services such as databases or HTTP
APIs. Nobody external needs to know that your database server is not reachable. Exposing such
information to the outside world can make you vulnerable for targeted attacks.
In order to build secure applications, it is crucial to understand this concept.
## Disable for development
For development purpose, exposing errors to the client can be needed depending on your architecture.
Error masking can be disabled using the `maskedErrors` option:
```ts filename="gateway.config.ts"
import { defineConfig } from '@graphql-hive/gateway'
export const gatewayConfig = defineConfig({
maskedErrors: false
})
```
{/* `TODO: Consider how to explain subgraph errors etc` */}

View file

@ -201,4 +201,4 @@ In most GraphQL servers any thrown error or rejected promise will result in the
leaking to the outside world. Some frameworks have custom logic for catching unexpected errors and
mapping them to an unexpected error instead. In Hive Gateway, this is enabled by default.
[Learn more about Error Masking](/docs/gateway/other-features/security/error-masking)
[Learn more about Error Masking](/docs/gateway/logging-and-error-handling)