JS WEB框架Express日志模块winston和express-winston以及winston-daily-rotate-file优化

news2025/2/4 12:53:40

1.前言

1.Express的日志模块winston和express-winston已经提供了开箱即用的大多数功能,但是和其他语言相比,还缺失对日志记录的当前文件和行号的支持,需要自己实现,以此记录一下。
2.express-winston主要用于记录请求进入和结束时的一些信息,可以将req的请求地址,请求方法,查询参数,请求体参数等记录下来,可以将res的请求全程耗时等记录下来。
3.winston 用于业务代码中对需要记录日志的地方进行记录
4.winston-daily-rotate-file用户日志文件的自动滚存,压缩以及清理

2.版本

express-winston 4.2.0
winston 3.8.2
winston-daily-rotate-file 4.7.1
express 4.18.2

3.日志通道Transport

3.1控制台Transport

level是定义的常量日志级别
handleExceptions为true的话,会在日志中将发生异常的堆栈记录下来
format.combine中的format.label和format.timestamp会作为参数传递给myFormat({format: LOG_DATE_FORMAT}是对日期的格式化,否则是时间戳形式)

require('winston-daily-rotate-file');
require('winston');
const expressWinston = require('express-winston');
const {createLogger, transports, format} = require("winston");
const {
    LOG_LEVEL,
    LOG_FILE_SIZE,
    LOG_FILE_COUNT,
    LOG_FREQUENCY,
    LOG_DATE_FORMAT,
    LOG_FILE_NAME,
    LOG_FILE_DIR
} = require("../config/setting")

const consoleTransport = new transports.Console(
    {
        level: LOG_LEVEL,
        handleExceptions: true,
        format: format.combine(
            format.label({label: "ExpressApp"}),
            format.timestamp({format: LOG_DATE_FORMAT}),
            myFormat
        )
    }
)

3.2日志文件Transport

dirname是日志文件存放的目录
filename是日志文件的文件名部分
datePattern是以’YYYY-MM-DD’格式化的日期,会拼接在文件名后面
zippedArchive为true的话,会对日志文件进行压缩保存
maxSize日志文件最大大小
maxFiles日志文件最大数量

require('winston-daily-rotate-file');
require('winston');
const expressWinston = require('express-winston');
const {createLogger, transports, format} = require("winston");

const {
    LOG_LEVEL,
    LOG_FILE_SIZE,
    LOG_FILE_COUNT,
    LOG_FREQUENCY,
    LOG_DATE_FORMAT,
    LOG_FILE_NAME,
    LOG_FILE_DIR
} = require("../config/setting")

const fileTransport = new (transports.DailyRotateFile)({
    level: LOG_LEVEL,
    dirname: LOG_FILE_DIR,
    filename: LOG_FILE_NAME,
    datePattern: 'YYYY-MM-DD',
    format: format.combine(
        format.label({label: "ExpressApp"}),
        format.timestamp({format: LOG_DATE_FORMAT}),
        // format.simple()
        myFormat
    ),
    handleExceptions: true,
    handleRejections: true,
    frequency: LOG_FREQUENCY,
    zippedArchive: true,
    maxSize: LOG_FILE_SIZE,
    maxFiles: LOG_FILE_COUNT
})

4.日志记录格式myFormat

const myFormat = format.printf(({level, label, message, fileName, lineNo, timestamp}) => {
    let levelUp = level.toUpperCase()
    // let msgStr = JSON.stringify(message)
    return `${timestamp} [${label}] [${fileName}] [${lineNo} Line] [${levelUp}]: ${message}`;
});

5.业务日志记录实例LOGGER初始化以及优化

5.1初始化

exitOnError为true,winston会在遇到异常即刻停止运行,不再记录其它日志

require('winston-daily-rotate-file');
require('winston');
const expressWinston = require('express-winston');
const {createLogger, transports, format} = require("winston");

const LOGGER = createLogger({
    transports: [
        consoleTransport,
        fileTransport
    ],
    exceptionHandlers: [
        consoleTransport,
        fileTransport
    ],
    rejectionHandlers: [
        consoleTransport,
        fileTransport
    ],
    exitOnError: true

});

5.2优化

1.重写LOGGER的各级别方法,将获取的文件名和行号通过meta传进去
2.我只重写了这几个级别,如需,其它可自己照样子重写

const {getStackInfo} = require("../config/utils")
LOGGER.info = (message, meta) => {
    const stackInfo = getStackInfo()
    // 调用原始的 info() 方法进行日志输出
    LOGGER.log("info", message, {fileName: stackInfo["fileName"], lineNo: stackInfo["line"]});
};

