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

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',// 日志格式为 JSONformat: 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);

相关文章:

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中读取对应的配置。…...

是德科技keysight N5173B信号发生器,是一款经济高效的仪器

是德科技keysight N5173B信号发生器安捷伦N5173B信号源 是德N5173B微波模拟信号发生器,拥有 9 kHz 至 40 GHz 的频率覆盖范围,N5173B为宽带滤波器、放大器、接收机等器件的参数测试提供了必要的信号,是一款经济高效的仪器。 N5173B特点&…...

从零到一:如何用阿里云百炼和火山引擎搭建专属 AI 助手(DeepSeek)?

本文首发:从零到一:如何用阿里云百炼和火山引擎搭建专属 AI 助手(DeepSeek)? 阿里云百炼和火山引擎都推出了免费的 DeepSeek 模型体验额度,今天我和大家一起搭建一个本地的专属 AI 助手。  阿里云百炼为 …...

FFmpeg视频处理入门级教程

一、FFmpeg常规处理流程 #mermaid-svg-W8X1llNEyuYptV3I {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-W8X1llNEyuYptV3I .error-icon{fill:#552222;}#mermaid-svg-W8X1llNEyuYptV3I .error-text{fill:#552222;str…...

C/C++ | 每日一练 (4)

💢欢迎来到张胤尘的技术站 💥技术如江河,汇聚众志成。代码似星辰,照亮行征程。开源精神长,传承永不忘。携手共前行,未来更辉煌💥 文章目录 C/C | 每日一练 (4)题目参考答案基础容器序列容器std:…...

数据安全_笔记系列06:数据生命周期管理(存储、传输、使用、销毁)深度解析

数据安全_笔记系列06:数据生命周期管理(存储、传输、使用、销毁)深度解析 数据生命周期管理(存储、传输、使用、销毁)详解 数据生命周期管理(Data Lifecycle Management, DLM)是围绕数据从创建…...

后端返回文件流,前端导出excel文件

