当前位置: 首页 > news >正文

express(node ORM) 使用 Winston 记录日志 及数据库保存日志

一、安装

npm i winston
npm i winston-mysql

二、 配置 winston

2.1、封装

const config = require(__dirname + ‘/…/config/config.json’)[env];

  • 先判断当前是什么环境,如果.env中没有配置,就是开发环境。
  • 接着去config/config.json中读取对应的配置。
  • 取到值后,填充到options
const {createLogger, format, transports} = require('winston');
const MySQLTransport = require('winston-mysql');

// 读取 config/config.json 数据库配置文件
// 根据环境变量 NODE_ENV 来选择对应数据库配置
const env = process.env.NODE_ENV || 'development';
const config = require(__dirname + '/../config/config.json')[env];
const options = {
  host: config.host,
  user: config.username,
  password: config.password,
  database: config.database,
  table: 'Logs'
};

const logger = createLogger({
  // 日志级别,只输出 info 及以上级别的日志
  level: 'info',
  // 日志格式为 JSON
  format: format.combine(
    format.errors({stack: true}),    // 添加错误堆栈信息
    format.json()
  ),
  // 添加元数据,这里添加了服务名称
  defaultMeta: {service: 'xw-api'},
  // 日志输出位置
  transports: [
    // 将 error 或更高级别的错误写入 error.log 文件
    new transports.File({filename: 'error.log', level: 'error'}),
    // 将 info 或更高级别的日志写入 combined.log 文件
    new transports.File({filename: 'combined.log'}),
    // 添加 MySQL 传输,将日志存储到数据库
    new MySQLTransport(options)
  ]
});

// 在非生产环境下,将日志输出到控制台
if (process.env.NODE_ENV !== 'production') {
  logger.add(new transports.Console({
    format: format.combine(
      format.colorize(), // 终端中输出彩色的日志信息
      format.simple()
    )
  }));
}

module.exports = logger;

config文件

在这里插入图片描述

2.2、测试
const logger = require('../utils/logger');

/**
 * 查询首页数据
 * GET /
 */
router.get('/', async function (req, res, next) {
  try {
    logger.info('这是一个 info 信息');
    logger.warn('这是一个 warn 信息');
    logger.error('这是一个 error 信息');

    // ...

  } catch (error) {
    failure(res, error);
  }
});


在这里插入图片描述

三、修改封装的responses.js

const createError = require('http-errors');
const multer = require('multer');
const logger = require("./logger");


/**
 * 请求成功
 * @param res
 * @param message
 * @param data
 * @param code
 */
function success(res, message, data = {}, code = 200) {
  res.status(code).json({
    status: true,
    message,
    data,
    code
  });
}


/**
 * 请求失败
 * @param res
 * @param error
 */
function failure(res, error) {
  // 初始化状态码和错误信息
  let statusCode;
  let errors;

  if (error.name === 'SequelizeValidationError') {      // Sequelize 验证错误
    statusCode = 400;
    errors = error.errors.map(e => e.message);
  } else if (error.name === 'JsonWebTokenError' || error.name === 'TokenExpiredError') {  // Token 验证错误
    statusCode = 401;
    errors = '您提交的 token 错误或已过期。';
  } else if (error instanceof createError.HttpError) {  // http-errors 库创建的错误
    statusCode = error.status;
    errors = error.message;
  } else if (error instanceof multer.MulterError) {     // multer 上传错误
    if (error.code === 'LIMIT_FILE_SIZE') {
      statusCode = 413;
      errors = '文件大小超出限制。';
    } else {
      statusCode = 400;
      errors = error.message;
    }
  } else {                                              // 其他未知错误
    statusCode = 500;
    errors = '服务器错误。';
    logger.error('服务器错误:', error);
  }

  res.status(statusCode).json({
    status: false,
    message: `请求失败: ${error.name}`,
    errors: Array.isArray(errors) ? errors : [errors]
  });
}

module.exports = {
  success,
  failure
}

四、封装日志接口

4.1、创建Log表
sequelize model:generate --name Log --attributes level:string,message:string,meta:string,timestamp:date
4.2、修改迁移
'use strict';
/** @type {import('sequelize-cli').Migration} */
module.exports = {
  async up(queryInterface, Sequelize) {
    await queryInterface.createTable('Logs', {
      id: {
        allowNull: false,
        autoIncrement: true,
        primaryKey: true,
        type: Sequelize.INTEGER.UNSIGNED
      },
      level: {
        allowNull: false,
        type: Sequelize.STRING(16)
      },
      message: {
        allowNull: false,
        type: Sequelize.STRING(2048)
      },
      meta: {
        allowNull: false,
        type: Sequelize.STRING(2048)
      },
      timestamp: {
        allowNull: false,
        type: Sequelize.DATE
      }
    });
  },
  async down(queryInterface, Sequelize) {
    await queryInterface.dropTable('Logs');
  }
};

