You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Winston日志按月份自动切换目录问题求助

解决Winston日志按月自动切换目录的问题

当前问题

我的日志目录结构如下:

  • logs
    • 2023-07
      • info-2023-07-20.log
      • info-2023-07-21.log

logs文件夹位于项目根目录,使用Winston结合winston-daily-rotate-file实现了日志按天自动切换,但无法自动按月生成新目录——必须重启服务器才能创建当月的日志目录,否则日志会一直追加到旧月份的目录中。

当前的logger.js配置代码如下:

const fs = require('fs')
const winston = require('winston')
const { format, transports } = winston
const { colorize, combine, timestamp, printf, splat, errors } = format
require('winston-daily-rotate-file')

if (!fs.existsSync('logs/')) {
  fs.mkdirSync('logs/')
}

const todayDate = new Date()
const currentMonth = ('0' + (todayDate.getMonth() + 1)).slice(-2)
const logDir = `logs/${todayDate.getFullYear()}-${currentMonth}` // Directory path for logs.
// month + 1 is because first month starts with 0
if (!fs.existsSync(logDir)) {
  fs.mkdirSync(logDir)
}

const logFormat = combine(
  timestamp({ format: 'HH:mm:ss' }),
  splat(),
  errors({ stack: true }),
  printf(({ timestamp, level, message, stack }) => {
    return `${timestamp},${global.isBackgroundServer ? ` [BG]:` : ''}${
      process.env.NODE_ENV === 'development' ? ` ${level}:` : ''
    } ${typeof message === 'object' ? JSON.stringify(message) : message}${stack ? `, Stack: ${stack}` : ''}`
  }),
)

const defaultLoggerOptions = {
  format: logFormat,
}

const infoTransport = new transports.DailyRotateFile({
  ...defaultLoggerOptions,
  level: 'info',
  filename: logDir + '/Info-%DATE%.log',
})

const errorTransport = new transports.DailyRotateFile({
  ...defaultLoggerOptions,
  level: 'error',
  filename: logDir + '/Error-%DATE%.log',
})

const warningTransport = new transports.DailyRotateFile({
  ...defaultLoggerOptions,
  level: 'debug',
  filename: logDir + '/Warning-%DATE%.log',
})

const consoleTransport = new transports.Console({
  format: combine(colorize(), logFormat),
})

const errorLogger = winston.createLogger({
  transports: [consoleTransport, errorTransport],
})

const successLogger = winston.createLogger({
  transports: [consoleTransport, infoTransport],
})

const warningLogger = winston.createLogger({
  transports: [consoleTransport, warningTransport],
})

const transportConsoleToWinston = () => {
  const getArgumentsStr = (arguments) => {
    return Array.prototype.slice.call(arguments).join(' ')
  }

  const wrappedSuccessLogger = (...args) => successLogger.log.apply(successLogger, ['info', getArgumentsStr(args)])
  const wrappedWarningLogger = (...args) => warningLogger.log.apply(warningLogger, ['warn', getArgumentsStr(args)])
  const wrappedDebugLogger = (...args) => warningLogger.log.apply(warningLogger, ['debug', getArgumentsStr(args)])

  console.log = wrappedSuccessLogger
  console.info = wrappedSuccessLogger
  console.warn = wrappedWarningLogger
  console.debug = wrappedDebugLogger
  console.error = (...args) => errorLogger.error.call(errorLogger, ...args)
}

module.exports = { errorLogger, successLogger, warningLogger, transportConsoleToWinston }

问题根源

当前代码在启动时就计算好了固定的logDir,之后所有日志都会写入这个目录,即使月份变更也不会重新计算目录路径,导致必须重启服务才能更新目录。

解决方案

通过让winston-daily-rotate-file的dirname属性使用动态函数,每次生成新日志文件时自动计算当前月份的目录并创建(如果不存在),实现无需重启服务即可自动切换月份目录。

修改后的完整代码