LOGGER.error = (message, meta) => {
    const stackInfo = getStackInfo()
    // 调用原始的 info() 方法进行日志输出
    LOGGER.log("error", message, {fileName: stackInfo["fileName"], lineNo: stackInfo["line"]});
};

LOGGER.debug = (message, meta) => {
    const stackInfo = getStackInfo()
    // 调用原始的 info() 方法进行日志输出
    LOGGER.log("debug", message, {fileName: stackInfo["fileName"], lineNo: stackInfo["line"]});
};

6.请求记录日志实例EXPRESS_LOGGER初始化以及优化

6.1优化

1.强制修改expressWinston.logger的options
2.const stackInfo = getStackInfo()获取日志记录时的文件名以及行号等
3.req.on(‘data’, (chunk))是为了拿到请求进入时的body数据,这是个回调函数
4.req.on(‘end’, ())会在请求完全进入时打印日志
5.自定义上述myFormat中message的输出格式,通过req可以拿到请求的协议protocol,请求的IP地址 hostname,请求的源URL地址 originalUrl,请求的方法method;请求体 reqBody是在req.on(‘data’, (chunk))中拿到的
6.给对象logEntry增加文件名fileName=stackInfo.fileName,行号lineNo=stackInfo.line属性
7.以info级别记录logEntry
8.在请求返回时以debug级别记录响应的时间responseTime以及响应的状态码statusCode

require('winston-daily-rotate-file');
require('winston');
const expressWinston = require('express-winston');
const {getStackInfo} = require("../config/utils")

expressWinston.logger = (options = {}) => {
    const winstonInstance = options.winstonInstance || createLogger({
        transports: [consoleTransport, fileTransport]
    });
    return (req, res, next) => {
        const startTime = new Date();
        const stackInfo = getStackInfo()
        let reqBody=""
        let responseBody;
        req.on('data', (chunk) =>{
            reqBody += chunk
        })


        req.on('end', () => {

            const logEntry = {
                message: `${req.protocol.toUpperCase()} [${req.hostname} => SERVER] Request ${req.method} ${req.originalUrl} QueryParam: ${JSON.stringify(req.query)} Body: ${reqBody.replace("undefined","")}`,
                body: req.body, // 添加请求体
                query: req.query,
                headers: req.headers,
                method: req.method,
                originalUrl: req.originalUrl,
                protocol: req.protocol,
                ip: req.ip,
                user: req.user,
                serverName: req.hostname,
                serverTime: new Date().toJSON(),
            };
            logEntry.fileName = stackInfo.fileName
            logEntry.lineNo = stackInfo.line
            winstonInstance.info(logEntry);
        });
        
        res.on('finish', () => {

            let responseTime = (new Date() - startTime);
            const logResEntry = {
                message: `${req.protocol.toUpperCase()} [SERVER => ${req.hostname}] Response ${responseTime}ms ${res.statusCode} ${req.originalUrl}`,

            };
            logResEntry.fileName = stackInfo.fileName
            logResEntry.lineNo = stackInfo.line

            winstonInstance.debug(logResEntry);
        });

        next();		// 这一步不能落下
    };
};

6.2初始化

require('winston-daily-rotate-file');
require('winston');
const expressWinston = require('express-winston');

const EXPRESS_LOGGER = expressWinston.logger({
    expressFormat: false,
    colorize: false,
    meta: true,

})

7.创建utils.js文件存放工具方法

1.这一段借鉴了别人的代码,stackIndex需要根据自己业务代码目录层级调整,可以在debug模式下查看堆栈
2.此方法最终会返回文件名称fileName,代码行号line,行中的位置pos等
3.projectRoot是项目的根目录,之所以去除是因为不想在日志中显示过长的路径

const projectRoot = join(__dirname, '../');
const getStackInfo = function () {
    // get call stack, and analyze it
    // get all file, method, and line numbers
    stackIndex = 3
    const stacklist = (new Error()).stack.split('\n');

    // stack trace format:
    // http://code.google.com/p/v8/wiki/JavaScriptStackTraceApi
    // do not remove the regex expresses to outside of this method (due to a BUG in node.js)
    const stackReg = /at\s+(.*)\s+\((.*):(\d*):(\d*)\)/gi;
    const stackReg2 = /at\s+()(.*):(\d*):(\d*)/gi;

    const s = stacklist[stackIndex] || stacklist[0];
    const sp = stackReg.exec(s) || stackReg2.exec(s);

    if (sp && sp.length === 5) {
        return {
         
            fileName: sp[2].replace(projectRoot, ""),
            line: sp[3],
            pos: sp[4],
            relativePath: basename(sp[2]),
            stack: stacklist.join('\n')
        }
    }
}