4.3、运行迁移
sequelize db:migrate
4.4、修改模型
'use strict';
const {
  Model
} = require('sequelize');
module.exports = (sequelize, DataTypes) => {
  class Log extends Model {
    /**
     * Helper method for defining associations.
     * This method is not a part of Sequelize lifecycle.
     * The `models/index` file will call this method automatically.
     */
    static associate(models) {
      // define association here
    }
  }
  Log.init({
    level: DataTypes.STRING,
    message: DataTypes.STRING,
    meta: {
      type: DataTypes.STRING,
      get() {
        return JSON.parse(this.getDataValue("meta"));
      }
    },
    timestamp: DataTypes.DATE,
  }, {
    sequelize,
    modelName: 'Log',
    timestamps: false, // 没有 createdAt 与 updatedAt
  });
  return Log;
};

  • meta里存储的是详细的错误信息。在读取的时候,需要使用JSON.parse转回json格式。
  • 加上了timestamps: false。因为已经有专门的timestamp字段记录时间了,所以并不需要createdAtupdatedAt
4.5、 接口封装
const express = require('express');
const router = express.Router();
const { Log } = require('../../models');
const { NotFound } = require('http-errors');
const { success, failure } = require('../../utils/responses');

/**
 * 查询日志列表
 * GET /admin/logs
 */
router.get('/', async function (req, res) {
  try {
    const logs = await Log.findAll({
      order: [['id', 'DESC']],
    });

    success(res, '查询日志列表成功。', { logs: logs });
  } catch (error) {
    failure(res, error);
  }
});

/**
 * 查询日志详情
 * GET /admin/logs/:id
 */
router.get('/:id', async function (req, res) {
  try {
    const log = await getLog(req);

    success(res, '查询日志成功。', { log });
  } catch (error) {
    failure(res, error);
  }
});

/**
 * 清空全部日志
 * DELETE /admin/logs/clear
 */
router.delete('/clear', async function (req, res) {
  try {
    await Log.destroy({ truncate: true });

    success(res, '清空日志成功。');
  } catch (error) {
    failure(res, error);
  }
});

/**
 * 删除日志
 * DELETE /admin/logs/:id
 */
router.delete('/:id', async function (req, res) {
  try {
    const log = await getLog(req);
    await log.destroy();

    success(res, '删除日志成功。');
  } catch (error) {
    failure(res, error);
  }
});

/**
 * 公共方法:查询当前日志
 */
async function getLog(req) {
  const { id } = req.params;

  const log = await Log.findByPk(id);
  if (!log) {
    throw new NotFound(`ID: ${id}的日志未找到。`)
  }

  return log;
}

module.exports = router;

  • 在清空日志里,我用了truncate: true,它的意思是截断表。作用是将表中的数据清空后,自增的id恢复从1开始
  • 还有要注意,清空全部日志路由,必须在删除日志路由的上面。不然就会先匹配到/:id,导致无法匹配到/clear的
4.6、app.js引入 参考自己的项目
const adminLogsRouter = require('./routes/admin/logs');

app.use('/admin/logs', adminAuth, adminLogsRouter);

相关文章:

  • DeepSeek在金融银行的应用方案
  • Unity基础——资源导出分享为Unity Package
  • 腾讯 DeepSeek-R1 × Vue3 使用体验报告
  • 深入浅出Spring Boot框架:从入门到精通
  • MySQL——创建与管理视图
  • ffmpeg-rockchip RK3588 armbian小盒子上编译rk硬件加速
  • 计算机毕业设计 ——jspssm510springboot 的人职匹配推荐系统
  • Linux下原子操作`__atomic_store_n`和`__atomic_load_n`的消耗问题
  • liunx安装redis并配置主从
  • ffmpeg常用方法(一)
  • 【MySQL】Mysql超大分页处理
  • 02_linux系统命令
  • 蓝桥杯备赛-拔河
  • 当下弹幕互动游戏源码开发教程及功能逻辑分析
  • excel
  • 网络渗透作业
  • 本地大模型编程实战(24)用智能体(Agent)实现智能纠错的SQL数据库问答系统(3)
  • 面试葵花宝典之React(持续更新中)
  • 华为机试牛客刷题之HJ75 公共子串计算
  • HTML第二节
  • 中国建造师官方网站查询/制作网页的网站
  • 四合一网站建设源码/百度软件应用中心下载
  • 国内网站是cn还是com/营销软文的范文
  • 基于html5设计的网站建设/百度推广优化
  • 库尔勒网站建设/手机登录百度pc端入口
  • 合肥网站制作哪家强/网络推广员上班靠谱吗