Egg.js 日志系统

Egg.js 日志系统 1. Egg 企业级日志系统解决什么问题Egg 内置日志能力主要由egg-logger提供目标不是简单地console.log而是解决 Web 服务中的如下问题1.日志分级egg 日志级别级别含义适合场景DEBUG调试日志最详细给开发者看的细节本地开发、排查问题INFO普通信息日志记录正常业务流程比如“订单创建成功”WARN警告日志出现异常苗头但不一定导致请求失败ERROR错误日志程序异常、请求失败、服务调用失败NONE不输出日志关闭日志输出日志级别的控制可通过如下进行配置exports.logger{level:INFO,// 默认INFO只打印INFO、WARN、ERROR如果配置WARN则只打印WARN、ERROR其他类推};2.请求上下文日志在请求处理中使用ctx.logger日志里会自动带上请求相关信息例如在代码里写的是ctx.logger.info([AgentChat] chatStream 用户请求入参: %j,{sessionId,message});但是在日志文件里会生成如下2026-07-26 11:22:06,879 INFO 10837 [-/::1/723f86b7-0038-48a9-987f-6e4158c55ab4/342.335ms POST /api/agent/chat/stream] [OpenClaw chatStream] 请求参数 {endpoint:http://dev1-ge.back.jd.com/oxygen/api/oxygen/chat/stream,sessionId:session_xxxx-ec35-4210-937c-xxxx,clientRequestId:req_516de26c-89a1-461b-8f8c-80e9bb5fd3e3}字段含义如下字段示例含义时间2026-07-26 09:54:26,351日志打印时间精确到毫秒日志级别INFO日志等级表示普通信息不是错误进程 ID77817当前 Node/Egg 进程的 PID日志分类[egg:lib:core:logger]打印日志的模块或命名空间日志内容init all loggers with options...具体事件说明3.错误日志集中收敛任意 logger 调用.error()打印的错误日志都会额外进入统一错误日志文件common-error.log。这方便排查线上异常。4.应用日志、框架日志、Agent 日志分离应用业务日志和框架内部日志不会混在一起。5.自动切割默认按天切割。也支持按小时、按文件大小切割。6.高性能默认不是每条日志都立刻写磁盘而是先写入内存 buffer再定时刷盘。2. Egg 日志分类Egg 里最核心的日志可以分成这几类分类Logger 对象常见使用入口默认日志文件主要用途备注应用业务日志appLogger / loggerctx.logger/app.logger${appInfo.name}-web.log例如server-web.log业务代码最常用的日志ctx.logger用于请求内app.logger用于应用级逻辑框架/插件日志coreLoggerctx.coreLogger/app.coreLoggeregg-web.logEgg 框架内部、插件日志普通业务代码一般不用写插件或框架扩展时更常用统一错误日志errorLogger一般不直接调用由所有 logger 的.error()自动汇总common-error.log汇总所有ERROR级别日志方便排查异常例如ctx.logger.error(err)会同时写入业务日志和common-error.logAgent 进程日志agentLoggeragent.logger/agent.coreLoggeregg-agent.logAgent 进程中的日志用于 Egg 多进程模型里的 Agent 进程自定义日志自定义 Logger例如payLogger/auditLoggerapp.getLogger(xxx)/ctx.getLogger(xxx)自定义文件例如pay.log独立业务日志如支付、审计、安全日志通过config.customLogger配置调用.error()也会汇总到common-error.log插件专用日志插件自定义 Logger例如scheduleLogger插件提供的 logger例如 schedule 插件 logger插件自定义例如egg-schedule.log某些插件自己的运行日志不是 Egg 基础必有日志是否存在取决于是否启用对应插件简版记忆ctx.logger / app.logger → 业务日志 → ${appInfo.name}-web.log ctx.coreLogger / app.coreLogger → 框架、插件日志 → egg-web.log 所有 logger.error(...) → 额外复制到 common-error.log agent.logger → Agent 进程日志 → egg-agent.log 自定义 logger → 自定义业务日志 → 自定义文件 scheduleLogger → schedule 插件日志 → egg-schedule.logEgg 默认把日志放在${appInfo.root}/logs/${appInfo.name}本地和单测环境的root是项目目录生产等其他环境的root是 HOME 目录name来自package.json。可以这样自定义日志目录// config/config.default.js exports.logger { dir: /path/to/your/custom/log/dir, };也可以自定义日志文件名// config/config.default.js module.exports appInfo { return { logger: { appLogName: ${appInfo.name}-web.log, coreLogName: egg-web.log, agentLogName: egg-agent.log, errorLogName: common-error.log, }, }; };3. 日志怎么打印3.1 请求处理中优先用ctx.loggerController、Service、Middleware 里处理一次请求时推荐用ctx.logger.info(some request data: %j, ctx.request.body); ctx.logger.warn(业务参数异常%j, ctx.query); ctx.logger.error(new Error(something wrong));为什么请求里推荐用ctx.logger因为它能带上请求上下文信息类似[userid/ip/traceId/12ms GET /api/user] message这样排查问题时可以知道哪个请求触发的日志请求耗时是多少请求路径是什么有没有 traceId 可以串联链路参数与格式化占位符ctx.logger.info([SessionController] 创建会话入口 %s,JSON.stringify({...}));第一个参数是格式化模板,%s是字符串占位符,第二个参数填进占位符位置。常用占位符:%s字符串、%d数字、%jJSON(自动序列化对象)。可简化:用%j省掉手动JSON.stringify:ctx.logger.info([SessionController] 创建会话入口 %j,{requestId,appId});3.2 应用级别用app.logger比如启动阶段、定时任务、应用级初始化逻辑// app.js module.exports app { app.logger.info(应用启动完成); app.logger.warn(某个配置缺失使用默认值); app.logger.error(new Error(启动阶段异常)); };3.3 框架/插件级别用coreLogger如果你是在写框架或插件不建议把框架内部日志打到业务日志里应该用app.coreLogger.info(plugin initialized); ctx.coreLogger.warn(framework warning);3.4 Agent 进程用agent.logger// agent.js module.exports agent { agent.logger.info(agent started); agent.logger.error(new Error(agent error)); };4. 错误日志要特别注意什么Egg 文档里有一个非常重要的建议ctx.logger.error(new Error(whoops));不要随便这样ctx.logger.error(something wrong);虽然字符串也能打印但如果你希望线上异常可追踪最好传Error对象。原因是Error对象有namemessagestackcause自定义属性源码中egg-logger会专门格式化Error输出完整堆栈相关实现可以看egg/packages/logger/src/utils.ts其中formatError()会把错误格式化成包含pid、hostname、stack 等信息的日志。5. 日志切割参考链接日志切割Egg 默认使用eggjs/logrotator进行日志切割。默认按天切割比如当前文件example-app-web.log到第二天会切成example-app-web.log.2026-07-24按大小切割例如egg-web.log超过 2GB 后切割const path require(path); module.exports appInfo { return { logrotator: { filesRotateBySize: [ path.join(appInfo.root, logs, appInfo.name, egg-web.log), ], maxFileSize: 2 * 1024 * 1024 * 1024, }, }; };按小时切割const path require(path); module.exports appInfo { return { logrotator: { filesRotateByHour: [ path.join(appInfo.root, logs, appInfo.name, common-error.log), ], }, }; };切割的定时任务(内置)Egg「约定优于配置」:插件把定时任务放在自己的app/schedule/目录,框架启动自动加载。位置:node_modules/eggjs/logrotator/dist/app/schedule/ ├── rotate_by_file.js → 按天切割 ├── rotate_by_size.js → 按大小切割 ├── rotate_by_hour.js → 按小时切割(默认关闭) └── clean_log.js → 删除过期日志任务文件cron触发时机干什么rotate_by_file.js1 0 0 * * *每天 00:00:01xxx.log→xxx.log.YYYY-MM-DD,建空文件clean_log.js0 0 * * *每天 00:00:00删除超过maxDays天的归档文件rotate_by_size.js每 60 秒轮询持续检查单文件超maxFileSize立即切cron 两种写法都支持:clean_log用标准 5 段(分 时 日 月 周),rotate_by_file用 6 段(最前多了秒)。这些任务本身就是 schedule 任务,所以会产生egg-schedule.log。保留天数与自动清理删除逻辑在clean_log.js的removeExpiredLogFiles:扫描日志目录所有文件;只挑后缀是日期格式的归档文件(如.2026-07-23);算过期线 今天往前推maxDays天;归档日期早于过期线的,直接fs.unlink删除。要点:只删带日期后缀的归档文件,永不删正在写入的活动文件。默认已开启清理,maxDays: 31(保留 31 天)。maxDays: 0表示永不删除、全部保留(才会无限堆积)。logrotator 默认配置logrotator:{disableRotateByDay:false,filesRotateByHour:null,hourDelimiter:-,filesRotateBySize:null,maxFileSize:50*1024*1024,// 50MBmaxFiles:10,// 按大小切割时最多保留数rotateDuration:60000,// 按大小轮询间隔 60smaxDays:31,// 保留天数,0 不删gzip:false,// 归档是否 gzip 压缩}自定义(在config/config.default.ts覆盖)config.logrotator{maxDays:7,// 只留 7 天maxFileSize:100*1024*1024,// 100MBgzip:true,// 归档压缩省空间};11. 源码视角Egg 日志初始化流程可以重点看这几个文件11.1 默认配置egg/packages/egg/src/config/config.default.ts这里定义config.logger { dir: path.join(appInfo.root, logs, appInfo.name), encoding: utf8, env: appInfo.env, level: INFO, consoleLevel: INFO, disableConsoleAfterReady: appInfo.env ! local appInfo.env ! unittest, outputJSON: false, buffer: true, appLogName: ${appInfo.name}-web.log, coreLogName: egg-web.log, agentLogName: egg-agent.log, errorLogName: common-error.log, coreLogger: {}, allowDebugAtProd: false, enableFastContextLogger: true, };11.2 创建 Loggersegg/packages/egg/src/lib/core/logger.ts这里会从app.config.logger读取配置创建EggLoggers生产环境保护 DEBUG应用 ready 后按配置关闭 console注册全局 logger应用关闭时清理全局 logger。11.3 Logger Manageregg/packages/logger/src/egg/loggers.ts这里创建errorLoggerloggercoreLoggercustom loggers并配置错误日志集中logger.duplicate(ERROR, concentrateLogger, { excludes: [console] });也就是说业务 logger 打一条 ERROR除了进入业务日志也会复制一份到common-error.log。12. 推荐实践12.1 业务请求日志ctx.logger.info(创建订单成功orderId%s, order.id);不要打印过大的对象// 不推荐 ctx.logger.info(request body: %j, ctx.request.body);如果 body 很大或有敏感字段应该筛选ctx.logger.info(创建订单请求%j, { userId: ctx.user.id, skuId: ctx.request.body.skuId, count: ctx.request.body.count, });12.2 错误日志推荐try { await service.doSomething(); } catch (err) { ctx.logger.error(err); throw err; }如果要补充上下文try { await service.pay(order); } catch (err) { ctx.logger.error([pay failed] orderId%s userId%s, order.id, ctx.user.id); ctx.logger.error(err); throw err; }12.3 不要滥用debugdebug适合开发和临时排查不适合长期在线上大量打开。12.4 不要在日志里打印敏感信息避免打印passwordtokencookie等12.5 自定义 logger 要克制只有当日志具有明确独立消费场景时才拆比如审计日志安全日志支付流水日志调度执行日志否则优先使用默认app.logger。13. 总结一句话Egg 的日志系统可以理解成业务代码调用 ctx.logger/app.logger ↓ EggLoggers 管理多个 logger ↓ 每个 logger 绑定多个 transporttransport负责判断当前日志级别是否应该输出 ↓ transport 根据日志级别、格式、上下文输出到文件/终端/JSON/远端 ↓ ERROR 日志额外集中到 common-error.log ↓ logrotator 负责日志切割