module.exports = {getStackInfo}

8.创建settings.js存放配置

const LOG_LEVEL = "debug"
const LOG_FILE_SIZE = "20m"
const LOG_FILE_COUNT = "5"
const LOG_FREQUENCY = "24h"
const LOG_DATE_FORMAT = "YYYY-MM-DD HH:mm:ss.SSS"
const LOG_FILE_NAME = "nodejsServer-%DATE%.log"
const LOG_FILE_DIR = "./logs"

module.exports = {
    
    LOG_LEVEL,
    LOG_FILE_SIZE,
    LOG_FILE_COUNT,
    LOG_FREQUENCY,
    LOG_DATE_FORMAT,
    LOG_FILE_NAME,
    LOG_FILE_DIR
    
}

9.完整的log.js代码

require('winston-daily-rotate-file');
require('winston');
const expressWinston = require('express-winston');
const {
    LOG_LEVEL,
    LOG_FILE_SIZE,
    LOG_FILE_COUNT,
    LOG_FREQUENCY,
    LOG_DATE_FORMAT,
    LOG_FILE_NAME,
    LOG_FILE_DIR
} = require("../config/setting")
const {getStackInfo} = require("../config/utils")
const {createLogger, transports, format} = require("winston");


const myFormat = format.printf(({level, label, message, fileName, lineNo, timestamp}) => {
    let levelUp = level.toUpperCase()
    // let msgStr = JSON.stringify(message)
    return `${timestamp} [${label}] [${fileName}] [${lineNo} Line] [${levelUp}]: ${message}`;
});
const consoleTransport = new transports.Console(
    {
        level: LOG_LEVEL,
        handleExceptions: true,
        format: format.combine(
            format.label({label: "ExpressApp"}),
            format.timestamp({format: LOG_DATE_FORMAT}),
            // format.
            // format.colorize(),
            // format.simple()
            myFormat
        )
    }
)
const fileTransport = new (transports.DailyRotateFile)({
    level: LOG_LEVEL,
    dirname: LOG_FILE_DIR,
    filename: LOG_FILE_NAME,
    datePattern: 'YYYY-MM-DD',
    format: format.combine(
        format.label({label: "ExpressApp"}),
        format.timestamp({format: LOG_DATE_FORMAT}),
        // format.simple()
        myFormat
    ),
    handleExceptions: true,
    handleRejections: true,
    frequency: LOG_FREQUENCY,
    zippedArchive: true,
    maxSize: LOG_FILE_SIZE,
    maxFiles: LOG_FILE_COUNT
})
const LOGGER = createLogger({
    transports: [
        consoleTransport,
        fileTransport
    ],
    exceptionHandlers: [
        consoleTransport,
        fileTransport
    ],
    rejectionHandlers: [
        consoleTransport,
        fileTransport
    ],
    exitOnError: true

});

expressWinston.logger = (options = {}) => {
    const winstonInstance = options.winstonInstance || createLogger({
        transports: [consoleTransport, fileTransport]
    });
    return (req, res, next) => {
        const startTime = new Date();
        const stackInfo = getStackInfo()
        let reqBody=""
        let responseBody;
        req.on('data', (chunk) =>{
            reqBody += chunk
        })


        req.on('end', () => {

            const logEntry = {
                message: `${req.protocol.toUpperCase()} [${req.hostname} => SERVER] Request ${req.method} ${req.originalUrl} QueryParam: ${JSON.stringify(req.query)} Body: ${reqBody.replace("undefined","")}`,
                body: req.body, // 添加请求体
                query: req.query,
                headers: req.headers,
                method: req.method,
                originalUrl: req.originalUrl,
                protocol: req.protocol,
                ip: req.ip,
                user: req.user,
                serverName: req.hostname,
                serverTime: new Date().toJSON(),
            };
            logEntry.fileName = stackInfo.fileName
            logEntry.lineNo = stackInfo.line
            winstonInstance.info(logEntry);
        });
        res.on('data', (chunk) => {
            responseBody += chunk;
        });
        res.on('finish', () => {

            let responseTime = (new Date() - startTime);
            const logResEntry = {
                message: `${req.protocol.toUpperCase()} [SERVER => ${req.hostname}] Response ${responseTime}ms ${res.statusCode} ${req.originalUrl}`,

            };
            logResEntry.fileName = stackInfo.fileName
            logResEntry.lineNo = stackInfo.line

            winstonInstance.debug(logResEntry);
        });

        next();
    };
};

