Node.js 应用部署到 Debian 之后,日志管理很快就会从“打印几行调试信息”变成运维问题:文件越来越大、错误和访问日志混在一起、重启后日志不好追、系统里也缺少统一入口。要把这件事做好,关键不在于堆更多工具,而是先确定日志输出方式,再决定由系统、应用还是进程管理器负责轮转与归档。
下面按实际部署顺序梳理一套常见做法:先选日志库并完成基础结构化输出,再看 logrotate 与 winston-daily-rotate-file 的分工,接着接入 PM2 管理运行时日志,最后把日志送进 syslog/journald 或 ELK、Sentry 这类平台。看完后,你可以根据应用规模和维护方式选择一套更合适的组合。
先选日志库:Winston、Pino 和 Morgan 怎么分工
在 Debian 系统上管理 Node.js 日志,第一步是选对日志库。常见选择有这三类:
- Winston:功能全面,支持文件、控制台、HTTP 等多种传输方式,适合需要较完整日志方案的应用。
- Pino:性能更高,输出速度快,更适合高并发场景。
- Morgan:主要记录 HTTP 请求日志,常与 Express 等 Web 框架一起使用。
如果你需要的是一套比较通用、可扩展的方案,Winston 依然是最容易落地的起点。下面是基础安装和配置示例。
Winston 基础安装与配置
npm install winston配置文件 logger.js 示例:
const { createLogger, format, transports } = require('winston');
const logger = createLogger({
level: process.env.NODE_ENV === 'production' 'warn' : 'debug', // 根据环境调整日志级别
format: format.combine(
format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }), // 添加时间戳
format.json() // 结构化日志(便于后续分析)
),
transports: [
new transports.File({ filename: 'logs/error.log', level: 'error' }), // 错误日志单独存储
new transports.File({ filename: 'logs/combined.log' }) // 所有日志合并存储
]
});
// 开发环境输出到控制台
if (process.env.NODE_ENV !== 'production') {
logger.add(new transports.Console({
format: format.simple() // 控制台输出简化格式
}));
}
module.exports = logger;在应用中这样调用:
const logger = require('./logger');
logger.info('Server started on port 3000');
logger.error('Database connection failed:', { error: err.message });这套配置的核心思路有三点:一是按环境切换日志级别,二是输出 JSON 结构化日志,三是把错误日志单独落盘。后面无论接入系统轮转还是集中分析平台,这些基础都会直接用上。
日志轮转怎么做:logrotate 和应用级轮转如何选择
日志轮转的作用很直接:防止日志文件无限增长,占满磁盘空间。在 Debian 环境里,常见方案有两种:

