mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-07-30 00:29:46 +08:00
refactor(server): add debug info on global exception log (#10118)
before  after 
This commit is contained in:
@@ -170,7 +170,13 @@ test('should be able to handle unknown internal error in http request', async t
|
|||||||
.expect(HttpStatus.INTERNAL_SERVER_ERROR);
|
.expect(HttpStatus.INTERNAL_SERVER_ERROR);
|
||||||
t.is(res.body.message, 'An internal error occurred.');
|
t.is(res.body.message, 'An internal error occurred.');
|
||||||
t.is(res.body.name, 'INTERNAL_SERVER_ERROR');
|
t.is(res.body.name, 'INTERNAL_SERVER_ERROR');
|
||||||
t.true(t.context.logger.error.calledOnceWith('internal_server_error'));
|
t.true(
|
||||||
|
t.context.logger.error.calledOnceWith(
|
||||||
|
`internal_server_error (${JSON.stringify({
|
||||||
|
requestId: res.body.requestId,
|
||||||
|
})})`
|
||||||
|
)
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Hard to test through websocket, will call event handler directly
|
// Hard to test through websocket, will call event handler directly
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import {
|
import {
|
||||||
DynamicModule,
|
DynamicModule,
|
||||||
|
ExecutionContext,
|
||||||
ForwardReference,
|
ForwardReference,
|
||||||
Logger,
|
Logger,
|
||||||
Module,
|
Module,
|
||||||
@@ -13,7 +14,11 @@ import { get } from 'lodash-es';
|
|||||||
import { ClsModule } from 'nestjs-cls';
|
import { ClsModule } from 'nestjs-cls';
|
||||||
|
|
||||||
import { AppController } from './app.controller';
|
import { AppController } from './app.controller';
|
||||||
import { genRequestId, getOptionalModuleMetadata } from './base';
|
import {
|
||||||
|
getOptionalModuleMetadata,
|
||||||
|
getRequestIdFromHost,
|
||||||
|
getRequestIdFromRequest,
|
||||||
|
} from './base';
|
||||||
import { CacheModule } from './base/cache';
|
import { CacheModule } from './base/cache';
|
||||||
import { AFFiNEConfig, ConfigModule, mergeConfigOverride } from './base/config';
|
import { AFFiNEConfig, ConfigModule, mergeConfigOverride } from './base/config';
|
||||||
import { ErrorModule } from './base/error';
|
import { ErrorModule } from './base/error';
|
||||||
@@ -57,7 +62,7 @@ export const FunctionalityModules = [
|
|||||||
generateId: true,
|
generateId: true,
|
||||||
idGenerator(req: Request) {
|
idGenerator(req: Request) {
|
||||||
// make every request has a unique id to tracing
|
// make every request has a unique id to tracing
|
||||||
return req.get('x-cloud-trace-context') ?? genRequestId('req');
|
return getRequestIdFromRequest(req, 'http');
|
||||||
},
|
},
|
||||||
setup(cls, _req, res: Response) {
|
setup(cls, _req, res: Response) {
|
||||||
res.setHeader('X-Request-Id', cls.getId());
|
res.setHeader('X-Request-Id', cls.getId());
|
||||||
@@ -68,9 +73,9 @@ export const FunctionalityModules = [
|
|||||||
interceptor: {
|
interceptor: {
|
||||||
mount: true,
|
mount: true,
|
||||||
generateId: true,
|
generateId: true,
|
||||||
idGenerator() {
|
idGenerator(context: ExecutionContext) {
|
||||||
// make every request has a unique id to tracing
|
// make every request has a unique id to tracing
|
||||||
return genRequestId('ws');
|
return getRequestIdFromHost(context);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
|
|||||||
@@ -136,7 +136,7 @@ export class UserFriendlyError extends Error {
|
|||||||
].join('\n');
|
].join('\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
log(context: string) {
|
log(context: string, debugInfo?: object) {
|
||||||
// ignore all user behavior error log
|
// ignore all user behavior error log
|
||||||
if (
|
if (
|
||||||
this.type !== 'internal_server_error' &&
|
this.type !== 'internal_server_error' &&
|
||||||
@@ -148,11 +148,15 @@ export class UserFriendlyError extends Error {
|
|||||||
const logger = new Logger(context);
|
const logger = new Logger(context);
|
||||||
const fn = this.status >= 500 ? logger.error : logger.log;
|
const fn = this.status >= 500 ? logger.error : logger.log;
|
||||||
|
|
||||||
fn.call(
|
let message = this.name;
|
||||||
logger,
|
if (debugInfo) {
|
||||||
this.name,
|
message += ` (${JSON.stringify(debugInfo)})`;
|
||||||
this.cause ? ((this.cause as any).stack ?? this.cause) : this.stack
|
}
|
||||||
);
|
let stack = this.stack ?? '';
|
||||||
|
if (this.cause) {
|
||||||
|
stack += `\n\nCaused by:\n\n${(this.cause as any).stack ?? this.cause}`;
|
||||||
|
}
|
||||||
|
fn.call(logger, message, stack);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import {
|
|||||||
UserFriendlyError,
|
UserFriendlyError,
|
||||||
} from '../error';
|
} from '../error';
|
||||||
import { metrics } from '../metrics';
|
import { metrics } from '../metrics';
|
||||||
|
import { getRequestIdFromHost } from '../utils';
|
||||||
|
|
||||||
export function mapAnyError(error: any): UserFriendlyError {
|
export function mapAnyError(error: any): UserFriendlyError {
|
||||||
if (error instanceof UserFriendlyError) {
|
if (error instanceof UserFriendlyError) {
|
||||||
@@ -45,7 +46,9 @@ export class GlobalExceptionFilter extends BaseExceptionFilter {
|
|||||||
// see '../graphql/logger-plugin.ts'
|
// see '../graphql/logger-plugin.ts'
|
||||||
throw error;
|
throw error;
|
||||||
} else {
|
} else {
|
||||||
error.log('HTTP');
|
error.log('HTTP', {
|
||||||
|
requestId: error.requestId ?? getRequestIdFromHost(host),
|
||||||
|
});
|
||||||
metrics.controllers
|
metrics.controllers
|
||||||
.counter('error')
|
.counter('error')
|
||||||
.add(1, { status: error.status, type: error.type, error: error.name });
|
.add(1, { status: error.status, type: error.type, error: error.name });
|
||||||
|
|||||||
@@ -84,16 +84,17 @@ export function parseCookies(
|
|||||||
* Request type
|
* Request type
|
||||||
*
|
*
|
||||||
* @description
|
* @description
|
||||||
* - `req`: http request
|
* - `graphql`: graphql request
|
||||||
|
* - `http`: http request
|
||||||
* - `ws`: websocket request
|
* - `ws`: websocket request
|
||||||
* - `se`: server event
|
* - `se`: server event
|
||||||
* - `job`: cron job
|
* - `job`: cron job
|
||||||
* - `rpc`: rpc request
|
* - `rpc`: rpc request
|
||||||
*/
|
*/
|
||||||
export type RequestType = 'req' | 'ws' | 'se' | 'job' | 'rpc';
|
export type RequestType = GqlContextType | 'se' | 'job';
|
||||||
|
|
||||||
export function genRequestId(type: RequestType) {
|
export function genRequestId(type: RequestType) {
|
||||||
return `${AFFiNE.flavor.type}:${type}-${randomUUID()}`;
|
return `${AFFiNE.flavor.type}:${type}/${randomUUID()}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getOrGenRequestId(type: RequestType) {
|
export function getOrGenRequestId(type: RequestType) {
|
||||||
@@ -101,3 +102,16 @@ export function getOrGenRequestId(type: RequestType) {
|
|||||||
// but it can be lost in unexpected scenarios, such as unit tests, where it is automatically generated.
|
// but it can be lost in unexpected scenarios, such as unit tests, where it is automatically generated.
|
||||||
return ClsServiceManager.getClsService()?.getId() ?? genRequestId(type);
|
return ClsServiceManager.getClsService()?.getId() ?? genRequestId(type);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getRequestIdFromRequest(req: Request, type: RequestType) {
|
||||||
|
const traceContext = req.headers['x-cloud-trace-context'] as string;
|
||||||
|
const traceId = traceContext ? traceContext.split('/', 1)[0] : undefined;
|
||||||
|
if (traceId) return traceId;
|
||||||
|
return genRequestId(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getRequestIdFromHost(host: ArgumentsHost) {
|
||||||
|
const type = host.getType<GqlContextType>();
|
||||||
|
const req = getRequestFromHost(host);
|
||||||
|
return getRequestIdFromRequest(req, type);
|
||||||
|
}
|
||||||
|
|||||||
@@ -110,7 +110,7 @@ test('should return doc when found', async t => {
|
|||||||
const res = await app
|
const res = await app
|
||||||
.GET(`/rpc/workspaces/${workspace.id}/docs/${docId}`)
|
.GET(`/rpc/workspaces/${workspace.id}/docs/${docId}`)
|
||||||
.set('x-access-token', t.context.crypto.sign(docId))
|
.set('x-access-token', t.context.crypto.sign(docId))
|
||||||
.set('x-cloud-trace-context', 'test-trace-id')
|
.set('x-cloud-trace-context', 'test-trace-id/span-id')
|
||||||
.expect(200)
|
.expect(200)
|
||||||
.expect('x-request-id', 'test-trace-id')
|
.expect('x-request-id', 'test-trace-id')
|
||||||
.expect('Content-Type', 'application/octet-stream');
|
.expect('Content-Type', 'application/octet-stream');
|
||||||
|
|||||||
Reference in New Issue
Block a user