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处理错误日志记录相关推荐

  1. PHP错误日志记录:display_errors与log_errors的区别

    我们所做的东西,无论在开发环境还是在生产环境都可能会出现一些问题. 开发环境下,我们会要求错误尽可能详细的呈现出来,错误提示信息越详细越好,越详细越能帮助开发人员确定问题所在并从根本上解决他们. 生产 ...

  2. App错误日志记录到本地

    1.使用背景:客户使用App过程中程序出错,如无法复现Bug,会很难受.所以,将错误记录至本地并发送后台,会方便日后优化及维护. 2.bug捕捉的工具类 import java.io.File; im ...

  3. .Net Core中间件和过滤器实现错误日志记录

    1.中间件的概念 ASP.NET Core的处理流程是一个管道,中间件是组装到应用程序管道中用来处理请求和响应的组件.每个中间件可以: 选择是否将请求传递给管道中的下一个组件. 可以在调用管道中的下一 ...

  4. keil debug如何在watch直接修改变量值_python日志记录系列教程,内置logging模块(一),直接使用logging模块的基础日志记录

    前言:成熟的软件开发不可避免的要进行日志记录,python内置模块logging提供了强大的日志记录能力,本文将从多个角度,由浅入深的介绍logging的常见使用方法和一些基本概念,本此系列文章分为两 ...

  5. asp.net Web项目中使用Log4Net进行错误日志记录

    使用log4net可以很方便地为应用添加日志功能.应用Log4net,开发者可以很精确地控制日志信息的输出,减少了多余信息,提高了日志记录性能.同时,通过外部配置文件,用户可以不用重新编译程序就能改变 ...

  6. 一个错误日志记录工具类

    package com.moxiu.downloader.util;/*** 当程序发生Uncaught异常的时候,有该类来接管程序,并记录错误日志* Created by ZGP on 2017/7 ...

  7. python报错输出到日志_Python下的异常处理及错误日志记录

    Python使用被称为异常的特殊对象来表达执行期间发现的错误.当这些异常没有被捕获并处理时,程序将停止,并向控制台打印错误信息.这个错误信息通常是一个traceback,包含了异常的类型,以及诱发这个 ...

  8. php fpm 日志记录,使用Nginx在PHP-FPM 7上启用错误日志记录?

    comment above by the_nuts帮我解决了这个问题.目录和文件不存在. 为了诊断,我补充说: print("\n\n log_errors: ".ini_get( ...

  9. ASP.NET MVC的最佳日志记录库

    目录 介绍 4个日志记录库 log4net Log4net记录文本文件中 Log4net记录到数据库中 NLOG Nlog日志记录在文本文件中 NLog日志记录到数据库中 Serilog Serilo ...

最新文章

  1. 用LaTeX优雅地书写伪代码:Algorithm2e简明指南
  2. asp导出EXCEL数字格式及自动适应宽度的问题
  3. 深入浅出统计学 第六章 排列与组合
  4. [算法 笔记]堆排序(续)
  5. html5 canvas 版 hello world! 暨haXe简介
  6. 从零开始学电脑_带你从零开始学装机 打造自己的专属电脑之固态和机械硬盘搭配篇...
  7. 人工智能系统研究的9大挑战和4大趋势
  8. 字典超详细--python
  9. 哈工大大数据实验_【新闻动态】南京大学PASA大数据实验室在KDD Cup 2020 AutoGraph自动化图数据建模国际挑战赛中荣获第二名...
  10. Python模块的导入
  11. 51单片机的篮球计分器设计
  12. 计算机有损压缩编码,有损压缩格式有哪些
  13. 无法启动此程序,因为计算机中丢失MSVCR71.dll.丢失的解决方法分享
  14. c语言求定积分的程序,C语言求定积分
  15. 前端学习笔记之页面制作(一)——PS切图
  16. 30行代码实现微信自动回复机器人
  17. 编写矩阵运算程序(C语言)
  18. 使用 requireJS 的shim参数 解决插件jequery 插件问题
  19. 1644年,紫禁城换了三任主人
  20. 417页16万字智慧医院信息化大数据建设 设计方案

热门文章

  1. 无线路由器上安装OpenWRT,在需要标准802.1x认证的网络中无线上网
  2. 比特、字节、字的概念以及相互之间的联系
  3. Centos7查看redis版本(问题:redis-cli: command not found)
  4. android新浪微博oauth2.0,新浪微博 Android SDK中OAuth2.0隐式授权部分的一个代码逻辑问题...
  5. 华为wifi设置虚拟服务器,家用路由器设置虚拟服务器
  6. 怎样寻找最适合的创业伙伴
  7. Ubuntu搭建PPTP和连接到PPTP
  8. 大厂资历程序员求职以为很容易,没想到栽在这里…
  9. 【JavaScript】原生js阻止事件的三种方式
  10. Git分支合并操作教程(超详细配图说明)