const EXPRESS_LOGGER = expressWinston.logger({
    expressFormat: false,
    colorize: false,
    meta: true,

})
LOGGER.info = (message, meta) => {
    const stackInfo = getStackInfo()
    // 调用原始的 info() 方法进行日志输出
    LOGGER.log("info", message, {fileName: stackInfo["fileName"], lineNo: stackInfo["line"]});
};

LOGGER.error = (message, meta) => {
    const stackInfo = getStackInfo()
    // 调用原始的 info() 方法进行日志输出
    LOGGER.log("error", message, {fileName: stackInfo["fileName"], lineNo: stackInfo["line"]});
};

LOGGER.debug = (message, meta) => {
    const stackInfo = getStackInfo()
    // 调用原始的 info() 方法进行日志输出
    LOGGER.log("debug", message, {fileName: stackInfo["fileName"], lineNo: stackInfo["line"]});
};



module.exports = {
    LOGGER, EXPRESS_LOGGER
}

10.以下是我的项目结构

日志模块全部代码在log.js里面
日志模块的一些常量配置在setting.js里面
日志模块用到一些工具函数等在utils.js里面

在这里插入图片描述

11.使用方法

11.1 LOGGER(主要在业务代码用于记录结果,参数,状态等)使用方法

const {LOGGER} = require("../log");
LOGGER.error(`test1`)
LOGGER.info(`test1`)
LOGGER.debug(`test1`)

11.2 EXPRESS_LOGGER(主要记录请求进来和返回的一些信息)使用方法

const express = require("express");
const app = new express();
const {EXPRESS_LOGGER} = require("./utils/log")
app.use(EXPRESS_LOGGER)

12.Tips

如果ORM框架用的是sequelize那么可以用LOGGER记录执行时的SQL
logging参数即用LOGGER.debug()记录执行SQL

const db = new Sequelize(DB_NAME, DB_USER, DB_PASSWD, {
  host: DB_HOST,
  dialect: DB_DIALECT,
  port:DB_PORT,
  //   pool: {
  //     max: 5,
  //     min: 0,
  //     idle: 10000,
  //   },
  dialectOptions: {
    // chartset: 'utf8mb4',--
    dateStrings: true,
    typeCast: true,
  },
  define: {
    // 字段以下划线(_)来分割(默认是驼峰命名风格)
    underscored: true,
    freezeTableName: false, //自定义表名,不设置会自动将表名转为复数形式
    timestamps: true, //自动生成更新时间、创建时间字段:updatedAt,createdAt
  },
  timezone: DB_TIMEZONE,
  logging: msg => LOGGER.debug(msg),
});

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/642138.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【运筹优化】ALNS自适应大领域搜索算法求解TSP问题 + Java代码实现

文章目录 一、TSP问题简介二、数学建模三、实现细节四、案例实战4.1 测试案例说明4.2 Java 完整代码4.2.1 TSP_Instance 实例类4.2.2 TSP_Solution 结果类4.2.3 TSP_Util 工具类4.2.4 TSP_Solver_ALNS 算法类4.2.5 RunAndPlot 运行类 4.3 运行结果展示 一、TSP问题简介 旅行推…

MySQL登录时报错:ERROR 1045 (28000): Access denied for user ‘root‘@‘localhost‘解决办法

问题描述 在云服务器使用 docker安装的Mysql5.7数据库,刚开始的时候使用正常,后面突然有一天就连接不上了,报错为: ERROR 1045 (28000): Access denied for user root1xxx(using password:YES), 当登录MySQL数据库出现 Error 1045…

python套接字(三):结合pyside2实现多人聊天室

文章目录 前言一、准备1、安装pyside22、设计界面 二、代码实现1、服务器端2、客户端 三、运行 前言 上一章python套接字(二):实现一个服务器和多客户端连接,大概实现了多人聊天室功能,但是比较简陋,本篇内容将结合pyside2做一个…

车间主任、班组长必读:生产车间的现场管理

与工厂车间操作层(一线员工)接触最多的基层管理者,即我们通常所说的班组长、车间主任等,他们是将企业战略规划落实到具体工作当中的终端管理者。 一线班组长的“角色” 1、责任者 对企业来说,班组长是基层的治理员&am…

MySQL高级篇第二天

文章目录 一、Mysql的体系结构概览 二、 存储引擎 三、优化SQL步骤 一、Mysql的体系结构概览 整个MySQL Server由以下组成 Connection Pool : 连接池组件 Management Services & Utilities : 管理服务和工具组件 SQL Interface : SQL接口组件 Parser : 查询分析器组件 O…

游戏测试与一般的软件测试的区别在哪里?

