在Egg开发实践中,常常会遇到一个问题:如何查看刚刚执行过的Egg组装的原生SQL语句呢?
javascript
能够直接在项目的config配置文件中添加MySQL配置debug: true
。这会启用底层模块mysql
的调试标志,而后输出有关SQL语句的详尽信息,效果以下:html
debug: true
方案有以下弊端:java
对于一个理想的SQL语句输出方案,咱们其实只关心两个问题:node
CabloyJS是基于Egg的上层开发框架,针对前面提到的两个核心问题,实现了以下效果mysql
这种SQL语句日志的输出效果:不只一目了然能够看到刚刚执行了多少SQL语句,并且每一条SQL语句的执行时间也是历历在目。固然,顺便咱们还能看到SQL语句是由哪一个链接对象执行的(经过threadId
)git
下面咱们看一下CabloyJS是如何实现的。这种实现机制也适用于其余Egg系的上层框架github
假设你已经建立了一个CabloyJS的项目,下面的源码均位于CabloyJS项目内sql
如何建立CabloyJS项目,请参见: 快速开始
为了让方案更灵活,咱们先扩展一下MySQL参数的定义数据库
node_modules/egg-born-backend/config/config.default.js
npm
// mysql config.mysql = { clients: { __ebdb: { // debug: true, hook: { meta: { color: 'orange', long_query_time: 0, }, callback: { onQuery, }, }, }, }, };
名称 | 说明 |
---|---|
debug | 若是为true,就是启用内置的调试标志。在这里没有启用 |
hook.meta | 包含hook的配置参数 |
hook.meta.color | 日志输出的颜色 |
hook.meta.long_query_time | 若是SQL语句的执行时间超过了long_query_time (ms),就会被输出到控制台。特别的,若是long_query_time 设为0 ,则输出全部SQL语句 |
hook.callback.onQuery | 为了提高灵活性,咱们能够经过onQuery 提供一个自定义的回调函数,当SQL语句的执行信息准备就绪时会被自动调用 |
Egg执行MySQL语句的技术栈以下:模块egg
-> 模块egg-mysql
-> 模块ali-rds
-> 模块mysql
在这里,咱们只须要改写模块ali-rds
便可
node_modules/@zhennann/ali-rds/lib/client.js
function RDSClient(options) { if (!(this instanceof RDSClient)) { return new RDSClient(options); } Operator.call(this); this.pool = mysql.createPool(options); const _hook = options.hook; const _getConnection = this.pool.getConnection.bind(this.pool); this.pool.getConnection = function(cb) { _getConnection(function(err, conn) { if (err) return cb(err, null); onQuery(conn, function(err) { if (err) return cb(err, null); onConnection(conn, function(err) { if (err) return cb(err, null); cb(null, conn); }); }); }); function onConnection(conn, cb) { if (!_hook || !_hook.callback || !_hook.callback.onConnection) return cb(null); if (conn.__hook_onConnection) return cb(null); conn.__hook_onConnection = true; co.wrap(_hook.callback.onConnection)(new RDSConnection(conn)).then(function() { cb(null); }).catch(function(err) { cb(err); }); } function onQuery(conn, cb) { if (!_hook || !_hook.callback || !_hook.callback.onQuery) return cb(null); if (conn.__hook_onQuery) return cb(null); conn.__hook_onQuery = true; const _query = conn.query; conn.query = function query(sql, values, cb) { const prevTime = Number(new Date()); const sequence = _query.call(conn, sql, values, cb); const _callback = sequence._callback; sequence._callback = function(...args) { const ms = Number(new Date()) - prevTime; _hook.callback.onQuery(_hook, ms, sequence, args); _callback && _callback(...args); }; return sequence; }; cb(null); } }; [ 'query', 'getConnection', ].forEach(method => { this.pool[method] = promisify(this.pool[method]); }); }
pool.getConnection
方法onConnection
和onQuery
onConnection
是在第一次建立connection对象时,执行一些初始化SQL语句,好比设置一些会话级别的变量,不是这里讨论的重点onQuery
的做用就是拦截connection.query
方法,在query执行前和执行后分别记录时间,从而获得SQL语句的执行时间,而后执行config配置中指定的回调函数hook.callback.onQuery
hook.callback.onQuery
咱们再回头看一下config配置文件中的回调函数是如何实现的
node_modules/egg-born-backend/config/config.default.js
function onQuery(hook, ms, sequence, args) { if (!hook.meta.long_query_time || hook.meta.long_query_time < ms) { const message = `threadId: ${sequence._connection.threadId}, ${ms}ms ==> ${sequence.sql}`; console.log(chalk.keyword(hook.meta.color)(message)); } }
hook.meta.long_query_time
,若是为0
或者小于执行时间,就会执行输出chalk
,并使用指定的颜色值hook.meta.color
输出SQL执行日志因为咱们改写了模块ali-rds
的源代码,因此咱们须要启用一个新的模块名称,在这里就是@zhennann/ali-rds
,发布到npm仓库便可
那么,如何使新模块@zhennann/ali-rds
生效呢?因为模块ali-rds
是被模块egg-mysql
所引用的。咱们若是还要改写模块egg-mysql
的源码,代价就未免太大了
在这里,咱们引入模块module-alias
,从而达到这样的效果:模块egg-mysql
源码不变,仍然是const rds = require('ali-rds');
,但实际上引用的倒是@zhennann/ali-rds
模块
module-alias
的机理,请参见:
https://github.com/ilearnio/module-alias
这里,咱们只需看一下如何使用模块module-alias
node_modules/egg-born-backend/index.js
const moduleAlias = require('module-alias'); moduleAlias.addAlias('ali-rds', '@zhennann/ali-rds');
这样,咱们就实现了一个轻巧的方案,不只能够直接在Egg上层框架中提供缺省的SQL语句输出方案,并且还能够经过覆盖config参数hook.callback.onQuery
提供自定义的输出方案