Nest搭建(2)
nest主要组件

中间件
- log
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();
}
}
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
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
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 ? `''` : '';
const typeOutput: string = `''${logEvent.pid.toString()} - `;
const dateOutput: string = `${Moment(logEvent.startTime).format('YYYY-MM-DD HH:mm:ss')}`;
const moduleOutput: string = moduleName ? `''` : '[LoggerService]';
let levelOutput: string = `''${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
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
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
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)