2025年9月3日
28 分钟阅读

Nest搭建(2)

nest主要组件

Nest搭建(2)

中间件

  • log
typescript
import { Injectable, NestMiddleware } from '@nestjs/common'; import { Request, Response, NextFunction } from 'express'; @Injectable() export class LoggerMiddleware implements NestMiddleware { use(req: Request, res: Response, next: NextFunction) { console.log('Request...'); next(); } }
typescript
import { Module, NestModule, MiddlewareConsumer } from '@nestjs/common'; import { LoggerMiddleware } from './common/middleware/logger.middleware'; import { CatsModule } from './cats/cats.module'; @Module({ imports: [CatsModule], }) export class AppModule implements NestModule { configure(consumer: MiddlewareConsumer) { consumer .apply(LoggerMiddleware) .forRoutes('cats'); } }

使用log4js

config

typescript
import * as path from 'path'; const baseLogPath = 'logs'; const log4jsConfig = { appenders: { console: { type: 'console', //打印到控制台 }, access: { type: 'dateFile', //会写入文件,并且按照日期分类 filename: `${baseLogPath}/access/access.log`, //日志文件名,会命名为:access.当前时间.log alwaysIncludePattern: true, pattern: 'yyyyMMdd', //时间格式 daysToKeep: 60, numBackups: 3, category: 'http', keepFileExt: true, //是否保留文件后缀 }, app: { type: 'dateFile', filename: `${baseLogPath}/app-out/app.log`, alwaysIncludePattern: true, layout: { type: 'pattern', pattern: '{"date":"%d","level":"%p","category":"%c","host":"%h","pid":"%z","data":\'%m\'}', }, //日志文件按日期切割 pattern: 'yyyyMMdd', daysToKeep: 60, numBackups: 3, keepFileExt: true, }, errorFile: { type: 'dateFile', filename: `${baseLogPath}/errors/error.log`, alwaysIncludePattern: true, layout: { type: 'pattern', pattern: '{"date":"%d","level":"%p","category":"%c","host":"%h","pid":"%z","data":\'%m\'}', }, //日志文件按日期切割 pattern: 'yyyyMMdd', daysToKeep: 60, numBackups: 3, keepFileExt: true, }, errors: { type: 'logLevelFilter', level: 'ERROR', appender: 'errorFile', }, }, categories: { default: { appenders: ['console', 'app', 'errors'], level: 'DEBUG', }, info: { appenders: ['console', 'app', 'errors'], level: 'info' }, access: { appenders: ['console', 'app', 'errors'], level: 'info' }, http: { appenders: ['access'], level: 'DEBUG' }, }, pm2: true, //使用pm2来管理项目时打开 pm2InstanceVar: 'INSTANCE_ID', // 会根据 pm2 分配的 id 进行区分,以免各进程在写日志时造成冲突 }; export default log4jsConfig;

log4js

typescript
import * as Path from 'path'; import * as Log4js from 'log4js'; import * as Util from 'util'; import * as Moment from 'dayjs'; // 处理时间的工具 import * as StackTrace from 'stacktrace-js'; import Chalk from 'chalk'; import config from '../../config/log4js'; //日志级别 export enum LoggerLevel{ ALL = 'ALL', MARK = 'MARK', TRACE = 'TRACE', DEBUG = 'DEBUG', INFO = 'INFO', WARN = 'WARN', ERROR = 'ERROR', FATAL = 'FATAL', OFF = 'OFF', } // 内容跟踪类 export class ContextTrace { constructor( public readonly context: string, public readonly path?: string, public readonly lineNumber?: number, public readonly columnNumber?: number ) { } } Log4js.addLayout('Awesome-nest', (logConfig: any) => { return (logEvent: Log4js.LoggingEvent): string => { let moduleName: string = ''; let position: string = ''; //日志组装 const messageList: string[] = []; logEvent.data.forEach((value: any) => { if (value instanceof ContextTrace) { moduleName = value.context; //显示触发日志的坐标(行/列) if (value.lineNumber && value.columnNumber) { position = `${value.lineNumber},${value.columnNumber}`; } return; } if (typeof value !== 'string') { value = Util.inspect(value, false, 3, true); } messageList.push(value); }); //日志组成部分 const messageOutput: string = messageList.join(' '); const positionOutput: string = position ? `[${position}]` : ''; const typeOutput: string = `[${logConfig.type}]${logEvent.pid.toString()} - `; const dateOutput: string = `${Moment(logEvent.startTime).format('YYYY-MM-DD HH:mm:ss')}`; const moduleOutput: string = moduleName ? `[${moduleName}]` : '[LoggerService]'; let levelOutput: string = `[${logEvent.level}]${messageOutput}`; //根据日志级别,用不同颜色区分 switch (logEvent.level.toString()) { case LoggerLevel.DEBUG: levelOutput = Chalk.green(levelOutput); break; case LoggerLevel.INFO: levelOutput = Chalk.cyan(levelOutput); break; case LoggerLevel.WARN: levelOutput = Chalk.yellow(levelOutput); break; case LoggerLevel.ERROR: levelOutput = Chalk.red(levelOutput); break; case LoggerLevel.FATAL: levelOutput = Chalk.hex('#DD4C35')(levelOutput); break; default: levelOutput = Chalk.grey(levelOutput); break; } return `${Chalk.green(typeOutput)} ${dateOutput} ${Chalk.yellow(moduleOutput)}` } }) // 注入配置 Log4js.configure(config); //实例化 const logger = Log4js.getLogger(); logger.level = LoggerLevel.TRACE; export class Logger { static trace(...args) { logger.trace(Logger.getStackTrace(), ...args); } static debug(...args) { logger.debug(Logger.getStackTrace(), ...args); } static log(...args) { logger.info(Logger.getStackTrace(), ...args); } static info(...args) { logger.info(Logger.getStackTrace(), ...args); } static warn(...args) { logger.warn(Logger.getStackTrace(), ...args); } static warning(...args) { logger.warn(Logger.getStackTrace(), ...args); } static error(...args) { logger.error(Logger.getStackTrace(), ...args); } static fatal(...args) { logger.fatal(Logger.getStackTrace(), ...args); } static access(...args) { const loggerCustom = Log4js.getLogger('http'); loggerCustom.info(Logger.getStackTrace(), ...args); } // 日志追踪,可以追溯到哪个文件、第几行第几列 static getStackTrace(deep: number = 2): string { const stackList: StackTrace.StackFrame[] = StackTrace.getSync(); const stackInfo: StackTrace.StackFrame = stackList[deep]; if (!stackInfo) return `no stack information`; const lineNumber: number = stackInfo.lineNumber || 0; const columnNumber: number = stackInfo.columnNumber|| 0; const fileName: string = stackInfo.fileName || ''; const basename: string = Path.basename(fileName); return `${basename}(line: ${lineNumber}, column: ${columnNumber}): \n`; } }

logger.middleware

typescript
import { Injectable, NestMiddleware } from "@nestjs/common"; import { NextFunction, Request, Response } from "express"; import { Logger } from '../utils/log4js'; export function logger(req: Request, res: Response, next: NextFunction) { const code = res.statusCode; next(); // 组装日志信息 const logFormat = `Method:${req.method} Request original url: ${req.originalUrl} IP:${req.ip} Status code:${code} ${JSON.stringify(req.body)} `; // 根据状态码进行日志类型区分 if (code >= 500) { Logger.error(logFormat); } else if (code >= 400) { Logger.warn(logFormat); } else { Logger.access(logFormat); Logger.log(logFormat); } }

interceptor

typescript
import { CallHandler, ExecutionContext, Injectable, NestInterceptor } from "@nestjs/common"; import { map } from "rxjs"; import { ResponseEnum } from "../enum/response.enum"; import { BaseResponse } from "../utils/baseResponse.entity"; import { Logger } from "../utils/log4js"; @Injectable() export class ResponseInterceptor implements NestInterceptor { intercept(context: ExecutionContext, next: CallHandler) { const req = context.getArgByIndex(1).req; return next.handle().pipe( map((data) => { let res; if (data instanceof BaseResponse) { res = data; } else if (data instanceof ResponseEnum) { res = new BaseResponse(data.data, data.code, data.message); } else { res = new BaseResponse(data); Logger.info(`Request original url: ${req.originalUrl} Method: ${req.method} IP: ${req.ip} UserId: ${JSON.stringify(req.userId)} ${JSON.stringify(res)} `); } return res; } ), ); } }

filter

typescript
import { ArgumentsHost, BadRequestException, Catch, ExceptionFilter, HttpException, HttpStatus, UnauthorizedException, } from '@nestjs/common'; import { Request, Response } from 'express'; import { Logger } from '../utils/log4js'; @Catch() export class CommonExceptionFilter implements ExceptionFilter { catch(exception: Error, host: ArgumentsHost) { const ctx = host.switchToHttp(); const response = ctx.getResponse<Response>(); const req = ctx.getRequest<Request>(); let status; let logFormat; switch (true) { case exception instanceof UnauthorizedException: { status = HttpStatus.UNAUTHORIZED; logFormat = `Request original url: ${req.originalUrl} Method: ${req.method} IP: ${req.ip}}`; break; } case exception instanceof BadRequestException: { if (exception instanceof BadRequestException) { status = exception.getStatus(); const badRequestException = exception.getResponse(); let message; if (typeof badRequestException === 'string') { message = badRequestException; } else if (badRequestException.hasOwnProperty('message')) { message = badRequestException['message'][0]; } exception.message = message; logFormat = `Request original url: ${req.originalUrl} Method: ${req.method} IP: ${req.ip}} ${message} ${exception.stack}`; } break; } case exception instanceof HttpException: { if (exception instanceof HttpException) status = exception.getStatus(); logFormat = `Request original url: ${req.originalUrl} Method: ${req.method} IP: ${req.ip}} ${exception.stack}`; break; } default: { status = HttpStatus.INTERNAL_SERVER_ERROR; logFormat = `Request original url: ${req.originalUrl} Method: ${req.method} IP: ${req.ip}} ${exception.stack}`; } } Logger.error(logFormat); response.status(status).json({ code: status, msg: exception.message, data: '', }); } }
评论区 (0)
你的临时ID:
暂无评论,来发表第一条评论吧!