fengmk2
2025-02-13 02:20:02 +00:00
parent 9a17422d36
commit 0ce4cc8609
6 changed files with 48 additions and 16 deletions
@@ -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
+9 -4
View File
@@ -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: [
+10 -6
View File
@@ -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');