- 系统级轮转:使用系统自带的
logrotate,适合统一管理磁盘上的日志文件。 - 应用级轮转:使用
winston-daily-rotate-file,适合由应用自己决定文件命名、大小和保留周期。
如果你的 Node.js 应用已经把日志写到固定文件路径,且希望和其他系统服务保持一致,优先考虑 logrotate。如果你更依赖 Winston 本身的传输体系,应用级轮转会更灵活。
方案一:用 logrotate 做系统级轮转
先安装 logrotate:
sudo apt update && sudo apt install logrotate然后创建 Node.js 专用配置文件 /etc/logrotate.d/nodejs:
/var/log/nodejs/*.log {
# 监控的日志路径(需与Node.js应用日志路径一致)
daily # 每天轮转一次
missingok # 日志文件不存在时不报错
rotate 7 # 保留最近7个轮转文件
compress # 压缩旧日志(节省空间)
delaycompress # 延迟压缩(避免立即压缩导致资源占用)
notifempty # 日志为空时不轮转
create 640 root adm # 新日志文件的权限和所有者
sharedscripts # 所有日志轮转完成后执行脚本
postrotate
# 可选:通知应用重新打开日志文件(如使用PM2需替换为pm2 reload)
systemctl restart your-node-app.service
endscript
}可以用下面的命令强制执行一次,检查配置是否生效:
sudo logrotate -f /etc/logrotate.d/nodejs这里最值得注意的是 postrotate。如果应用还握着旧文件句柄,轮转后可能继续往旧文件里写,所以需要在轮转完成后重启服务,或者按你的运行方式替换为 pm2 reload。
方案二:用 winston-daily-rotate-file 做应用级轮转
如果你希望日志文件按日期切分,并由应用自己控制文件大小与保留天数,可以安装这个扩展:
npm install winston-daily-rotate-file修改 logger.js 配置:
const DailyRotateFile = require('winston-daily-rotate-file');
const logger = createLogger({
// ...其他配置
transports: [
new DailyRotateFile({
filename: 'logs/application-%DATE%.log', // 轮转文件名(%DATE%为日期占位符)
datePattern: 'YYYY-MM-DD', // 日期格式
zippedArchive: true, // 压缩旧日志
maxSize: '20m', // 单个日志文件最大大小
maxFiles: '14d' // 保留最近14天的日志
}),
new transports.Console()
]
});这类做法的优势在于配置集中,日志策略和应用代码放在一起,部署时更容易跨环境复制;但如果你的机器上服务很多,系统级统一管理通常仍然更省心。
PM2 如何接管运行时日志
如果线上是通过 PM2 管理 Node.js 进程,那么日志管理最好一起纳入 PM2。它不仅能自动拉起应用,还能提供实时查看、日志合并和保留策略等能力。

安装 PM2 并启动应用
sudo npm install pm2 -g # 全局安装pm2
pm2 start app.js --name my-app # 启动应用并命名常用日志查看命令
查看实时日志:
pm2 logs my-app查看最近 100 行日志:
pm2 logs my-app --lines 100 # 查看最后100行配置 PM2 的日志轮转参数
通过 PM2 内置能力,可以限制日志文件大小和保留数量:
pm2 set pm2:log-date-format "YYYY-MM-DD HH:mm Z" # 设置日志时间格式
pm2 set pm2:merge-logs true # 合并stdout和stderr
pm2 set pm2:max-size 10M # 单个日志文件最大10MB
pm2 set pm2:retain 7 # 保留最近7天的日志这套配置更适合中小型部署场景,特别是应用本身没有做复杂日志拆分时。它的优势是运维入口统一,但如果你已经使用 Winston 严格区分错误日志与业务日志,PM2 更适合作为补充,而不是唯一日志策略。
把 PM2 日志写入系统日志目录
如果希望 PM2 管理的应用日志统一落到 /var/log/nodejs,可以调整 ecosystem.config.js:
module.exports = {
apps: [{
name: 'my-app',
script: 'app.js',
log_date_format: 'YYYY-MM-DD HH:mm Z',
merge_logs: true,
out_file: '/var/log/nodejs/my-app.log', // 输出到系统日志目录
error_file: '/var/log/nodejs/my-app-error.log',
env: {
NODE_ENV: 'production'
}
}]
};修改后重启 PM2:
pm2 restart my-app这样做的好处是,PM2 负责进程,系统目录负责承接日志文件,后续再配合 logrotate 就会更顺手。
如何接入 syslog 和 journald 做统一管理
如果你的目标不是简单保存日志,而是要和系统服务一起统一查看、归档或转发,那么把 Node.js 日志送入 syslog 或 journald 会更合适。

用 winston-syslog 发送到 syslog
先安装扩展:
npm install winston-syslog修改 logger.js:
const SyslogTransport = require('winston-syslog').SyslogTransport;
const logger = createLogger({
// ...其他配置
transports: [
new SyslogTransport({
host: 'localhost', // syslog服务器地址(本地则为localhost)
port: 514, // syslog默认端口
protocol: 'udp4', // 传输协议(udp/tcp)
app_name: 'my-node-app', // 应用名称(syslog中标识)
facility: 'local0' // 设施类型(如local0-local7)
})
]
});然后配置 /etc/rsyslog.d/50-nodejs.conf,把对应程序名的日志单独写到指定文件:
if $programname == 'my-node-app' then /var/log/nodejs/my-app.log # 将my-node-app日志写入指定文件
& stop # 停止继续处理(避免重复写入)最后重启 rsyslog:
sudo systemctl restart rsyslog这套方式适合已经依赖 syslog 生态的服务器,尤其是需要后续转发到远端日志系统时。
用 systemd-cat 发送到 journald
如果应用本身就是通过 systemd 服务启动的,那么直接写入 journald 往往更自然。可以通过 systemd-cat 发送日志:
const { exec } = require('child_process');
// 记录info日志
exec('systemd-cat -t my-app -p info "Server started on port 3000"', (error, stdout, stderr) => {
if (error) console.error(`Error sending to journald: ${error.message}`);
});
// 记录error日志
exec('systemd-cat -t my-app -p err "Database connection failed"', (error, stdout, stderr) => {
if (error) console.error(`Error sending to journald: ${error.message}`);
});查看 journald 中的日志:
journalctl -t my-app -f # 实时查看my-app日志
journalctl -u my-app.service # 查看应用对应的systemd服务日志和 syslog 相比,journald 的优势是和 systemd 服务天然关联,排查某个服务的启动、重启、报错过程会更直接。
需要集中分析时,再接 ELK 或 Sentry
如果你面对的是多台机器、多服务协同,或者需要做检索、聚合、告警,那么本地文件和系统日志通常还不够。这时候可以把日志继续送往集中化平台。
ELK Stack:适合检索与可视化分析
一个常见做法是把文件日志交给 Logstash 采集,再送入 Elasticsearch,并通过 Kibana 展示。
logstash.conf 示例:
input {
file {
path => "/var/log/nodejs/*.log"
start_position => "beginning"
sincedb_path => "/dev/null"
}
}
filter {
json {
source => "message"
}
}
output {
elasticsearch {
hosts => ["localhost:9200"]
index => "nodejs-logs-%{+YYYY.MM.dd}"
}
stdout { codec => rubydebug }
}启动 Kibana 后,在“Discover”页面选择 nodejs-logs-* 索引,就可以查看和分析日志数据。
Sentry:适合错误监控与异常告警
如果重点不是全量日志,而是异常追踪和报警,Sentry 更轻量。
安装 SDK:
npm install @sentry/node @sentry/tracing在 app.js 中初始化:
const Sentry = require('@sentry/node');
const Tracing = require('@sentry/tracing');
Sentry.init({
dsn: 'YOUR_SENTRY_DSN', // 替换为你的Sentry DSN
environment: process.env.NODE_ENV,
tracesSampleRate: 1.0 // 采样率(1.0表示100%采样)
});
// 捕获未处理的异常
process.on('uncaughtException', (err) => {
Sentry.captureException(err);
process.exit(1);
});
// 捕获未处理的Promise rejection
process.on('unhandledRejection', (reason, promise) => {
Sentry.captureException(reason);
});配置完成后,错误日志会自动上传到 Sentry,可以在控制台查看异常详情并设置报警规则。
实际落地时,怎样组合最省事
如果你只维护一台 Debian 服务器上的单个 Node.js 服务,一般可以从 Winston + 文件输出 + logrotate 起步。这套方案简单、稳定,排查问题也直观。
如果你已经用 PM2 托管应用,比较实用的组合通常是 PM2 + 指定日志目录 + logrotate。这样进程管理和日志文件管理职责清晰,不容易互相打架。
如果你的重点是统一运维视角,或者服务器上还有很多 systemd 服务,那么可以进一步接入 syslog/journald。而当你需要检索、仪表盘、异常告警时,再把日志送到 ELK 或 Sentry,通常就足够覆盖大部分生产场景。