1、当后端接口返回文件流时,需前端导出excel文件,在请求中添加 responseType: blob限制条件,根据返回的文件流导出 封装的方法: /** * 公共的导出excel方法 * param {*} content 后端接口返回的二进制文件 * param {*} name 导出…...

Python开发 Flask框架面试题及参考答案

目录 Flask 的核心设计理念是什么?与 Django 相比有哪些显著差异? 解释 Flask 框架的核心理念及其作为 “微框架” 的优缺点 Flask 的依赖库有哪些?简述 Werkzeug 和 Jinja2 的作用 什么是 WSGI?Flask 如何基于 WSGI 实现服务端与应用的交互 解释 RESTful API 的设计原…...

Python 3.11 69 个内置函数(完整版)

一、数学与数值运算(12个) 函数 说明 示例 abs(x) 绝对值 abs(-5)→ 5 divmod(a, b) 返回(a//b, a%b) divmod(7,3)→ (2,1) max(iterable) 最大值 max([1,2,3])→ 3 min(iterable) 最小值 min([1,2,3])→ 1 pow(a, b) a^b(等…...

蓝桥杯备考:贪心算法之矩阵消除游戏

这道题是牛客上的一道题,它呢和我们之前的排座位游戏非常之相似,但是,排座位问题选择行和列是不会改变元素的值的,这道题呢每每选一行都会把这行或者这列清零,所以我们的策略就是先用二进制把选择所有行的情况全部枚举…...

跳跃游戏两则

跳跃游戏 给你一个非负整数数组 nums ,你最初位于数组的 第一个下标 。数组中的每个元素代表你在该位置可以跳跃的最大长度。 判断你是否能够到达最后一个下标,如果可以,返回 true ;否则,返回 false 。 思路 这里只…...

机器视觉--相机曝光

在现代工业生产的精密舞台上,机器视觉技术已然成为推动生产自动化、智能化的关键力量。而工业相机作为机器视觉系统的 “眼睛”,其曝光环节更是决定了视觉信息获取的质量与精度,如同为工业生产赋予了一双洞察入微的 “智慧之眼”,…...

基于 CFD 预测的机器学习第 2 部分:在 Benchmark 应用程序上使用 Stochos 预测流场

了解机器学习和 Stochos 如何彻底改变制造业的 CFD 预测。 挑战 预测复杂流体动力学场景中的流场一直是工程师和科学家面临的重大挑战。传统的计算流体动力学 (CFD) 方法需要大量的计算资源和时间,因此难以处理实时预测和大规模模拟。 此外…...

批量导出数据库表到Excel

这篇文章将介绍如何批量的将多个甚至成千上万的数据库表导出为Excel文件。 准备数据 如下图是数据库里的表,我们需要将它们全部导出为excel文件,这里以SQL Server数据库为例 新增导出 打开的卢导表工具,新建数据库连接,这里以S…...

力扣提升第一天

力扣提升第一天 题目链接:https://leetcode.cn/problems/design-memory-allocator/?envTypedaily-question&envId2025-02-25 一开始解题思路 暴力解决法 我奔着先从简单的写法做起,之后再想办法进行改进,心里已经预料到会出现超出时间…...

uni-app 开发 App 、 H5 横屏签名(基于lime-signature)

所用插件&#xff1a;lime-signature 使用到 CSS 特性 绝对定位transform 旋转transform-origin transform 原点 复习一下定位元素&#xff08;相对定位、绝对定位、粘性定位&#xff09; 代码# <template><view class"signature-page"><view clas…...

【Python】Python顺序语句经典题(四)

Python顺序语句经典练习题例题&#xff08;四&#xff09;。题目来源&#xff1a;Acwing 前三期合集&#xff1a;【Python】Python顺序语句经典题合集-CSDN博客 目录 1.最大值 题目描述 解题思路 AC代码 2.距离 题目描述 AC代码 3.燃料消耗 题目描述 AC代码 4.钞票…...

mysql的字符集和比较规则

mysql的字符集和比较规则 一、字符集&#xff08;Character Set&#xff09;二、比较规则&#xff08;Collation&#xff09;三、客户端与服务器的字符集转换四、注意事项总结 深度解读mysql是怎样运行的 MySQL的字符集和比较规则是其处理字符串存储、传输及比较的核心机制&…...

Vue3 + Vite + TS,使用 配置项目别名属性:server

官网地址传送门 点哇点哇&#xff0c;vite 官网传送门 直接上马 server: {https: false, // 是否开启 httpsopen: true, // 是否自动在浏览器中打开port: 8001, // 端口号host: "0.0.0.0",// 跨域代理proxy: {/api: {target: "http://localhost:3000", …...

03_pyqt5 + vlc 实现视频播放器

1.功能需求如图 按钮: 播放/暂停, 前进/后退, 视频上一个/下一个, 打开视频进度条: 视频进度条显示, 进度条拖拽, 音量控制按键控制: 1,2,3,4缩放画面大小, 2.方案选择 开发语言: python UI界面: pyqt5 qt_designed 设计ui布局 视频编码: python-vlc 方案说明: 视频解码可…...

Grafana使用日志5--如何重置Grafana密码

背景 有时候当账号太多的时候&#xff0c;根本记不住所有的账号密码&#xff0c;这时候就很容易登录失败&#xff0c;这时候怎么办呢&#xff1f; 接下来就让我来给大家演示一下Grafana的账号如果忘记了的话&#xff0c;该怎么找回自己的账号密码 操作 让我们来看一下具体的…...

使用 pytest-mock 进行 Python 高级单元测试与模拟

一、单元测试与模拟的意义 在软件开发中,单元测试用于验证代码逻辑的正确性。但实际项目中,代码常依赖外部服务(如数据库、API、文件系统)。直接测试这些依赖会导致: 测试速度变慢测试结果不可控产生副作用(如真实发送邮件)模拟(Mocking) 技术通过创建虚拟对象替代真…...

索提诺比率(Sortino Ratio):更精准的风险调整收益指标(中英双语)

索提诺比率&#xff08;Sortino Ratio&#xff09;&#xff1a;更精准的风险调整收益指标 &#x1f4c9;&#x1f4ca; &#x1f4cc; 什么是索提诺比率&#xff1f; 在投资分析中&#xff0c;我们通常使用 夏普比率&#xff08;Sharpe Ratio&#xff09; 来衡量风险调整后的…...

prometheus+node_exporter+grafana监控K8S信息

prometheusnode_exportergrafana监控K8S 1.prometheus部署2.node_exporter部署3.修改prometheus配置文件4.grafana部署 1.prometheus部署 包下载地址&#xff1a;https://prometheus.io/download/ 将包传至/opt 解压 tar xf prometheus-2.53.3.linux-amd64.tar.gz 移动到…...

IDEA关闭SpringBoot程序后仍然占用端口的排查与解决

IDEA关闭SpringBoot程序后仍然占用端口的排查与解决 问题描述 在使用 IntelliJ IDEA 开发 Spring Boot 应用时&#xff0c;有时即使关闭了应用&#xff0c;程序仍然占用端口&#xff08;例如&#xff1a;4001 端口&#xff09;。这会导致重新启动应用时出现端口被占用的错误&a…...

kafka的ACL配置的sasl.kerberos.principal.to.local.rules配置解释

kafka配置acl认证的用户名转换规则 1、Kerberos中的介绍2、自定义sasl user name3、自定义ssl 的用户名4、关于kafka配置kerberos以及开启acl的实践 1、Kerberos中的介绍 Kerberos 关于此配置项的解释 https://web.mit.edu/Kerberos/krb5-latest/doc/admin/conf_files/krb5_co…...

山东大学软件学院nosql实验三

实验题目&#xff1a; 用Java做简单查询(2学时) 实验内容 用API方式&#xff0c;做简单查询。 实验要求 在以下要求中选择至少2个&#xff0c;使用Java语言实现数据查询&#xff0c;最终把数据输出到前端界面。 &#xff08;1&#xff09;找出年龄小于20岁的所有学生 &…...

Feign 类型转换问题解析:如何正确处理 `ResponseEntity<byte[]>` 返回值

在微服务架构中,Feign 是一种常见的用于服务间调用的客户端,它允许我们通过声明式接口来调用远程服务。使用 Feign 时,我们通常通过接口方法的返回类型来接收服务的响应体。然而,某些情况下,我们会遇到 Feign 无法正确解析响应体类型的问题,尤其是当服务返回一个如 Respo…...

零样本学习 zero-shot

1 是什么 2 如何利用零样本学习进行跨模态迁移&#xff1f; demo代码 安装clip pip install ftfy regex tqdm pip install githttps://github.com/openai/CLIP.git import torch import clip from PIL import Image# 加载 CLIP 模型 device "cuda" if torch.cuda.i…...

《深度学习实战》第3集:循环神经网络(RNN)与序列建模

第3集&#xff1a;循环神经网络&#xff08;RNN&#xff09;与序列建模 引言 在深度学习领域&#xff0c;处理序列数据&#xff08;如文本、语音、时间序列等&#xff09;是一个重要的研究方向。传统的全连接网络和卷积神经网络&#xff08;CNN&#xff09;难以直接捕捉序列中…...