本文为稀土掘金技术社区首发签约文章,30天内禁止转载,30天后未获授权禁止转载,侵权必究!
经常做全栈项目或者做过正式后端项目的开发者都知道,日志记录是一个项目不可或缺的功能。不过,经常看Next.js开源项目的朋友会发现,几乎没什么项目集成了日志模块,我分析了一下,可能的原因有两个:
- 国外使用Next.js开发的项目大多都部署在Vercel这样的Serverless平台,平台会记录 console.log 日志,开发者可能使用 console.log 替代正式的日志模块。
- 很多Next.js开发的项目功能单一,调试和排查难度低,所以造成很多Next.js项目不会主动去集成日志模块。
但是,根据我的经验看,Next.js项目还是比较需要日志模块的,根据实际经验总结出来的原因如下有几个:
- Serverless 平台和自己购买的服务器环境是有区别的,这就带来Next.js项目运行时不同,例如在Serverless 平台里,Next.js使用的是edge运行时,它相比于Node环境来说,缺少了一些API,这会导致偶尔出现本地运行正常,部署到Serverless 平台发现报错了。
- 调用了第三方平台的API,当出现连接错误的情况,你去排查是第三方服务不稳定还是你的API额度用完,这都是比较麻烦了,但是如果有日志文件直接查看,定位问题就可以更快速。
所以,即使你现在不需要日志模块,但仍然要考虑预留功能,以备不时之需。
本文就来说说Next.js App Router下使用winston集成一个易于维护和易于帮助排查问题的日志模块。
日志模块需求
一个合格的日志模块应该符合以下要求:
- 能够判断是否有日志保存路径,如果没有要自动创建文件夹
- 可以指定日志文件保存路径,支持不同环境使用不同配置
- 日志分级,支持记录info, warning, error这3个级别的日志
- 能够按照日期和文件大小自动拆分文件
- 能够限制日志保存天数,超出自动删除
- 异步写入日志,不影响正常程序的执行。我们使用的是JavaScript,所以这一点无需额外操作。
开始整活!
集成winston日志
鲁迅我曾经说过:重复的劳动是对程序员的亵渎。
所以集成日志模块我直接在自己的发布的开源项目——clean-nextjs-starter 上添加新代码,clone下来就得到如下的项目:
├─ app # 应用入口
│ ├─ layout.tsx
│ └─ page.tsx # 首页
├─ components # 组件
│ ├─ ……
├─ config # 网站配置
│ └─ site.ts
├─ content # mdx 文件统一放在这里
│ ├─ ……
├─ lib # 公共工具类
│ ├─ logger.ts # ⚠️新增:日志配置文件
│ ├─ ……
├─ public # 公共静态资源
│ ├─ ……
├─ styles # 样式
│ ├─ ……
│ ……
我已经在 lib 文件夹下新增了日志配置文件 logger.ts了。接下来正式开始开发!
首先,需要安装 winston 和 winston-daily-rotate-file,后者是用于日志文件自动拆分的库:
npm install winston winston-daily-rotate-file @types/winston
添加日志文件路径配置和自动创建文件夹的代码:
import * as fs from 'fs';
const logDir: string = process.env.LOG_DIR || 'log'; // 使用环境变量或默认值
if (!fs.existsSync(logDir)) {
fs.mkdirSync(logDir, { recursive: true });
}
调用 winston 配置日志记录级别
const winston = require('winston');
const logger = winston.createLogger({
level: 'info',
format: winston.format.combine(
winston.format.timestamp({
format: 'YYYY-MM-DD HH:mm:ss'
}),
winston.format.json()
),
});
在 winston 中,level 用来配置日志级别,日志级别按照严重性排序,顺序为(从最低到最高严重性):
silly: 最详细的日志级别,用于记录极为详尽的信息。debug: 用于详细的系统内部信息,主要用于调试。verbose: 提供比debug级别稍少的详细日志。info: 一般信息,常用于跟踪程序的运行状态。warn: 警告信息,表明可能的问题,但不一定是错误。error: 错误信息,表明功能性的问题,通常需要用户或开发者介入解决。
在开发或调试阶段,你如果希望获得比较多的细节来帮助定位问题,可以将 level 设置问 debug 或 verbose。生产环境建议使用info级别就可以,表示会记录 info、warn和error三个级别的日志。
format 用来配置日志格式,winston.format.combine 方法用于组合多种日志格式化方法:
winston.format.timestamp:添加一个时间戳到日志消息中,这里配置的格式是YYYY-MM-DD HH:mm:ss,即年-月-日 时:分:秒。winston.format.json:将日志消息格式化为JSON格式,这样日志更容易被结构化存储和解析。
接下来配置日志输出格式:
const winston = require('winston');
require('winston-daily-rotate-file');
const fileTransport = new winston.transports.DailyRotateFile({
filename: `${logDir}/%DATE%-results.log`,
datePattern: 'YYYY-MM-DD',
zippedArchive: true,
maxSize: '20m',
maxFiles: '3d', // 保留3天的日志
level: 'info', // 此传输层记录info及以上级别的日志(info, warning, error)
});
const logger = winston.createLogger({
// ……
transports: [
fileTransport,
new winston.transports.Console({
level: 'info', // 控制台同时也输出所有级别的日志
format: winston.format.combine(
winston.format.colorize(),
winston.format.simple()
)
})
]
});
先介绍 fileTransport 的部分,这部分配置了文件命名和保存规则,其中:
filename和datePattern:定义了日志文件命名格式为2024-05-24-results.log这样的形式zippedArchive:设置为true表示日志文件在滚动时(即新的日志文件开始使用时)会自动被压缩成.zip文件。这有助于减少存储空间的占用。maxSize:定义了单个日志文件的最大大小,这里设置为20m(20兆字节)。当文件大小达到这个限制时,将创建一个新的日志文件继续记录,而旧的文件则根据maxFiles设置进行处理。maxFiles:控制日志文件的保留数量或时间。这里设置为3d,意味着只保留最近三天的日志文件。超过这个时间范围的日志文件将被自动删除。
然后看一下 transports,这个参数是用来定义日志消息的输出目标。每个 transport 是一个输出通道,可以是文件、控制台或其他自定义目标。
fileTransport:之前定义的文件输出配置,会保存在 logDir 指定的文件夹下new winston.transports.Console:这是一个控制台输出配置,设置为输出debug级别及以上的所有日志。有了这个配置,你会看到控制台也会同步输出日志,在开发的时候会更方便,当然,如果你觉得这样看着很繁杂,可以删掉这部分配置。
完整配置如下,你也可以到我的开源仓库:clean-nextjs-starter查看:
import * as fs from 'fs';
import * as winston from 'winston';
import 'winston-daily-rotate-file';
const logDir: string = process.env.LOG_DIR || 'log'; // 使用环境变量或默认值
if (!fs.existsSync(logDir)) {
fs.mkdirSync(logDir, { recursive: true });
}
const fileTransport = new winston.transports.DailyRotateFile({
filename: `${logDir}/%DATE%-results.log`,
datePattern: 'YYYY-MM-DD',
zippedArchive: true,
maxSize: '20m',
maxFiles: '3d', // 保留3天的日志
level: 'info', // 此传输层记录info及以上级别的日志(info, warning, error)
});
const logger: winston.Logger = winston.createLogger({
level: 'info', // 最低级别
format: winston.format.combine(
winston.format.timestamp({
format: 'YYYY-MM-DD HH:mm:ss'
}),
winston.format.json()
),
transports: [
fileTransport,
new winston.transports.Console({
level: 'info', // 控制台同时也输出所有级别的日志
format: winston.format.combine(
winston.format.colorize(),
winston.format.simple()
)
})
]
});
export default logger;
使用 Logger
上一节就是 集成winston所需的配置了,现在我们在服务器组件调用日志模块试一下
// app/page.tsx
import logger from "@/lib/logger";
export default function Home() {
logger.debug("Debug message");
logger.info("Info message");
logger.warn("Warning message");
logger.error("Error message");
// ……
}
现在打开首页(如果你clone了上面的项目,打开 http://localhost:3000 ),能够在vscode控制台看到日志,也可以看到根目录下生成了log文件夹,里面有跟我们配置对应的日志文件。
如果你不需要日志模块,只要不去调用 logger.ts 就可以,让logger模块静静地躺在角落吧!
开源地址:clean-nextjs-starter
关于我
我是一名前端工程师,Next.js 手艺人,AI降临派。
今年致力于 Next.js 和 Node.js 领域的开源项目开发和知识分享。