mirror of
https://github.com/crystelf/crystelf-core.git
synced 2025-12-05 10:31:56 +00:00
feat:保存调用日志
This commit is contained in:
parent
c2194e6140
commit
fc2ffeb145
108
src/common/interceptors/request-log.interceptor.ts
Normal file
108
src/common/interceptors/request-log.interceptor.ts
Normal file
@ -0,0 +1,108 @@
|
||||
import {
|
||||
CallHandler,
|
||||
ExecutionContext,
|
||||
Injectable,
|
||||
Logger,
|
||||
NestInterceptor,
|
||||
} from '@nestjs/common';
|
||||
import { Observable } from 'rxjs';
|
||||
import { tap } from 'rxjs/operators';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
|
||||
interface RequestLogEntry {
|
||||
timestamp: string;
|
||||
ip: string | string[] | undefined;
|
||||
method: string;
|
||||
url: string;
|
||||
controller: string;
|
||||
handler: string;
|
||||
userAgent?: string;
|
||||
params?: any;
|
||||
query?: any;
|
||||
body?: any;
|
||||
statusCode?: number;
|
||||
durationMs?: number;
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class RequestLogInterceptor implements NestInterceptor {
|
||||
private readonly logger = new Logger(RequestLogInterceptor.name);
|
||||
|
||||
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
|
||||
const http = context.switchToHttp();
|
||||
const req = http.getRequest();
|
||||
const res = http.getResponse();
|
||||
|
||||
const now = Date.now();
|
||||
const controller = context.getClass().name;
|
||||
const handler = context.getHandler().name;
|
||||
const ip = req.headers['x-forwarded-for'] || req.ip;
|
||||
const method = req.method;
|
||||
const url = req.originalUrl || req.url;
|
||||
const userAgent = req.headers['user-agent'];
|
||||
this.logger.log(
|
||||
`${method} ${url} - ${controller}.${handler} - ip=${Array.isArray(ip) ? ip[0] : ip}`,
|
||||
);
|
||||
|
||||
return next.handle().pipe(
|
||||
tap({
|
||||
next: () => {
|
||||
this.writeLog({
|
||||
timestamp: new Date(now).toISOString(),
|
||||
ip,
|
||||
method,
|
||||
url,
|
||||
controller,
|
||||
handler,
|
||||
userAgent,
|
||||
params: req.params,
|
||||
query: req.query,
|
||||
body: req.body,
|
||||
statusCode: res.statusCode,
|
||||
durationMs: Date.now() - now,
|
||||
});
|
||||
},
|
||||
error: () => {
|
||||
this.writeLog({
|
||||
timestamp: new Date(now).toISOString(),
|
||||
ip,
|
||||
method,
|
||||
url,
|
||||
controller,
|
||||
handler,
|
||||
userAgent,
|
||||
params: req.params,
|
||||
query: req.query,
|
||||
body: req.body,
|
||||
statusCode: res.statusCode,
|
||||
durationMs: Date.now() - now,
|
||||
});
|
||||
},
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
private writeLog(entry: RequestLogEntry) {
|
||||
try {
|
||||
const baseDir = path.resolve(process.cwd(), 'logs');
|
||||
if (!fs.existsSync(baseDir)) {
|
||||
fs.mkdirSync(baseDir, { recursive: true });
|
||||
}
|
||||
const fileName = `access-${this.getDateStr()}.jsonl`;
|
||||
const filePath = path.join(baseDir, fileName);
|
||||
const serialized = JSON.stringify(entry) + '\n';
|
||||
fs.appendFile(filePath, serialized, { encoding: 'utf8' }, () => {});
|
||||
} catch (err) {
|
||||
this.logger.warn(`写入访问日志失败: ${err?.message || err}`);
|
||||
}
|
||||
}
|
||||
|
||||
private getDateStr(): string {
|
||||
const d = new Date();
|
||||
const yyyy = d.getFullYear();
|
||||
const mm = String(d.getMonth() + 1).padStart(2, '0');
|
||||
const dd = String(d.getDate()).padStart(2, '0');
|
||||
return `${yyyy}-${mm}-${dd}`;
|
||||
}
|
||||
}
|
||||
@ -3,6 +3,7 @@ import { AppModule } from './app.module';
|
||||
import { Logger, RequestMethod } from '@nestjs/common';
|
||||
import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';
|
||||
import { ResponseInterceptor } from './common/interceptors/response.interceptor';
|
||||
import { RequestLogInterceptor } from './common/interceptors/request-log.interceptor';
|
||||
import { AllExceptionsFilter } from './common/filters/all-exception.filter';
|
||||
import { SystemService } from './core/system/system.service';
|
||||
import { WsAdapter } from '@nestjs/platform-ws';
|
||||
@ -35,7 +36,7 @@ async function bootstrap() {
|
||||
{ path: 'public/(.*)', method: RequestMethod.ALL },
|
||||
],
|
||||
});
|
||||
app.useGlobalInterceptors(new ResponseInterceptor());
|
||||
app.useGlobalInterceptors(new RequestLogInterceptor(), new ResponseInterceptor());
|
||||
app.useGlobalFilters(new AllExceptionsFilter());
|
||||
const systemService = app.get(SystemService);
|
||||
const restartDuration = systemService.checkRestartTime();
|
||||
|
||||
@ -101,7 +101,7 @@ export class WordsService {
|
||||
const safeType = this.safePathSegment(type);
|
||||
const safeName = this.safePathSegment(name);
|
||||
const cacheKey = `${safeType}/${safeName}`;
|
||||
this.logger.log(`重载文案: ${cacheKey}..`);
|
||||
//this.logger.log(`重载文案: ${cacheKey}..`);
|
||||
const filePath = path.join(
|
||||
this.paths.get('words'),
|
||||
safeType,
|
||||
@ -136,7 +136,7 @@ export class WordsService {
|
||||
const names = files
|
||||
.filter((f) => f.isFile() && f.name.endsWith('.json'))
|
||||
.map((f) => f.name.replace(/\.json$/, ''));
|
||||
this.logger.log(`扫描文案类型 ${safeType} 下的文件: ${names.join(', ')}`);
|
||||
//this.logger.log(`扫描文案类型 ${safeType} 下的文件: ${names.join(', ')}`);
|
||||
return names;
|
||||
} catch (e) {
|
||||
this.logger.error(`读取文案目录失败: ${safeType}`, e);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user