mirror of
https://github.com/crystelf/crystelf-core.git
synced 2025-12-05 18:41: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 { Logger, RequestMethod } from '@nestjs/common';
|
||||||
import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';
|
import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';
|
||||||
import { ResponseInterceptor } from './common/interceptors/response.interceptor';
|
import { ResponseInterceptor } from './common/interceptors/response.interceptor';
|
||||||
|
import { RequestLogInterceptor } from './common/interceptors/request-log.interceptor';
|
||||||
import { AllExceptionsFilter } from './common/filters/all-exception.filter';
|
import { AllExceptionsFilter } from './common/filters/all-exception.filter';
|
||||||
import { SystemService } from './core/system/system.service';
|
import { SystemService } from './core/system/system.service';
|
||||||
import { WsAdapter } from '@nestjs/platform-ws';
|
import { WsAdapter } from '@nestjs/platform-ws';
|
||||||
@ -35,7 +36,7 @@ async function bootstrap() {
|
|||||||
{ path: 'public/(.*)', method: RequestMethod.ALL },
|
{ path: 'public/(.*)', method: RequestMethod.ALL },
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
app.useGlobalInterceptors(new ResponseInterceptor());
|
app.useGlobalInterceptors(new RequestLogInterceptor(), new ResponseInterceptor());
|
||||||
app.useGlobalFilters(new AllExceptionsFilter());
|
app.useGlobalFilters(new AllExceptionsFilter());
|
||||||
const systemService = app.get(SystemService);
|
const systemService = app.get(SystemService);
|
||||||
const restartDuration = systemService.checkRestartTime();
|
const restartDuration = systemService.checkRestartTime();
|
||||||
|
|||||||
@ -101,7 +101,7 @@ export class WordsService {
|
|||||||
const safeType = this.safePathSegment(type);
|
const safeType = this.safePathSegment(type);
|
||||||
const safeName = this.safePathSegment(name);
|
const safeName = this.safePathSegment(name);
|
||||||
const cacheKey = `${safeType}/${safeName}`;
|
const cacheKey = `${safeType}/${safeName}`;
|
||||||
this.logger.log(`重载文案: ${cacheKey}..`);
|
//this.logger.log(`重载文案: ${cacheKey}..`);
|
||||||
const filePath = path.join(
|
const filePath = path.join(
|
||||||
this.paths.get('words'),
|
this.paths.get('words'),
|
||||||
safeType,
|
safeType,
|
||||||
@ -136,7 +136,7 @@ export class WordsService {
|
|||||||
const names = files
|
const names = files
|
||||||
.filter((f) => f.isFile() && f.name.endsWith('.json'))
|
.filter((f) => f.isFile() && f.name.endsWith('.json'))
|
||||||
.map((f) => f.name.replace(/\.json$/, ''));
|
.map((f) => f.name.replace(/\.json$/, ''));
|
||||||
this.logger.log(`扫描文案类型 ${safeType} 下的文件: ${names.join(', ')}`);
|
//this.logger.log(`扫描文案类型 ${safeType} 下的文件: ${names.join(', ')}`);
|
||||||
return names;
|
return names;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.logger.error(`读取文案目录失败: ${safeType}`, e);
|
this.logger.error(`读取文案目录失败: ${safeType}`, e);
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user