mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-09-07 09:21:24 +08:00
refactor(server): server errors (#5741)
standardize the error raising in both GraphQL Resolvers and Controllers.
Now, All user aware errors should be throwed with `HttpException`'s variants, for example `NotFoundException`.
> Directly throwing `GraphQLError` are forbidden.
The GraphQL errorFormatter will handle it automatically and set `code`, `status` in error extensions.
At the same time, the frontend `GraphQLError` should be imported from `@affine/graphql`, which introduce a better error extensions type.
----
controller example:
```js
@Get('/docs/${id}')
doc() {
// ...
// imported from '@nestjs/common'
throw new NotFoundException('Doc is not found.');
// ...
}
```
the above will response as:
```
status: 404 Not Found
{
"message": "Doc is not found.",
"statusCode": 404,
"error": "Not Found"
}
```
resolver example:
```js
@Mutation()
invite() {
// ...
throw new PayloadTooLargeException('Workspace seats is full.')
// ...
}
```
the above will response as:
```
status: 200 Ok
{
"data": null,
"errors": [
{
"message": "Workspace seats is full.",
"extensions": {
"code": 404,
"status": "Not Found"
}
}
]
}
```
for frontend GraphQLError user-friend, a helper function introduced:
```js
import { findGraphQLError } from '@affine/graphql'
fetch(query)
.catch(errOrArr => {
const e = findGraphQLError(errOrArr, e => e.extensions.code === 404)
if (e) {
// handle
}
})
```
This commit is contained in:
@@ -4,7 +4,7 @@ import {
|
||||
GraphQLRequestListener,
|
||||
} from '@apollo/server';
|
||||
import { Plugin } from '@nestjs/apollo';
|
||||
import { Logger } from '@nestjs/common';
|
||||
import { HttpException, Logger } from '@nestjs/common';
|
||||
import { Response } from 'express';
|
||||
|
||||
import { metrics } from '../metrics/metrics';
|
||||
@@ -27,28 +27,44 @@ export class GQLLoggerPlugin implements ApolloServerPlugin {
|
||||
|
||||
metrics.gql.counter('query_counter').add(1, { operation });
|
||||
const start = Date.now();
|
||||
function endTimer() {
|
||||
return Date.now() - start;
|
||||
}
|
||||
|
||||
return Promise.resolve({
|
||||
willSendResponse: () => {
|
||||
const costInMilliseconds = Date.now() - start;
|
||||
res.setHeader(
|
||||
'Server-Timing',
|
||||
`gql;dur=${costInMilliseconds};desc="GraphQL"`
|
||||
);
|
||||
metrics.gql
|
||||
.histogram('query_duration')
|
||||
.record(costInMilliseconds, { operation });
|
||||
const time = endTimer();
|
||||
res.setHeader('Server-Timing', `gql;dur=${time};desc="GraphQL"`);
|
||||
metrics.gql.histogram('query_duration').record(time, { operation });
|
||||
return Promise.resolve();
|
||||
},
|
||||
didEncounterErrors: () => {
|
||||
const costInMilliseconds = Date.now() - start;
|
||||
res.setHeader(
|
||||
'Server-Timing',
|
||||
`gql;dur=${costInMilliseconds};desc="GraphQL ${operation}"`
|
||||
);
|
||||
metrics.gql
|
||||
.histogram('query_duration')
|
||||
.record(costInMilliseconds, { operation });
|
||||
didEncounterErrors: ctx => {
|
||||
metrics.gql.counter('query_error_counter').add(1, { operation });
|
||||
|
||||
ctx.errors.forEach(err => {
|
||||
// only log non-user errors
|
||||
let msg: string | undefined;
|
||||
|
||||
if (!err.originalError) {
|
||||
msg = err.toString();
|
||||
} else {
|
||||
const originalError = err.originalError;
|
||||
|
||||
// do not log client errors, and put more information in the error extensions.
|
||||
if (!(originalError instanceof HttpException)) {
|
||||
if (originalError.cause && originalError.cause instanceof Error) {
|
||||
msg = originalError.cause.stack ?? originalError.cause.message;
|
||||
} else {
|
||||
msg = originalError.stack ?? originalError.message;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (msg) {
|
||||
this.logger.error('GraphQL Unhandled Error', msg);
|
||||
}
|
||||
});
|
||||
|
||||
return Promise.resolve();
|
||||
},
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user