Handling and Logging Errors处理错误日志记录
Handling and Logging Errors
Introduction
现实会出很多错误,例如数据库连接断开,要发送合适的消息给回Client,并在Server记录日志。
Handing Rejected Promises
如果手停止MongoDB数据库再发送请求就会出现错误
出现无法请求的Promise错误,且没有妥善处理。回到genres.js
// routes/genres.js
router.get("/", async (req, res) => {const genres = await Genre.find().sort("name");res.send(genres);
});
这里await了但是没有用try-catch去处理错误,类似于对Promise使用then以后没有使用catch一样。如果使用Promise,要使用catch、采用异步方法则使用try-catch
router.get("/", async (req, res) => {try {const genres = await Genre.find().sort("name");res.send(genres);} catch (ex) {res.status(500).send('Something failed.')}
});
这个时候就会再请求,Client会收到提示。
Express Error Middleware
如果是现实,我们要给每个句柄都套上try-catch,而且要修改的话要到每一处都做好修改,所以要把处理错误的逻辑进行集中。回到index.js添加中间件。
需要一个err参数,为在应用中某处捕捉到的异常。
// index.js
app.use(express.json());
// ...
app.use("/api/auth", auth);app.use(function (err, req, res, next) {// Log the exceptionres.status(500).send('Something failed.')
})
现在看到genres.js刚才的try-catch块,在catch块中,要将控制权转移到错误处理函数,所以在路由句柄加上一个next,来转移控制权。
router.get("/", async (req, res, next) => {try {//...} catch (ex) {next();}
});
现实开发中,处理函数会很长,所以要做封装。新建middleware/error.js
// middleware/error.js
module.exports = function (err, req, res, next) {// Log the exceptionres.status(500).send('Something failed.')
}
// index.js
const error = require("./middleware/error");
app.use(error);
**注意:此处不是没有调用函数,只是传来一了一个error的应用。**目前依然要以来try-catch来捕捉错误,所以要进一步优化。
Removing Try Catch Blocks
要将那些高级代码放到单独的函数,在genres.js先新建一个async Middleware,这个函数的基本结构是一个try-catch块。
function asycMiddleware() {try{// ... }catch(ex){next(ex);}
}
传入路由句柄函数,并在try当中调用。这里路由句柄函数是async的,所以要稍作修改。
async function asycMiddleware(handler) {try{await handler();}catch(ex){next(ex);}
}
router.get("/", async asyncMiddleware((req, res) => {const genres = await Genre.find().sort("name");res.send(genres);
}));
把这给路由句柄作为参数传入。但又有新的问题,request和response怎么传入。我们唯一的参数只有handler。如何访问request,response和ex呢?
**我们在定义Express路由时,*我们没有调用中中间函数,只是传入了一个引用。**例如
routes.get("/another", (req, res, next) => {});
这里我们并没有调用这个Arrow Function,只是传入了他的引用,是Express自己调用了它。这三个参数也是由Express传给它的。
在这里其实是调用了async Middleware,我们没有传入一个参数有三个参数的函数,所以要对这个处理函数做修改,我们让它返回一个函数,这样Express会自己调用这个返回的函数,然后传入三个参数。这个函数就类似于一个Factory Function。调用它,返回一个路由句柄函数。
function asycMiddleware(handler) {return async (req, res, next) => {try {await handler(req, res);} catch (ex) {next(ex);}};
}
这里返回的是async函数,所以其本身就不需要为async函数。现在新建middleware/async.js,将这个函数放入导出即可。
Express Async Errors
刚才虽然封装好了错误处理函数,但依然需要在每个地方调用,现在可以利用一个模块,它会自动捕捉异常,当发送请求时,它会自动把路由句柄包裹。
npm i express-async-errors
index.js调用
require("express-async-errors");
现在去掉genres.js刚才嵌套的错误处理函数即可。
router.get("/", (req, res) => {const genres = await Genre.find().sort("name");res.send(genres);
});
Logging Errors
使用Winston记录日志。index.js调用,将logger(记录器)对象储存。
// index.js
const winston = require("winston");
这个logger对象,有输运特性(Transport)。输运基本上就是日志记录的介质。
Winston有很多记录介质,Console:发送到控制台、File/Http:发送给指定终端地址,也可搭配其他附加的特定Winston模块使用,例如MongoDB。刚才的加载默认导出为logger。
// index.js
winston.add(winston.transports.File, { filename: "logfile.log" });
回到error.js。当捕捉到错误就可以记录了。
// middleware/error.js
const winston = require("winston");module.exports = function (err, req, res, next) {// Log the exceptionwinston.log('error', err.message, err); // winston.error(err.message, err);// error// warn// info// verbose// debug// sillyres.status(500).send("Something failed.");
};
使用log,第一个参数为记录等级,从下往上等级越高。然后传入错误信息,也可以传入原始信息作为第三个参数。也可以直接使用error方法直接输出错误,这样少一个参数。
回到genres.js,抛出一个错误并测试。
// routes/genres.js
router.get("/",async (req, res, next) => {throw new Error("Could not get the genres.");//...}
);
测试以后会发现多一个文件:logfile.log。里面的JSON记录了详细的错误信息。
Logging to MongoDB
npm i winston-mongodb
回到index.js,添加运输介质。
// index.js
require("winston-mongodb");
winston.add(winston.transports.MongoDB, {db: "mongodb://localhost/vidly",});
这里直接加载整个模块即可,现实开发中,日志记录要单独建一个数据库,此处直接使用同一个vidly库。
现在测试一下,打开MongoDB就能看到多一个文档log用于记录日志。
可以在设定传输介质的时候只记录特定等级的日志。假设记录info
winston.add(winston.transports.MongoDB, {db: "mongodb://localhost/vidly",level: "info",
});
这里info是第三级,意味着比它高级的error和warn等级的日志都会被记录。
Uncaught Exceptions
现在添加到错误捕捉逻辑只能捕捉请求流程中的错误,是特别针对Express的,发生在Express之外的错误是不被捕捉的。例如
// index.js
throw new Error("...");
程序会无法运行,但这个错误不会被记录在logfile.log。使用process对象处理。
process对象有一个事件发生器(Event Emitter),它可以产生并发起事件,其有个方法on用于监听事件,Node中有个特定的时间uncaught Exception,它在Node处理过程中出现错误时发起,但我们没有专门捕捉它的try-catch。
process.on("uncaughtException", (ex) => {console.log("WE GOT AN UNCAUGHT EXCEPTION.");winston.error(ex.message, ex);process.exit(1);
});
第二个参数为错误处理函数,在里面使用Winston记录日志即可。
Unhandled Promise Rejections
刚才的方法只能处理同步代码,不能处理异步Promise
// index.js
const p = Promise.reject(new Error('Somethind failed miserably!'));
p.then(() => consol.log('Done.'));
这就是一个未被处理的被拒Promise。
利用刚才类似的方法,事件换为unhandled Rejection。
process.on("unhandledRejection", (ex) => {console.log("WE GOT AN UNHANDLED REJECTION.");winston.error(ex.message, ex);process.exit(1);
});
此处process.exit(1)是为了重启引用,0为成功值,除此之外都为失败值。
还有一种方法,不需要process而是利用Winston的辅助函数。
winston.handleExceptions(new winston.transports.File({ filename: "uncaughtExceptions.log" }));
// With this, we can handle uncaughtExceptions as well, also unhandledRejection
还有另一种。我们直接抛出错误,让被拒Promise变为一个未处理的异常。
process.on("unhandledRejection", (ex) => {throw ex;});// In this way, winston will catch the ex automaticly, so we can hadle both of them in less code.
Extracting the Routes
现在的index.js太冗杂了,很多功能都集中在一块,现在把功能做单独封装。
新建文件夹startup,新建文件route.js。里面导出一个函数,并放置所有的路由和中间件。
// startup/route.js
const genres = require("../routes/genres");
const customers = require("../routes/customers");
const movies = require("../routes/movies");
const rentals = require("../routes/rentals");
const users = require("../routes/users");
const auth = require("../routes/auth");
const error = require("../middleware/error");
const express = require("express");module.exports = function (app) {app.use(express.json());app.use("/api/genres", genres);app.use("/api/customers", customers);app.use("/api/movies", movies);app.use("/api/rental", rentals);app.use("/api/users", users);app.use("/api/auth", auth);app.use(error);
};
这里我们接收一个app作为参数,整个应用程序应该只有一个单一的app实例,而不是在这另外创建一个
// index.js
const express = require("express");
const app = express();require("./startup/routes")(app);
注意更改引用地址
Extracting the DB Logic
新建startup/db.js
// startup/db.js
const winston = require("winston");
const mongoose = require("mongoose");module.exports = function () {mongoose.connect("mongodb://localhost/vidly").then(() => winston.info("Connected to MongoDB..."));
};
这里改用Winston做记录,取代Console,也不需要catch块了。因为已经做过处理了。
// index.js
require("./startup/db")();
Extracting the Logging Logic
新建startup/logging.js
// startup/logging.js
const winston = require("winston");
require("winston-mongodb");module.exports = function () {winston.handleExceptions(new winston.transports.File({ filename: "uncaughtExceptions.log" }));process.on("unhandledRejection", (ex) => {throw ex;});winston.add(winston.transports.File, { filename: "logfile.log" });winston.add(winston.transports.MongoDB, {db: "mongodb://localhost/vidly",level: "info",}); // Only message level > info will be recored(error warn info),
};
// index.js
require("./startup/logging")();
require("./startup/routes")(app);
require("./startup/db")();
这里logging放在最上面,以确保所有出错都会被捕捉。
Extracting the Config Logic
新建startup/config.js
// startup/config.js
const config = require("config");module.exports = function () {if (!config.get("jwtPrivateKey")) {throw new Error("FATAL ERROR: jwtPrivateKey is not defined");}
};
取代console,抛出Error异常以由Winston记录错误并结束进程。
// index.js
require("./startup/config")();
Extracting the Validation Logic
新建startup/validation.js
// startup/validation.js
const Joi = require("joi");
// A MongoDB ObjectId validator for Joi.
module.exports = function () {Joi.objectId = require("joi-objectid")(Joi);
};
// index.js
const winston = require('winston');
const express = require("express");
const app = express();require("./startup/logging")();
require("./startup/routes")(app);
require("./startup/db")();
require("./startup/config")();
require("./startup/validation")();const port = process.env.PORT || 3000;
app.listen(port, () => winston.info(`Listening on port ${port}...`));
现在index.js简短很多了。把listen的console改为Winston即可。
Showing Unhandled Exceptions on the Console
我们需要将报错显示到控制台,不然有可能换台电脑,不会正常显示进程终止的原因。
// startup/logging.js
module.exports = function () {winston.handleExceptions(new winston.transports.Console({ colorize: true, prettyPrint: true }),new winston.transports.File({ filename: "uncaughtExceptions.log" }));// ...
};
加上Console即可。里面设置了一些选项让可读性变强。
Handling and Logging Errors处理错误日志记录相关推荐
- PHP错误日志记录:display_errors与log_errors的区别
我们所做的东西,无论在开发环境还是在生产环境都可能会出现一些问题. 开发环境下,我们会要求错误尽可能详细的呈现出来,错误提示信息越详细越好,越详细越能帮助开发人员确定问题所在并从根本上解决他们. 生产 ...
- App错误日志记录到本地
1.使用背景:客户使用App过程中程序出错,如无法复现Bug,会很难受.所以,将错误记录至本地并发送后台,会方便日后优化及维护. 2.bug捕捉的工具类 import java.io.File; im ...
- .Net Core中间件和过滤器实现错误日志记录
1.中间件的概念 ASP.NET Core的处理流程是一个管道,中间件是组装到应用程序管道中用来处理请求和响应的组件.每个中间件可以: 选择是否将请求传递给管道中的下一个组件. 可以在调用管道中的下一 ...
- keil debug如何在watch直接修改变量值_python日志记录系列教程,内置logging模块(一),直接使用logging模块的基础日志记录
前言:成熟的软件开发不可避免的要进行日志记录,python内置模块logging提供了强大的日志记录能力,本文将从多个角度,由浅入深的介绍logging的常见使用方法和一些基本概念,本此系列文章分为两 ...
- asp.net Web项目中使用Log4Net进行错误日志记录
使用log4net可以很方便地为应用添加日志功能.应用Log4net,开发者可以很精确地控制日志信息的输出,减少了多余信息,提高了日志记录性能.同时,通过外部配置文件,用户可以不用重新编译程序就能改变 ...
- 一个错误日志记录工具类
package com.moxiu.downloader.util;/*** 当程序发生Uncaught异常的时候,有该类来接管程序,并记录错误日志* Created by ZGP on 2017/7 ...
- python报错输出到日志_Python下的异常处理及错误日志记录
Python使用被称为异常的特殊对象来表达执行期间发现的错误.当这些异常没有被捕获并处理时,程序将停止,并向控制台打印错误信息.这个错误信息通常是一个traceback,包含了异常的类型,以及诱发这个 ...
- php fpm 日志记录,使用Nginx在PHP-FPM 7上启用错误日志记录?
comment above by the_nuts帮我解决了这个问题.目录和文件不存在. 为了诊断,我补充说: print("\n\n log_errors: ".ini_get( ...
- ASP.NET MVC的最佳日志记录库
目录 介绍 4个日志记录库 log4net Log4net记录文本文件中 Log4net记录到数据库中 NLOG Nlog日志记录在文本文件中 NLog日志记录到数据库中 Serilog Serilo ...
最新文章
- 用LaTeX优雅地书写伪代码:Algorithm2e简明指南
- asp导出EXCEL数字格式及自动适应宽度的问题
- 深入浅出统计学 第六章 排列与组合
- [算法 笔记]堆排序(续)
- html5 canvas 版 hello world! 暨haXe简介
- 从零开始学电脑_带你从零开始学装机 打造自己的专属电脑之固态和机械硬盘搭配篇...
- 人工智能系统研究的9大挑战和4大趋势
- 字典超详细--python
- 哈工大大数据实验_【新闻动态】南京大学PASA大数据实验室在KDD Cup 2020 AutoGraph自动化图数据建模国际挑战赛中荣获第二名...
- Python模块的导入
- 51单片机的篮球计分器设计
- 计算机有损压缩编码,有损压缩格式有哪些
- 无法启动此程序,因为计算机中丢失MSVCR71.dll.丢失的解决方法分享
- c语言求定积分的程序,C语言求定积分
- 前端学习笔记之页面制作(一)——PS切图
- 30行代码实现微信自动回复机器人
- 编写矩阵运算程序(C语言)
- 使用 requireJS 的shim参数 解决插件jequery 插件问题
- 1644年,紫禁城换了三任主人
- 417页16万字智慧医院信息化大数据建设 设计方案
热门文章
- 无线路由器上安装OpenWRT,在需要标准802.1x认证的网络中无线上网
- 比特、字节、字的概念以及相互之间的联系
- Centos7查看redis版本(问题:redis-cli: command not found)
- android新浪微博oauth2.0,新浪微博 Android SDK中OAuth2.0隐式授权部分的一个代码逻辑问题...
- 华为wifi设置虚拟服务器,家用路由器设置虚拟服务器
- 怎样寻找最适合的创业伙伴
- Ubuntu搭建PPTP和连接到PPTP
- 大厂资历程序员求职以为很容易,没想到栽在这里…
- 【JavaScript】原生js阻止事件的三种方式
- Git分支合并操作教程(超详细配图说明)