543 lines
12 KiB
TypeScript
543 lines
12 KiB
TypeScript
|
|
import { Injectable, Inject, Logger } from '@nestjs/common';
|
||
|
|
import type {
|
||
|
|
LoggingInterface,
|
||
|
|
StructuredLoggingInterface,
|
||
|
|
StructuredLogData,
|
||
|
|
RequestLogData,
|
||
|
|
ResponseLogData,
|
||
|
|
DatabaseQueryLogData,
|
||
|
|
CacheOperationLogData,
|
||
|
|
ExternalApiCallLogData,
|
||
|
|
BusinessEventLogData,
|
||
|
|
UserLogData,
|
||
|
|
ErrorLogData,
|
||
|
|
LoggingOptions,
|
||
|
|
} from './logging.interface';
|
||
|
|
import { LogLevel } from './logging.interface';
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 日志服务
|
||
|
|
* 基于 NestJS 官方示例实现
|
||
|
|
* 参考: https://docs.nestjs.cn/fundamentals/dependency-injection
|
||
|
|
* 对应 Java: 日志服务
|
||
|
|
*/
|
||
|
|
@Injectable()
|
||
|
|
export class LoggingService
|
||
|
|
implements LoggingInterface, StructuredLoggingInterface
|
||
|
|
{
|
||
|
|
private readonly logger = new Logger(LoggingService.name);
|
||
|
|
private currentLevel: LogLevel = LogLevel.INFO;
|
||
|
|
|
||
|
|
constructor(
|
||
|
|
@Inject('LOGGING_PROVIDER')
|
||
|
|
private readonly loggingProvider: LoggingInterface,
|
||
|
|
@Inject('STRUCTURED_LOGGING_PROVIDER')
|
||
|
|
private readonly structuredLoggingProvider: StructuredLoggingInterface,
|
||
|
|
) {}
|
||
|
|
|
||
|
|
// ==================== 基础日志接口 ====================
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 记录调试日志
|
||
|
|
*/
|
||
|
|
debug(message: string, context?: string, meta?: Record<string, any>): void {
|
||
|
|
try {
|
||
|
|
this.loggingProvider.debug(message, context, meta);
|
||
|
|
} catch (error) {
|
||
|
|
this.logger.error(`Failed to log debug message: ${message}`, error);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 记录信息日志
|
||
|
|
*/
|
||
|
|
info(message: string, context?: string, meta?: Record<string, any>): void {
|
||
|
|
try {
|
||
|
|
this.loggingProvider.info(message, context, meta);
|
||
|
|
} catch (error) {
|
||
|
|
this.logger.error(`Failed to log info message: ${message}`, error);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 记录警告日志
|
||
|
|
*/
|
||
|
|
warn(message: string, context?: string, meta?: Record<string, any>): void {
|
||
|
|
try {
|
||
|
|
this.loggingProvider.warn(message, context, meta);
|
||
|
|
} catch (error) {
|
||
|
|
this.logger.error(`Failed to log warn message: ${message}`, error);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 记录错误日志
|
||
|
|
*/
|
||
|
|
error(message: string, context?: string, meta?: Record<string, any>): void {
|
||
|
|
try {
|
||
|
|
this.loggingProvider.error(message, context, meta);
|
||
|
|
} catch (error) {
|
||
|
|
this.logger.error(`Failed to log error message: ${message}`, error);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 记录致命错误日志
|
||
|
|
*/
|
||
|
|
fatal(message: string, context?: string, meta?: Record<string, any>): void {
|
||
|
|
try {
|
||
|
|
this.loggingProvider.fatal(message, context, meta);
|
||
|
|
} catch (error) {
|
||
|
|
this.logger.error(`Failed to log fatal message: ${message}`, error);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 记录日志
|
||
|
|
*/
|
||
|
|
log(
|
||
|
|
level: LogLevel,
|
||
|
|
message: string,
|
||
|
|
context?: string,
|
||
|
|
meta?: Record<string, any>,
|
||
|
|
): void {
|
||
|
|
try {
|
||
|
|
this.loggingProvider.log(level, message, context, meta);
|
||
|
|
} catch (error) {
|
||
|
|
this.logger.error(`Failed to log message: ${message}`, error);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 设置日志级别
|
||
|
|
*/
|
||
|
|
setLevel(level: LogLevel): void {
|
||
|
|
try {
|
||
|
|
this.currentLevel = level;
|
||
|
|
this.loggingProvider.setLevel(level);
|
||
|
|
} catch (error) {
|
||
|
|
this.logger.error(`Failed to set log level: ${level}`, error);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 获取当前日志级别
|
||
|
|
*/
|
||
|
|
getLevel(): LogLevel {
|
||
|
|
return this.currentLevel;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 创建子日志器
|
||
|
|
*/
|
||
|
|
child(context: string): LoggingInterface {
|
||
|
|
try {
|
||
|
|
return this.loggingProvider.child(context);
|
||
|
|
} catch (error) {
|
||
|
|
this.logger.error(`Failed to create child logger: ${context}`, error);
|
||
|
|
return this;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// ==================== 结构化日志接口 ====================
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 记录结构化日志
|
||
|
|
*/
|
||
|
|
logStructured(
|
||
|
|
level: LogLevel,
|
||
|
|
message: string,
|
||
|
|
structuredData: StructuredLogData,
|
||
|
|
): void {
|
||
|
|
try {
|
||
|
|
this.structuredLoggingProvider.logStructured(
|
||
|
|
level,
|
||
|
|
message,
|
||
|
|
structuredData,
|
||
|
|
);
|
||
|
|
} catch (error) {
|
||
|
|
this.logger.error(`Failed to log structured message: ${message}`, error);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 记录请求日志
|
||
|
|
*/
|
||
|
|
logRequest(
|
||
|
|
request: RequestLogData,
|
||
|
|
response: ResponseLogData,
|
||
|
|
duration: number,
|
||
|
|
): void {
|
||
|
|
try {
|
||
|
|
this.structuredLoggingProvider.logRequest(request, response, duration);
|
||
|
|
} catch (error) {
|
||
|
|
this.logger.error('Failed to log request', error);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 记录数据库查询日志
|
||
|
|
*/
|
||
|
|
logDatabaseQuery(
|
||
|
|
query: DatabaseQueryLogData,
|
||
|
|
duration: number,
|
||
|
|
result?: any,
|
||
|
|
): void {
|
||
|
|
try {
|
||
|
|
this.structuredLoggingProvider.logDatabaseQuery(query, duration, result);
|
||
|
|
} catch (error) {
|
||
|
|
this.logger.error('Failed to log database query', error);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 记录缓存操作日志
|
||
|
|
*/
|
||
|
|
logCacheOperation(
|
||
|
|
operation: CacheOperationLogData,
|
||
|
|
hit: boolean,
|
||
|
|
duration: number,
|
||
|
|
): void {
|
||
|
|
try {
|
||
|
|
this.structuredLoggingProvider.logCacheOperation(
|
||
|
|
operation,
|
||
|
|
hit,
|
||
|
|
duration,
|
||
|
|
);
|
||
|
|
} catch (error) {
|
||
|
|
this.logger.error('Failed to log cache operation', error);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 记录外部API调用日志
|
||
|
|
*/
|
||
|
|
logExternalApiCall(
|
||
|
|
apiCall: ExternalApiCallLogData,
|
||
|
|
response: any,
|
||
|
|
duration: number,
|
||
|
|
): void {
|
||
|
|
try {
|
||
|
|
this.structuredLoggingProvider.logExternalApiCall(
|
||
|
|
apiCall,
|
||
|
|
response,
|
||
|
|
duration,
|
||
|
|
);
|
||
|
|
} catch (error) {
|
||
|
|
this.logger.error('Failed to log external API call', error);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 记录业务事件日志
|
||
|
|
*/
|
||
|
|
logBusinessEvent(
|
||
|
|
event: BusinessEventLogData,
|
||
|
|
user?: UserLogData,
|
||
|
|
meta?: Record<string, any>,
|
||
|
|
): void {
|
||
|
|
try {
|
||
|
|
this.structuredLoggingProvider.logBusinessEvent(event, user, meta);
|
||
|
|
} catch (error) {
|
||
|
|
this.logger.error('Failed to log business event', error);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// ==================== 装饰器支持 ====================
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 日志装饰器实现
|
||
|
|
*/
|
||
|
|
async logMethod<T>(
|
||
|
|
options: LoggingOptions,
|
||
|
|
fn: () => T | Promise<T>,
|
||
|
|
args: any[] = [],
|
||
|
|
): Promise<T> {
|
||
|
|
const {
|
||
|
|
level = LogLevel.INFO,
|
||
|
|
context = 'Method',
|
||
|
|
logArgs = false,
|
||
|
|
logResult = false,
|
||
|
|
logDuration = true,
|
||
|
|
logError = true,
|
||
|
|
enabled = true,
|
||
|
|
message,
|
||
|
|
meta = {},
|
||
|
|
} = options;
|
||
|
|
|
||
|
|
if (!enabled) {
|
||
|
|
return await fn();
|
||
|
|
}
|
||
|
|
|
||
|
|
const startTime = Date.now();
|
||
|
|
const logMessage = message || `${context} execution`;
|
||
|
|
const logMeta = { ...meta };
|
||
|
|
|
||
|
|
try {
|
||
|
|
// 记录方法开始
|
||
|
|
if (logArgs) {
|
||
|
|
logMeta.args = args;
|
||
|
|
}
|
||
|
|
|
||
|
|
this.log(level, `${logMessage} started`, context, logMeta);
|
||
|
|
|
||
|
|
// 执行方法
|
||
|
|
const result = await fn();
|
||
|
|
|
||
|
|
// 记录方法完成
|
||
|
|
const duration = Date.now() - startTime;
|
||
|
|
const successMeta = { ...logMeta };
|
||
|
|
|
||
|
|
if (logResult) {
|
||
|
|
successMeta.result = result;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (logDuration) {
|
||
|
|
successMeta.duration = duration;
|
||
|
|
}
|
||
|
|
|
||
|
|
this.log(level, `${logMessage} completed`, context, successMeta);
|
||
|
|
|
||
|
|
return result;
|
||
|
|
} catch (error) {
|
||
|
|
const duration = Date.now() - startTime;
|
||
|
|
const errorMeta = { ...logMeta };
|
||
|
|
|
||
|
|
if (logDuration) {
|
||
|
|
errorMeta.duration = duration;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (logError) {
|
||
|
|
errorMeta.error = this.serializeError(error);
|
||
|
|
}
|
||
|
|
|
||
|
|
this.error(`${logMessage} failed`, context, errorMeta);
|
||
|
|
throw error;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// ==================== 工具方法 ====================
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 序列化错误
|
||
|
|
*/
|
||
|
|
private serializeError(error: any): ErrorLogData {
|
||
|
|
if (error instanceof Error) {
|
||
|
|
return {
|
||
|
|
name: error.name,
|
||
|
|
message: error.message,
|
||
|
|
stack: error.stack,
|
||
|
|
code: (error as any).code,
|
||
|
|
cause: (error as any).cause,
|
||
|
|
context: (error as any).context,
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
return {
|
||
|
|
name: 'UnknownError',
|
||
|
|
message: String(error),
|
||
|
|
context: { originalError: error },
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 记录HTTP请求
|
||
|
|
*/
|
||
|
|
logHttpRequest(
|
||
|
|
method: string,
|
||
|
|
url: string,
|
||
|
|
statusCode: number,
|
||
|
|
duration: number,
|
||
|
|
userAgent?: string,
|
||
|
|
ip?: string,
|
||
|
|
userId?: string,
|
||
|
|
): void {
|
||
|
|
const request: RequestLogData = {
|
||
|
|
method,
|
||
|
|
url,
|
||
|
|
headers: {},
|
||
|
|
ip,
|
||
|
|
userAgent,
|
||
|
|
userId,
|
||
|
|
};
|
||
|
|
|
||
|
|
const response: ResponseLogData = {
|
||
|
|
statusCode,
|
||
|
|
headers: {},
|
||
|
|
};
|
||
|
|
|
||
|
|
this.logRequest(request, response, duration);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 记录数据库操作
|
||
|
|
*/
|
||
|
|
logDatabaseOperation(
|
||
|
|
operation: string,
|
||
|
|
table: string,
|
||
|
|
query: string,
|
||
|
|
duration: number,
|
||
|
|
params?: any[],
|
||
|
|
result?: any,
|
||
|
|
): void {
|
||
|
|
const queryData: DatabaseQueryLogData = {
|
||
|
|
operation,
|
||
|
|
table,
|
||
|
|
query,
|
||
|
|
params,
|
||
|
|
};
|
||
|
|
|
||
|
|
this.logDatabaseQuery(queryData, duration, result);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 记录缓存操作
|
||
|
|
*/
|
||
|
|
logCacheOperationSimple(
|
||
|
|
operation: string,
|
||
|
|
key: string,
|
||
|
|
hit: boolean,
|
||
|
|
duration: number,
|
||
|
|
ttl?: number,
|
||
|
|
size?: number,
|
||
|
|
): void {
|
||
|
|
const operationData: CacheOperationLogData = {
|
||
|
|
operation,
|
||
|
|
key,
|
||
|
|
ttl,
|
||
|
|
size,
|
||
|
|
};
|
||
|
|
|
||
|
|
this.logCacheOperation(operationData, hit, duration);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 记录外部API调用
|
||
|
|
*/
|
||
|
|
logExternalApiCallSimple(
|
||
|
|
service: string,
|
||
|
|
endpoint: string,
|
||
|
|
method: string,
|
||
|
|
statusCode: number,
|
||
|
|
duration: number,
|
||
|
|
headers?: Record<string, string>,
|
||
|
|
body?: any,
|
||
|
|
): void {
|
||
|
|
const apiCall: ExternalApiCallLogData = {
|
||
|
|
service,
|
||
|
|
endpoint,
|
||
|
|
method,
|
||
|
|
headers: headers || {},
|
||
|
|
body,
|
||
|
|
};
|
||
|
|
|
||
|
|
const response = { statusCode };
|
||
|
|
this.logExternalApiCall(apiCall, response, duration);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 记录业务事件
|
||
|
|
*/
|
||
|
|
logBusinessEventSimple(
|
||
|
|
event: string,
|
||
|
|
action: string,
|
||
|
|
resource: string,
|
||
|
|
resourceId?: string,
|
||
|
|
result?: 'success' | 'failure' | 'partial',
|
||
|
|
data?: Record<string, any>,
|
||
|
|
userId?: string,
|
||
|
|
): void {
|
||
|
|
const eventData: BusinessEventLogData = {
|
||
|
|
event,
|
||
|
|
action,
|
||
|
|
resource,
|
||
|
|
resourceId,
|
||
|
|
result,
|
||
|
|
data,
|
||
|
|
};
|
||
|
|
|
||
|
|
const userData: UserLogData | undefined = userId
|
||
|
|
? { id: userId }
|
||
|
|
: undefined;
|
||
|
|
this.logBusinessEvent(eventData, userData);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 记录性能指标
|
||
|
|
*/
|
||
|
|
logPerformance(
|
||
|
|
operation: string,
|
||
|
|
duration: number,
|
||
|
|
context?: string,
|
||
|
|
meta?: Record<string, any>,
|
||
|
|
): void {
|
||
|
|
const performanceMeta = {
|
||
|
|
operation,
|
||
|
|
duration,
|
||
|
|
...meta,
|
||
|
|
};
|
||
|
|
|
||
|
|
if (duration > 1000) {
|
||
|
|
this.warn(`Slow operation: ${operation}`, context, performanceMeta);
|
||
|
|
} else if (duration > 500) {
|
||
|
|
this.info(`Operation: ${operation}`, context, performanceMeta);
|
||
|
|
} else {
|
||
|
|
this.debug(`Operation: ${operation}`, context, performanceMeta);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 记录安全事件
|
||
|
|
*/
|
||
|
|
logSecurityEvent(
|
||
|
|
event: string,
|
||
|
|
severity: 'low' | 'medium' | 'high' | 'critical',
|
||
|
|
context?: string,
|
||
|
|
meta?: Record<string, any>,
|
||
|
|
): void {
|
||
|
|
const securityMeta = {
|
||
|
|
event,
|
||
|
|
severity,
|
||
|
|
...meta,
|
||
|
|
};
|
||
|
|
|
||
|
|
switch (severity) {
|
||
|
|
case 'critical':
|
||
|
|
this.fatal(`Security event: ${event}`, context, securityMeta);
|
||
|
|
break;
|
||
|
|
case 'high':
|
||
|
|
this.error(`Security event: ${event}`, context, securityMeta);
|
||
|
|
break;
|
||
|
|
case 'medium':
|
||
|
|
this.warn(`Security event: ${event}`, context, securityMeta);
|
||
|
|
break;
|
||
|
|
case 'low':
|
||
|
|
this.info(`Security event: ${event}`, context, securityMeta);
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 记录审计日志
|
||
|
|
*/
|
||
|
|
logAudit(
|
||
|
|
action: string,
|
||
|
|
resource: string,
|
||
|
|
resourceId?: string,
|
||
|
|
userId?: string,
|
||
|
|
result?: 'success' | 'failure',
|
||
|
|
meta?: Record<string, any>,
|
||
|
|
): void {
|
||
|
|
const auditMeta = {
|
||
|
|
action,
|
||
|
|
resource,
|
||
|
|
resourceId,
|
||
|
|
userId,
|
||
|
|
result,
|
||
|
|
...meta,
|
||
|
|
};
|
||
|
|
|
||
|
|
this.info(`Audit: ${action} on ${resource}`, 'audit', auditMeta);
|
||
|
|
}
|
||
|
|
}
|