Nestjs 使用log4js实现日志输出

发布时间:2023年12月28日


log4js默认的日志级别如下:ALL < TRACE < DEBUG < INFO < WARN < ERROR < FATAL < MARK < OFF


1.安装
yarn add log4js
yarn add stacktrace-js


2.创建目录
/src/config/logConfig.ts? 复制下面
import * as path from "path";
const baseLogPath = path.resolve(__dirname, "../../logs"); // 日志根目录,视情况而定,这里将于项目同级

const log4jsConfig = {
? appenders: {
? ? console: {
? ? ? type: "console", // 控制台打印
? ? },
? ? access: {
? ? ? type: "dateFile", // 写入按日期分类文件
? ? ? filename: `${baseLogPath}/access/access.log`, // 日志名称,会加上pattern格式的日期
? ? ? alwaysIncludePattern: true,
? ? ? pattern: "yyyy-MM-dd",
? ? ? daysToKeep: 7, //保存天数
? ? ? numBackups: 3, // 日志文件
? ? ? category: "http", //category 类型
? ? ? keepFileExt: true, // 文件后缀
? ? ? compress: true, // 压缩
? ? },
? ? app: {
? ? ? type: "dateFile",
? ? ? filename: `${baseLogPath}/apps/app.log`,
? ? ? alwaysIncludePattern: true,
? ? ? layout: {
? ? ? ? type: "pattern",
? ? ? ? pattern:
? ? ? ? ? '{"date":"%d","level":"%p","category":"%c","host":"%h","pid":"%z","data":\'%m\'}',
? ? ? },
? ? ? maxLogSize: 10485760,
? ? ? pattern: "yyyy-MM-dd",
? ? ? daysToKeep: 7,
? ? ? 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: "yyyy-MM-dd",
? ? ? daysToKeep: 7,
? ? ? 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时,此演示项目将通过云服务器Linux pm2部署
? pm2InstanceVar: "INSTANCE_ID", // 根据pm2 id分配
};

export default log4jsConfig;

3.定义 log4js
/src/common/log4js/index.ts? 复制下面
import * as Path from 'path';
import * as Log4js from 'log4js';
import * as Util from 'util';
import { format } from '../../utils/moment';
import * as StackTrace from 'stacktrace-js';
import Chalk from 'chalk';
import log4jsConfig from '../../config/logConfig';

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 = '';
? ? let position = '';

? ? // 日志组装
? ? 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 = `[${logConfig.type}] ${logEvent.pid.toString()} ? - `;
? ? const dateOutput = `${format(
? ? ? logEvent.startTime,
? ? ? 'YYYY-MM-D:HH:mm:ss',
? ? ? false,
? ? )}`;
? ? const moduleOutput: string = moduleName
? ? ? ? `[${moduleName}] `
? ? ? : '[LoggerService] ';
? ? let levelOutput = `[${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,
? ? )}${levelOutput}${positionOutput}`;
? };
});

// 注入配置
Log4js.configure(log4jsConfig);

// 实例化
const logger = Log4js.getLogger();
logger.level = LoggerLevel.TRACE;

export class Logger {
? static trace(...args: any[]) {
? ? logger.trace(Logger.getStackTrace(), ...args);
? }

? static debug(...args: any[]) {
? ? logger.debug(Logger.getStackTrace(), ...args);
? }

? static log(...args: any[]) {
? ? logger.info(Logger.getStackTrace(), ...args);
? }

? static info(...args: any[]) {
? ? logger.info(Logger.getStackTrace(), ...args);
? }

? static warn(...args: any[]) {
? ? logger.warn(Logger.getStackTrace(), ...args);
? }

? static warning(...args: any[]) {
? ? logger.warn(Logger.getStackTrace(), ...args);
? }

? static error(...args: any[]) {
? ? logger.error(Logger.getStackTrace(), ...args);
? }

? static fatal(...args: any[]) {
? ? logger.fatal(Logger.getStackTrace(), ...args);
? }

? static access(...args: any[]) {
? ? const loggerCustom = Log4js.getLogger('http');
? ? loggerCustom.info(Logger.getStackTrace(), ...args);
? }

? // StackTrace追溯日志发生在哪个文件,行列 user.service.ts(line: 17, column: 12)
? static getStackTrace(deep = 2): string {
? ? const stackList: StackTrace.StackFrame[] = StackTrace.getSync();
? ? const stackInfo: StackTrace.StackFrame = stackList[deep];

? ? const lineNumber: number = stackInfo.lineNumber;
? ? const columnNumber: number = stackInfo.columnNumber;
? ? const fileName: string = stackInfo.fileName;
? ? const basename: string = Path.basename(fileName);
? ? return `${basename}(line: ${lineNumber}, column: ${columnNumber}): \n`;
? }
}


