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):
错误日志