feat: 增加结构化请求日志
- src/common/logging: 新增 Pino 结构化日志服务和 HTTP 请求日志拦截器 - src/main.ts: 全局接入请求日志并保留响应 envelope - package.json: 增加 pino 运行时依赖
This commit is contained in:
@@ -30,6 +30,7 @@
|
||||
"@nestjs/platform-express": "^11.1.6",
|
||||
"@nestjs/swagger": "^11.2.0",
|
||||
"@prisma/client": "^6.14.0",
|
||||
"pino": "^10.3.1",
|
||||
"reflect-metadata": "^0.2.2",
|
||||
"rxjs": "^7.8.2",
|
||||
"zod": "^4.0.17"
|
||||
|
||||
Generated
+98
@@ -26,6 +26,9 @@ importers:
|
||||
'@prisma/client':
|
||||
specifier: ^6.14.0
|
||||
version: 6.19.3(prisma@6.19.3(typescript@5.9.3))(typescript@5.9.3)
|
||||
pino:
|
||||
specifier: ^10.3.1
|
||||
version: 10.3.1
|
||||
reflect-metadata:
|
||||
specifier: ^0.2.2
|
||||
version: 0.2.2
|
||||
@@ -741,6 +744,9 @@ packages:
|
||||
'@nestjs/platform-express':
|
||||
optional: true
|
||||
|
||||
'@pinojs/redact@0.4.0':
|
||||
resolution: {integrity: sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg==}
|
||||
|
||||
'@pkgjs/parseargs@0.11.0':
|
||||
resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==}
|
||||
engines: {node: '>=14'}
|
||||
@@ -1197,6 +1203,10 @@ packages:
|
||||
array-timsort@1.0.3:
|
||||
resolution: {integrity: sha512-/+3GRL7dDAGEfM6TseQk/U+mi18TU2Ms9I3UlLdUMhz2hbvGNTKdj9xniwXfUqgYhHxRx0+8UnKkvlNwVU+cWQ==}
|
||||
|
||||
atomic-sleep@1.0.0:
|
||||
resolution: {integrity: sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==}
|
||||
engines: {node: '>=8.0.0'}
|
||||
|
||||
babel-jest@30.4.1:
|
||||
resolution: {integrity: sha512-fATAbM8piYxkiXQp3RBXmZHxZVNJZAVXXfyeyCN2Tida3+qJ8ea9UxhiJ2y4fLO90ZImKt6k9FlcH2+rLkJGhw==}
|
||||
engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0}
|
||||
@@ -2333,6 +2343,10 @@ packages:
|
||||
ohash@2.0.11:
|
||||
resolution: {integrity: sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==}
|
||||
|
||||
on-exit-leak-free@2.1.2:
|
||||
resolution: {integrity: sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
|
||||
on-finished@2.4.1:
|
||||
resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==}
|
||||
engines: {node: '>= 0.8'}
|
||||
@@ -2431,6 +2445,16 @@ packages:
|
||||
resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
pino-abstract-transport@3.0.0:
|
||||
resolution: {integrity: sha512-wlfUczU+n7Hy/Ha5j9a/gZNy7We5+cXp8YL+X+PG8S0KXxw7n/JXA3c46Y0zQznIJ83URJiwy7Lh56WLokNuxg==}
|
||||
|
||||
pino-std-serializers@7.1.0:
|
||||
resolution: {integrity: sha512-BndPH67/JxGExRgiX1dX0w1FvZck5Wa4aal9198SrRhZjH3GxKQUKIBnYJTdj2HDN3UQAS06HlfcSbQj2OHmaw==}
|
||||
|
||||
pino@10.3.1:
|
||||
resolution: {integrity: sha512-r34yH/GlQpKZbU1BvFFqOjhISRo1MNx1tWYsYvmj6KIRHSPMT2+yHOEb1SG6NMvRoHRF0a07kCOox/9yakl1vg==}
|
||||
hasBin: true
|
||||
|
||||
pirates@4.0.7:
|
||||
resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==}
|
||||
engines: {node: '>= 6'}
|
||||
@@ -2469,6 +2493,9 @@ packages:
|
||||
typescript:
|
||||
optional: true
|
||||
|
||||
process-warning@5.0.0:
|
||||
resolution: {integrity: sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA==}
|
||||
|
||||
proxy-addr@2.0.7:
|
||||
resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==}
|
||||
engines: {node: '>= 0.10'}
|
||||
@@ -2487,6 +2514,9 @@ packages:
|
||||
resolution: {integrity: sha512-Rzq0KEyX/w/tEybncDgdkZrJgVUsUMk3xjh3t5bv3S1HTAtg+uOYt72+ZfwiQwKdysThkTBdL/rTi6HDmX9Ddw==}
|
||||
engines: {node: '>=0.6'}
|
||||
|
||||
quick-format-unescaped@4.0.4:
|
||||
resolution: {integrity: sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==}
|
||||
|
||||
range-parser@1.2.1:
|
||||
resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==}
|
||||
engines: {node: '>= 0.6'}
|
||||
@@ -2512,6 +2542,13 @@ packages:
|
||||
resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==}
|
||||
engines: {node: '>= 14.18.0'}
|
||||
|
||||
real-require@0.2.0:
|
||||
resolution: {integrity: sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==}
|
||||
engines: {node: '>= 12.13.0'}
|
||||
|
||||
real-require@1.0.0:
|
||||
resolution: {integrity: sha512-P4nbQYQfePJxRSmY+v/KINxVucm4NF3p3s7pJveMTtom52FR4YGltUQLB8idDXwDDWW+eYrWDFbuzUnjoWHF7g==}
|
||||
|
||||
reflect-metadata@0.2.2:
|
||||
resolution: {integrity: sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==}
|
||||
|
||||
@@ -2552,6 +2589,10 @@ packages:
|
||||
safe-buffer@5.2.1:
|
||||
resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==}
|
||||
|
||||
safe-stable-stringify@2.5.0:
|
||||
resolution: {integrity: sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
safer-buffer@2.1.2:
|
||||
resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==}
|
||||
|
||||
@@ -2618,6 +2659,9 @@ packages:
|
||||
resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
sonic-boom@4.2.1:
|
||||
resolution: {integrity: sha512-w6AxtubXa2wTXAUsZMMWERrsIRAdrK0Sc+FUytWvYAhBJLyuI4llrMIC1DtlNSdI99EI86KZum2MMq3EAZlF9Q==}
|
||||
|
||||
source-map-support@0.5.13:
|
||||
resolution: {integrity: sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==}
|
||||
|
||||
@@ -2632,6 +2676,10 @@ packages:
|
||||
resolution: {integrity: sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==}
|
||||
engines: {node: '>= 8'}
|
||||
|
||||
split2@4.2.0:
|
||||
resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==}
|
||||
engines: {node: '>= 10.x'}
|
||||
|
||||
sprintf-js@1.0.3:
|
||||
resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==}
|
||||
|
||||
@@ -2765,6 +2813,10 @@ packages:
|
||||
resolution: {integrity: sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
thread-stream@4.2.0:
|
||||
resolution: {integrity: sha512-e2zZ96wSChazBsbENf/Pcm/4swHt2cEKQ92rhUjkL9GCKiTDJIaTBenjE/m9DXi0QBmTMDkFDdOomUy20A1tDQ==}
|
||||
engines: {node: '>=20'}
|
||||
|
||||
tinyexec@1.2.4:
|
||||
resolution: {integrity: sha512-SHf/r48b7vOrjve9PxJo3MN5v5yuyjHvdUcrQffT3WXMUfnGmHDVbC4k3sHJaJTgZCwpUplIaAo5ANtMyp3YHg==}
|
||||
engines: {node: '>=18'}
|
||||
@@ -3848,6 +3900,8 @@ snapshots:
|
||||
optionalDependencies:
|
||||
'@nestjs/platform-express': 11.1.26(@nestjs/common@11.1.26(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.26)
|
||||
|
||||
'@pinojs/redact@0.4.0': {}
|
||||
|
||||
'@pkgjs/parseargs@0.11.0':
|
||||
optional: true
|
||||
|
||||
@@ -4323,6 +4377,8 @@ snapshots:
|
||||
|
||||
array-timsort@1.0.3: {}
|
||||
|
||||
atomic-sleep@1.0.0: {}
|
||||
|
||||
babel-jest@30.4.1(@babel/core@7.29.7):
|
||||
dependencies:
|
||||
'@babel/core': 7.29.7
|
||||
@@ -5628,6 +5684,8 @@ snapshots:
|
||||
|
||||
ohash@2.0.11: {}
|
||||
|
||||
on-exit-leak-free@2.1.2: {}
|
||||
|
||||
on-finished@2.4.1:
|
||||
dependencies:
|
||||
ee-first: 1.1.1
|
||||
@@ -5724,6 +5782,26 @@ snapshots:
|
||||
|
||||
picomatch@4.0.4: {}
|
||||
|
||||
pino-abstract-transport@3.0.0:
|
||||
dependencies:
|
||||
split2: 4.2.0
|
||||
|
||||
pino-std-serializers@7.1.0: {}
|
||||
|
||||
pino@10.3.1:
|
||||
dependencies:
|
||||
'@pinojs/redact': 0.4.0
|
||||
atomic-sleep: 1.0.0
|
||||
on-exit-leak-free: 2.1.2
|
||||
pino-abstract-transport: 3.0.0
|
||||
pino-std-serializers: 7.1.0
|
||||
process-warning: 5.0.0
|
||||
quick-format-unescaped: 4.0.4
|
||||
real-require: 0.2.0
|
||||
safe-stable-stringify: 2.5.0
|
||||
sonic-boom: 4.2.1
|
||||
thread-stream: 4.2.0
|
||||
|
||||
pirates@4.0.7: {}
|
||||
|
||||
pkg-dir@4.2.0:
|
||||
@@ -5758,6 +5836,8 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- magicast
|
||||
|
||||
process-warning@5.0.0: {}
|
||||
|
||||
proxy-addr@2.0.7:
|
||||
dependencies:
|
||||
forwarded: 0.2.0
|
||||
@@ -5773,6 +5853,8 @@ snapshots:
|
||||
dependencies:
|
||||
side-channel: 1.1.1
|
||||
|
||||
quick-format-unescaped@4.0.4: {}
|
||||
|
||||
range-parser@1.2.1: {}
|
||||
|
||||
raw-body@3.0.2:
|
||||
@@ -5799,6 +5881,10 @@ snapshots:
|
||||
|
||||
readdirp@4.1.2: {}
|
||||
|
||||
real-require@0.2.0: {}
|
||||
|
||||
real-require@1.0.0: {}
|
||||
|
||||
reflect-metadata@0.2.2: {}
|
||||
|
||||
require-directory@2.1.1: {}
|
||||
@@ -5838,6 +5924,8 @@ snapshots:
|
||||
|
||||
safe-buffer@5.2.1: {}
|
||||
|
||||
safe-stable-stringify@2.5.0: {}
|
||||
|
||||
safer-buffer@2.1.2: {}
|
||||
|
||||
schema-utils@3.3.0:
|
||||
@@ -5924,6 +6012,10 @@ snapshots:
|
||||
|
||||
slash@3.0.0: {}
|
||||
|
||||
sonic-boom@4.2.1:
|
||||
dependencies:
|
||||
atomic-sleep: 1.0.0
|
||||
|
||||
source-map-support@0.5.13:
|
||||
dependencies:
|
||||
buffer-from: 1.1.2
|
||||
@@ -5938,6 +6030,8 @@ snapshots:
|
||||
|
||||
source-map@0.7.4: {}
|
||||
|
||||
split2@4.2.0: {}
|
||||
|
||||
sprintf-js@1.0.3: {}
|
||||
|
||||
stack-utils@2.0.6:
|
||||
@@ -6030,6 +6124,10 @@ snapshots:
|
||||
glob: 7.2.3
|
||||
minimatch: 3.1.5
|
||||
|
||||
thread-stream@4.2.0:
|
||||
dependencies:
|
||||
real-require: 1.0.0
|
||||
|
||||
tinyexec@1.2.4: {}
|
||||
|
||||
tinyglobby@0.2.17:
|
||||
|
||||
@@ -17,6 +17,8 @@ import { PrismaModule } from './prisma/prisma.module';
|
||||
import { ProjectsModule } from './projects/projects.module';
|
||||
import { SettingsModule } from './settings/settings.module';
|
||||
import { WeComModule } from './notifications/wecom/wecom.module';
|
||||
import { HttpRequestLoggingInterceptor } from './common/logging/http-request-logging.interceptor';
|
||||
import { StructuredLoggerService } from './common/logging/structured-logger.service';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
@@ -43,5 +45,6 @@ import { WeComModule } from './notifications/wecom/wecom.module';
|
||||
ProjectsModule,
|
||||
DeployRunsModule,
|
||||
],
|
||||
providers: [StructuredLoggerService, HttpRequestLoggingInterceptor],
|
||||
})
|
||||
export class AppModule {}
|
||||
|
||||
@@ -0,0 +1,155 @@
|
||||
/**
|
||||
* 全局 HTTP 日志拦截器记录请求结果、耗时和操作者,不记录 body/header/token。
|
||||
*/
|
||||
import {
|
||||
CallHandler,
|
||||
ExecutionContext,
|
||||
HttpException,
|
||||
Injectable,
|
||||
NestInterceptor,
|
||||
} from '@nestjs/common';
|
||||
import { Observable, catchError, tap, throwError } from 'rxjs';
|
||||
import { AuthenticatedUser } from '../../auth/auth.types';
|
||||
import { AppError } from '../errors/app-error';
|
||||
import { redactSensitive } from '../security/redact-sensitive';
|
||||
import { RequestHeaders, resolveRequestId } from '../http/request-id';
|
||||
import { StructuredLoggerService } from './structured-logger.service';
|
||||
|
||||
type RequestWithLoggingContext = {
|
||||
headers?: RequestHeaders;
|
||||
method?: string;
|
||||
originalUrl?: string;
|
||||
url?: string;
|
||||
ip?: string;
|
||||
socket?: {
|
||||
remoteAddress?: string;
|
||||
};
|
||||
user?: AuthenticatedUser;
|
||||
};
|
||||
|
||||
type ResponseWithStatus = {
|
||||
statusCode?: number;
|
||||
};
|
||||
|
||||
@Injectable()
|
||||
export class HttpRequestLoggingInterceptor implements NestInterceptor {
|
||||
constructor(private readonly logger: StructuredLoggerService) {}
|
||||
|
||||
intercept(context: ExecutionContext, next: CallHandler): Observable<unknown> {
|
||||
if (context.getType() !== 'http') {
|
||||
return next.handle();
|
||||
}
|
||||
|
||||
const httpContext = context.switchToHttp();
|
||||
const request = httpContext.getRequest<RequestWithLoggingContext>();
|
||||
const response = httpContext.getResponse<ResponseWithStatus>();
|
||||
const startedAt = Date.now();
|
||||
|
||||
return next.handle().pipe(
|
||||
tap(() => {
|
||||
this.logRequest('success', request, response, startedAt);
|
||||
}),
|
||||
catchError((error: unknown) => {
|
||||
this.logRequest('error', request, response, startedAt, error);
|
||||
return throwError(() => error);
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
private logRequest(
|
||||
outcome: 'success' | 'error',
|
||||
request: RequestWithLoggingContext,
|
||||
response: ResponseWithStatus,
|
||||
startedAt: number,
|
||||
error?: unknown,
|
||||
): void {
|
||||
const statusCode = this.resolveStatusCode(response, error);
|
||||
const payload = {
|
||||
event: 'http_request_completed',
|
||||
outcome,
|
||||
requestId: resolveRequestId(request.headers),
|
||||
method: request.method,
|
||||
path: this.safePath(request.originalUrl ?? request.url),
|
||||
statusCode,
|
||||
durationMs: Date.now() - startedAt,
|
||||
actorId: request.user?.id,
|
||||
actorName: request.user?.account,
|
||||
actorRole: request.user?.role,
|
||||
sourceIp: request.ip ?? request.socket?.remoteAddress,
|
||||
...this.errorPayload(error),
|
||||
};
|
||||
|
||||
if (statusCode >= 500) {
|
||||
this.logger.error(payload, 'http request failed');
|
||||
return;
|
||||
}
|
||||
|
||||
if (statusCode >= 400) {
|
||||
this.logger.warn(payload, 'http request completed with client error');
|
||||
return;
|
||||
}
|
||||
|
||||
this.logger.info(payload, 'http request completed');
|
||||
}
|
||||
|
||||
private resolveStatusCode(
|
||||
response: ResponseWithStatus,
|
||||
error?: unknown,
|
||||
): number {
|
||||
if (error instanceof AppError) {
|
||||
return error.statusCode;
|
||||
}
|
||||
|
||||
if (error instanceof HttpException) {
|
||||
return error.getStatus();
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return 500;
|
||||
}
|
||||
|
||||
return response.statusCode ?? 200;
|
||||
}
|
||||
|
||||
private errorPayload(error?: unknown): Record<string, unknown> {
|
||||
if (!error) {
|
||||
return {};
|
||||
}
|
||||
|
||||
if (error instanceof AppError) {
|
||||
return {
|
||||
errorCode: error.code,
|
||||
errorMessage: this.redactedMessage(error.message),
|
||||
};
|
||||
}
|
||||
|
||||
if (error instanceof HttpException) {
|
||||
return {
|
||||
errorCode: 'HTTP_EXCEPTION',
|
||||
errorMessage: this.redactedMessage('请求处理失败'),
|
||||
};
|
||||
}
|
||||
|
||||
if (error instanceof Error) {
|
||||
return {
|
||||
errorCode: 'UNEXPECTED_ERROR',
|
||||
errorName: error.name,
|
||||
errorMessage: this.redactedMessage(error.message),
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
errorCode: 'UNEXPECTED_ERROR',
|
||||
};
|
||||
}
|
||||
|
||||
private redactedMessage(message: string): string {
|
||||
const redacted = redactSensitive({ message });
|
||||
return typeof redacted.message === 'string' ? redacted.message : '已隐藏错误信息';
|
||||
}
|
||||
|
||||
private safePath(value?: string): string {
|
||||
const path = value?.split('?')[0]?.trim();
|
||||
return path || '/';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
/**
|
||||
* Pino 结构化日志服务只输出运维可索引字段,避免业务层直接 console。
|
||||
*/
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import pino, { Logger } from 'pino';
|
||||
import { EnvConfig } from '../../config/env.schema';
|
||||
|
||||
export type StructuredLogPayload = Record<string, unknown>;
|
||||
|
||||
@Injectable()
|
||||
export class StructuredLoggerService {
|
||||
private readonly logger: Logger;
|
||||
|
||||
constructor(private readonly config: ConfigService<EnvConfig, true>) {
|
||||
this.logger = pino({
|
||||
name: 'devops-platform-api',
|
||||
base: {
|
||||
service: 'devops-platform-api',
|
||||
env: this.config.get('NODE_ENV', { infer: true }),
|
||||
},
|
||||
level: this.config.get('NODE_ENV', { infer: true }) === 'test' ? 'silent' : 'info',
|
||||
timestamp: pino.stdTimeFunctions.isoTime,
|
||||
});
|
||||
}
|
||||
|
||||
info(payload: StructuredLogPayload, message: string): void {
|
||||
this.logger.info(payload, message);
|
||||
}
|
||||
|
||||
warn(payload: StructuredLogPayload, message: string): void {
|
||||
this.logger.warn(payload, message);
|
||||
}
|
||||
|
||||
error(payload: StructuredLogPayload, message: string): void {
|
||||
this.logger.error(payload, message);
|
||||
}
|
||||
}
|
||||
+5
-1
@@ -4,6 +4,7 @@ import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
|
||||
import { AppModule } from './app.module';
|
||||
import { ApiEnvelopeInterceptor } from './common/http/api-envelope.interceptor';
|
||||
import { AllExceptionsFilter } from './common/http/all-exceptions.filter';
|
||||
import { HttpRequestLoggingInterceptor } from './common/logging/http-request-logging.interceptor';
|
||||
import { EnvConfig } from './config/env.schema';
|
||||
|
||||
async function bootstrap(): Promise<void> {
|
||||
@@ -19,7 +20,10 @@ async function bootstrap(): Promise<void> {
|
||||
credentials: true,
|
||||
});
|
||||
app.useGlobalFilters(new AllExceptionsFilter());
|
||||
app.useGlobalInterceptors(new ApiEnvelopeInterceptor());
|
||||
app.useGlobalInterceptors(
|
||||
app.get(HttpRequestLoggingInterceptor),
|
||||
new ApiEnvelopeInterceptor(),
|
||||
);
|
||||
|
||||
const swaggerConfig = new DocumentBuilder()
|
||||
.setTitle('运维平台 API')
|
||||
|
||||
Reference in New Issue
Block a user