4. 创建/utils/_initernal/isType.ts
const isType = function(o) {
? let s = Object.prototype.toString.call(o);
? return s.match(/\[object (.*?)\]/)[1].toLowerCase();
};

export const isPrimitive = (o) => {
? let name = isType(o);
? return (
? ? name === "string" ||
? ? name === "number" ||
? ? name === "symbol" ||
? ? name === "boolean"
? );
};
export const isDate = (o) => {
? return isType(o) === "date";
};
export const isNumber = (o) => {
? return isType(o) === "number";
};
export const isString = (o) => {
? return isType(o) === "string";
};
export const isObject = (o) => {
? return isType(o) === "object";
};
export const isArray = (o) => {
? return isType(o) === "array";
};
export const isBuffer = (o) => {
? return isType(o) === "buffer";
};

5.创建/utils/moment.ts
import { isDate, isString, isNumber } from "./_initernal/isType";

const format = (date: any, format: string, isUTC: any) => {
? if (!isNumber(date) && !isString(date) && !isDate(date)) {
? ? throw new Error("The first parameter must be number, string, Date object");
? }

? let d = date;

? if (isNumber(date) || isString(date)) {
? ? d = new Date(date);
? }

? if (!isString(format)) {
? ? return d.toString();
? }
? const year = isUTC
? ? ? d.getUTCFullYear().toString()
? ? : d.getFullYear().toString();
? const month = isUTC
? ? ? (d.getUTCMonth() + 1).toString()
? ? : (d.getMonth() + 1).toString();
? const day = isUTC ? d.getUTCDate().toString() : d.getDate().toString();
? const hour = isUTC ? d.getUTCHours().toString() : d.getHours().toString();
? const hour12 = (hour % 12).toString();
? const amOrPm = hour < 12 ? "AM" : "PM";
? const minute = isUTC
? ? ? d.getUTCMinutes().toString()
? ? : d.getMinutes().toString();
? const second = isUTC
? ? ? d.getUTCSeconds().toString()
? ? : d.getSeconds().toString();
? const millisecond = isUTC
? ? ? d.getUTCMilliseconds().toString()
? ? : d.getMilliseconds().toString();

? return format
? ? .replace(/(^|[^Y])YYYY([^Y]|$)/g, `$1${year}$2`)
? ? .replace(/(^|[^Y])YY([^Y]|$)/g, `$1${String(year).slice(-2)}$2`)
? ? .replace(/(^|[^M])MM([^M]|$)/g, `$1${month.padStart(2, "0")}$2`)
? ? .replace(/(^|[^M])M([^M]|$)/g, `$1${month}$2`)
? ? .replace(/(^|[^D])DD([^D]|$)/g, `$1${day.padStart(2, "0")}$2`)
? ? .replace(/(^|[^D])D([^D]|$)/g, `$1${day}$2`)
? ? .replace(/(^|[^H])HH([^H]|$)/g, `$1${hour.padStart(2, "0")}$2`)
? ? .replace(/(^|[^H])H([^H]|$)/g, `$1${hour}$2`)
? ? .replace(/(^|[^h])hh([^h]|$)/g, `$1${hour12.padStart(2, "0")}$2`)
? ? .replace(/(^|[^h])h([^h]|$)/g, `$1${hour12}$2`)
? ? .replace(/(^|[^A])A([^A]|$)/g, `$1${amOrPm}$2`)
? ? .replace(/(^|[^a])a([^a]|$)/g, `$1${amOrPm.toLowerCase()}$2`)
? ? .replace(/(^|[^m])mm([^m]|$)/g, `$1${minute.padStart(2, "0")}$2`)
? ? .replace(/(^|[^m])m([^m]|$)/g, `$1${minute}$2`)
? ? .replace(/(^|[^s])ss([^s]|$)/g, `$1${second.padStart(2, "0")}$2`)
? ? .replace(/(^|[^s])s([^s]|$)/g, `$1${second}$2`)
? ? .replace(
? ? ? /(^|[^S]+)([S]+)([^S]+|$)/g,
? ? ? (match: any, s1: any, s2: string | any[], s3: any) => {
? ? ? ? let msStr = millisecond.padStart(3, "0");
? ? ? ? for (let i = 3; i < s2.length; i++) {
? ? ? ? ? msStr += "0";
? ? ? ? }
? ? ? ? msStr = msStr.slice(0, s2.length);
? ? ? ? return `${s1}${msStr}${s3}`;
? ? ? }
? ? );
};

export { format };


6.使用 ?Logger.error('错误日志');

效果
[2023-12-27T16:24:46.500] [ERROR] default - user.controller.ts(line: 90, column: 14):
?错误日志

文章来源:https://blog.csdn.net/m0_74772114/article/details/135248038
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。