const fs = require('fs')
const winston = require('winston')
const { format, transports } = winston
const { colorize, combine, timestamp, printf, splat, errors } = format
require('winston-daily-rotate-file')

// 确保根日志目录存在
if (!fs.existsSync('logs/')) {
  fs.mkdirSync('logs/')
}

const logFormat = combine(
  timestamp({ format: 'HH:mm:ss' }),
  splat(),
  errors({ stack: true }),
  printf(({ timestamp, level, message, stack }) => {
    return `${timestamp},${global.isBackgroundServer ? ` [BG]:` : ''}${
      process.env.NODE_ENV === 'development' ? ` ${level}:` : ''
    } ${typeof message === 'object' ? JSON.stringify(message) : message}${stack ? `, Stack: ${stack}` : ''}`
  }),
)

const defaultLoggerOptions = {
  format: logFormat,
  datePattern: 'YYYY-MM-DD', // 明确指定日期格式
}

// 生成动态月份目录的工具函数
const getCurrentMonthDir = () => {
  const today = new Date()
  const year = today.getFullYear()
  const month = ('0' + (today.getMonth() + 1)).slice(-2)
  const dir = `logs/${year}-${month}`
  
  // 递归创建目录(确保父目录存在)
  if (!fs.existsSync(dir)) {
    fs.mkdirSync(dir, { recursive: true })
  }
  return dir
}

const infoTransport = new transports.DailyRotateFile({
  ...defaultLoggerOptions,
  level: 'info',
  filename: 'Info-%DATE%.log',
  dirname: getCurrentMonthDir, // 动态获取目录
})

const errorTransport = new transports.DailyRotateFile({
  ...defaultLoggerOptions,
  level: 'error',
  filename: 'Error-%DATE%.log',
  dirname: getCurrentMonthDir,
})

const warningTransport = new transports.DailyRotateFile({
  ...defaultLoggerOptions,
  level: 'debug',
  filename: 'Warning-%DATE%.log',
  dirname: getCurrentMonthDir,
})

const consoleTransport = new transports.Console({
  format: combine(colorize(), logFormat),
})

const errorLogger = winston.createLogger({
  transports: [consoleTransport, errorTransport],
})

const successLogger = winston.createLogger({
  transports: [consoleTransport, infoTransport],
})

const warningLogger = winston.createLogger({
  transports: [consoleTransport, warningTransport],
})

const transportConsoleToWinston = () => {
  const getArgumentsStr = (arguments) => {
    return Array.prototype.slice.call(arguments).join(' ')
  }

  const wrappedSuccessLogger = (...args) => successLogger.log.apply(successLogger, ['info', getArgumentsStr(args)])
  const wrappedWarningLogger = (...args) => warningLogger.log.apply(warningLogger, ['warn', getArgumentsStr(args)])
  const wrappedDebugLogger = (...args) => warningLogger.log.apply(warningLogger, ['debug', getArgumentsStr(args)])

  console.log = wrappedSuccessLogger
  console.info = wrappedSuccessLogger
  console.warn = wrappedWarningLogger
  console.debug = wrappedDebugLogger
  console.error = (...args) => errorLogger.error.call(errorLogger, ...args)
}

module.exports = { errorLogger, successLogger, warningLogger, transportConsoleToWinston }

关键修改点

  1. 移除了启动时固定计算logDir和创建月份目录的代码,改为通过getCurrentMonthDir函数动态生成目录
  2. 为每个DailyRotateFile配置添加dirname: getCurrentMonthDir,每次生成新日志文件时都会调用该函数获取当前月份的目录
  3. 使用fs.mkdirSync(dir, { recursive: true })确保目录不存在时自动创建,无需担心父目录缺失
  4. 显式指定datePattern: 'YYYY-MM-DD',让日志文件名的日期格式更清晰

这样配置后,当月份变更时,Winston会自动创建新的月份目录并将日志写入其中,无需重启服务器。

内容的提问来源于stack exchange,提问作者Juhil Somaiya

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.15 19:05:53