有很多同学进入测试行业之后,一直从事的是软件测试的工作,然后跳槽时遇到一些游戏的公司的面试,就会有点慌,我做的都是软件测试,能胜任游戏测试么? 所以,今天我们需要先来了解一下,…

科技政策 | 《深圳市加快加快推动人工智能高质量发展高水平应用行动方案(2023—2024年)》发布

原创 | 文 BFT机器人 导语 Introduction 近日,深圳市发布了《深圳市加快推动人工智能高质量发展高水平应用行动方案(2023-2024年)》旨在以更大热情拥抱创新,打造最好生态,推动人工智能高质量发展和全方位各领域高水平…

C语言-关键字

关键字就是c语言已经定义好的名字,直接可以拿过来使用,不需要再次定义 1 数据类型相关的关键字 用于定义变量或者类型 定义变量的语法结构: 类型 变量名; 拓展:变量名属于标识符,标识符(变量…

关于Axios发请求(get或post)的参数问题

版本说明: {"name": "wx_vue_3.0","version": "0.1.0","private": true,"scripts": {"serve": "vue-cli-service serve","build": "vue-cli-service build"…

K-Means聚类算法

引言 聚类算法是传统机器学习算法中比较重要的一个算法,也是工程项目当中一个比较常用的算法。 一. 分类与聚类 分类 分类其实是从特定的数据中挖掘模式,作出判断的过程。 分类学习主要过程: (1)训练数据集存在一个类…

mathtype公式右编号对齐

mathtype公式右编号对齐 1.选中文中编辑好的公式,复制 2.mathtype里的点击右编号,将上面复制的公式粘贴到新出现的框内 3.编号设置

聚类效果评估

目录 1.轮廓系数(Silhouette Coefficient) 1.1 为什么轮廓系数可以评价聚类效果的好坏? 1.2 平均轮廓系数 2. 其他聚类质量函数 2.1方差比准则(Variance Ratio Criterion, VRC) 2.2 戴维斯-博尔丁指数(Davies-Bouldin指数,DB指数) 评价聚…

linux- 定时任务清理日志

定时任务清理日志 一、查找并删除文件1.1 查找文件1.2 查找并删除 二、计划任务:2.1 创建shell脚本,并分配权限2.2 编辑shell脚本2.3 计划任务 linux是一个很能自动产生文件的系统,在实际部署运行中,发现日志文件会占用大量内存&a…

SpringBoot+Thymeleaf 后端转html,pdf HTML生成PDF SpringBoot生成PDF Java PDF生成

SpringBoot 生成PDF Thymeleaf企业级真实应用:将HTML界面数据转换为PDF输出 参考: https://blog.51cto.com/u_13146445/6190475 https://blog.csdn.net/qq_27242695/article/details/115654447 0. 需求 后端渲染pdf生成 (thymeleaf根据已有…

Android开发之数据传递的桥梁——Bundle

解释 在安卓sdk源码中,Bundle类的说明是这样的 A mapping from String keys to various Parcelable values. See Also: PersistableBundle public final class Bundle extends BaseBundle implements Cloneable, Parcelable 字符串的键到持久化值的映射。 作用 …

只用2个小时,我把公司的进销存流程全部搬到了线上!

目录 一、前言 二、线下流程的弊端 三、仅用2个小时,如何将流程搬到线上? (1)基础资料模块 (2)采购管理模块 (3)销售管理模块 (4)库存管理模块 &…

MySQL之视图,触发器与存储过程

一、视图 视图是一个虚拟表(非真实存在),其本质是【根据SQL语句获取动态的数据集,并为其命名】,用户使用时只需使用【名称】即可获取结果集,可以将该结果集当做表来使用。 使用视图我们可以把查询过程中的…

基于虚拟化的物联网沙盒操作系统

了解她的技术 先谈谈虚拟化吧! 为什么要有虚拟化?物理CPU,物理内存和存储,物理网络的硬件能力越来越丰富的情况下,为了高效、灵活的使用资源,以及在使用时的资源隔离,把硬件资源抽象成软件资源…

机器学习第一课

实现流程: 数据输入->数据基本处理->特征工程->训练->模型评估->新数据输入->预测结果 数据类型: 类型一:特征值目标值 类型二:只有特征值 一、数据基本处理 达到的标准 二、特征工程 三、机器学习&#…

Java+Swing+mysql图书管理系统

JavaSwingmysql图书管理系统 一、系统介绍二、功能展示1.管理员登陆2.图书查询3.图书入库4.借书5.还书6.图书证管理 三、系统实现1.BookManageMainFrame.java 四、其它1.其他系统实现2.获取源码 一、系统介绍 该系统实现了 用户: 书籍查询,借书,还书功能…