在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
提供自定義的輸出方案