Skip to content

Commit b61fc6e

Browse files
Leon van Kammengergelyke
authored andcommitted
feat(mysql): Mysql support
1 parent 52e4125 commit b61fc6e

7 files changed

Lines changed: 195 additions & 35 deletions

File tree

lib/consts.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ module.exports = {
44
},
55
PROTOCOLS: {
66
POSTGRES: 'pg',
7+
MYSQL: 'mysql',
78
HTTP: 'http',
89
MONGODB: 'mongodb',
910
REDIS: 'redis'

lib/instrumentations/mysql.js

Lines changed: 40 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,73 @@
11
var Shimmer = require('../utils/shimmer')
2+
var consts = require('../consts')
3+
var utils = require('./utils')
24

35
var CONNECTION_OPERATIONS = [
46
'connect',
5-
'query'
7+
'query',
8+
'end'
69
]
710

811
var POOL_OPERATIONS = [
9-
'getConnection'
12+
'getConnection',
13+
'query',
14+
'destroy'
1015
]
1116

17+
function decideWrap (me, original, args, agent, methodName) {
18+
if (methodName === 'query') {
19+
return utils.wrapQuery.call(me, original, args, agent, composeAgentData(me.config, args))
20+
} else {
21+
return original.apply(me, args)
22+
}
23+
}
24+
25+
function composeAgentData (config, args) {
26+
var connectionURI
27+
if (!!config.user && !!config.host && !!config.port && !!config.database) {
28+
connectionURI = 'mysql://' + (config.user || 'unknown') + '@' +
29+
(config.host || 'unknown') + ':' +
30+
(config.port || 'unknown') + '/' +
31+
(config.database || 'unknown')
32+
}
33+
return {
34+
protocol: consts.PROTOCOLS.MYSQL,
35+
url: connectionURI || undefined,
36+
host: config.host || 'unknown',
37+
method: utils.tryParseSql(args[0] || ''),
38+
parseError: function (err) {
39+
// *FIXME* mysql responsecodes != http response codes
40+
return err ? 400 : 200
41+
}
42+
}
43+
}
44+
1245
module.exports = function (mysql, agent) {
1346
var _createConnection = mysql.createConnection
1447
var _createPool = mysql.createPool
1548

1649
mysql.createConnection = function (config) {
1750
var Connection = _createConnection(config)
1851

19-
Shimmer.wrap(Connection, 'Connection', CONNECTION_OPERATIONS, function (original) {
52+
Shimmer.wrap(Connection, 'Connection', CONNECTION_OPERATIONS, function (original, name) {
2053
return function () {
2154
var args = Array.prototype.slice.apply(arguments)
2255
var last = args.length - 1
2356
var callback = args[last]
2457

25-
if (typeof callback === 'function') {
58+
if (callback === 'function') {
2659
args[last] = agent.bind(callback)
2760
}
28-
29-
return original.apply(this, args)
61+
return decideWrap(this, original, args, agent, name)
3062
}
3163
})
32-
3364
return Connection
3465
}
3566

3667
mysql.createPool = function (config) {
3768
var Pool = _createPool(config)
3869

39-
Shimmer.wrap(Pool, 'Pool', POOL_OPERATIONS, function (original) {
70+
Shimmer.wrap(Pool, 'Pool', POOL_OPERATIONS, function (original, name) {
4071
return function () {
4172
var args = Array.prototype.slice.apply(arguments)
4273
var last = args.length - 1
@@ -45,13 +76,11 @@ module.exports = function (mysql, agent) {
4576
if (typeof callback === 'function') {
4677
args[last] = agent.bind(callback)
4778
}
48-
49-
return original.apply(this, args)
79+
return decideWrap(this, original, args, agent, name)
5080
}
5181
})
5282

5383
return Pool
5484
}
55-
5685
return mysql
5786
}
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
'use strict'
2+
3+
var wrap = require('./mysql')
4+
var expect = require('chai').expect
5+
6+
function fakeAgent (sandbox) {
7+
return {
8+
generateSpanId: function () { return 'fakeSpanId' },
9+
getMicrotime: function () { return 42 },
10+
getTransactionId: function () { return 'fakeTransactionId' },
11+
clientSend: sandbox.spy(),
12+
clientReceive: sandbox.spy(),
13+
CLIENT_SEND: 'fakeSend'
14+
}
15+
}
16+
17+
describe('The mysql wrapper', function () {
18+
var agent
19+
var connection
20+
it('should connect & instrument query & catch error', function (done) {
21+
agent = fakeAgent(this.sandbox)
22+
var mysql = wrap(require('mysql'), agent)
23+
connection = mysql.createConnection({
24+
host: process.env.MYSQL_HOST || 'localhost', //
25+
user: process.env.MYSQL_USER || 'root', // these should be
26+
password: process.env.MYSQL_PASSWORD || '', // default install settings
27+
database: process.env.MYSQL_DATABASE || 'information_schema'
28+
})
29+
connection.connect(function (err) {
30+
if (err) {
31+
return console.error('could not connect to mysql: install mysql or check "mysql -uroot" in console', err)
32+
}
33+
var queryStr = 'SELECT 1 + 1 AS solution'
34+
connection.query(queryStr, function (err, rows, fields) {
35+
if (err) throw err
36+
expect(rows[0].solution).to.eql(2)
37+
expect(agent.clientReceive).to.have.been.calledWith({
38+
host: 'localhost',
39+
id: 'fakeTransactionId',
40+
method: 'SELECT',
41+
mustCollect: undefined,
42+
protocol: 'mysql',
43+
responseTime: 0,
44+
spanId: 'fakeSpanId',
45+
status: 0,
46+
statusCode: 200,
47+
time: 42,
48+
url: 'mysql://root@localhost:3306/information_schema'
49+
})
50+
done()
51+
})
52+
expect(agent.clientSend).to.have.been.called
53+
expect(agent.clientSend).to.have.been.calledWith({
54+
host: 'localhost',
55+
id: 'fakeTransactionId',
56+
method: 'SELECT',
57+
spanId: 'fakeSpanId',
58+
time: 42,
59+
type: 'fakeSend',
60+
url: 'mysql://' + connection.config.user + '@' +
61+
connection.config.host + ':' +
62+
connection.config.port + '/' +
63+
connection.config.database
64+
})
65+
})
66+
})
67+
it('should instrument error', function (done) {
68+
agent.clientSend.reset()
69+
agent.clientReceive.reset()
70+
var queryStr = 'SELECT 1 + AS solution'
71+
connection.query(queryStr, function (err, rows, fields) {
72+
expect(err !== false)
73+
expect(agent.clientSend).to.have.been.called
74+
expect(agent.clientReceive).to.have.been.calledWith({
75+
host: 'localhost',
76+
id: 'fakeTransactionId',
77+
method: 'SELECT',
78+
mustCollect: '1',
79+
protocol: 'mysql',
80+
responseTime: 0,
81+
spanId: 'fakeSpanId',
82+
status: 1,
83+
statusCode: 400,
84+
time: 42,
85+
url: 'mysql://root@localhost:3306/information_schema'
86+
})
87+
done()
88+
})
89+
})
90+
})

lib/instrumentations/mysql.spec.js

Lines changed: 54 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,47 @@
11
'use strict'
22

3-
var mysql = require('./mysql')
3+
var utils = require('./utils')
4+
var wrap = require('./mysql')
45
var Shimmer = require('../utils/shimmer')
56
var expect = require('chai').expect
67

8+
var fakeConfig = {
9+
config: {
10+
user: 'flop'
11+
}
12+
}
13+
var fakeConnection = { me: 'fakeConnection' }
14+
var fakePool = { me: 'fakePool' }
15+
716
var CONNECTION_OPERATIONS = [
817
'connect',
9-
'query'
18+
'query',
19+
'end'
1020
]
1121

1222
var POOL_OPERATIONS = [
13-
'getConnection'
23+
'getConnection',
24+
'query',
25+
'destroy'
1426
]
1527

28+
function fakeAgent (sandbox) {
29+
return {
30+
generateSpanId: function () { return 'fakeSpanId' },
31+
getMicrotime: function () { return 42 },
32+
getTransactionId: function () { return 'fakeTransactionId' },
33+
clientSend: sandbox.spy(),
34+
clientReceive: sandbox.spy(),
35+
CLIENT_SEND: 'fakeSend'
36+
}
37+
}
38+
1639
describe('The mysql wrapper', function () {
1740
it('should wrap mysql', function () {
1841
var fakeMysql = { }
1942

20-
var wrappedFakeMysql = mysql(fakeMysql, null)
43+
var agent = fakeAgent(this.sandbox)
44+
var wrappedFakeMysql = wrap(fakeMysql, agent)
2145

2246
expect(wrappedFakeMysql.createPool).to.be.a('function')
2347
expect(wrappedFakeMysql.createConnection).to.be.a('function')
@@ -26,18 +50,15 @@ describe('The mysql wrapper', function () {
2650
it('should wrap Connections', function () {
2751
var shimmerWrapStub = this.sandbox.stub(Shimmer, 'wrap')
2852

29-
var fakeConfig = { me: 'fakeConfig' }
30-
31-
var fakeConnection = { me: 'fakeConnection' }
32-
3353
var createConnectionStub = this.sandbox.stub()
3454
createConnectionStub.returns(fakeConnection)
3555

3656
var fakeMysql = {
3757
createConnection: createConnectionStub
3858
}
3959

40-
var wrappedFakeMysql = mysql(fakeMysql, null)
60+
var agent = fakeAgent(this.sandbox)
61+
var wrappedFakeMysql = wrap(fakeMysql, agent)
4162

4263
wrappedFakeMysql.createConnection(fakeConfig)
4364

@@ -55,18 +76,15 @@ describe('The mysql wrapper', function () {
5576
it('should wrap Pools', function () {
5677
var shimmerWrapStub = this.sandbox.stub(Shimmer, 'wrap')
5778

58-
var fakeConfig = { me: 'fakeConfig' }
59-
60-
var fakePool = { me: 'fakePool' }
61-
6279
var createPoolStub = this.sandbox.stub()
6380
createPoolStub.returns(fakePool)
6481

6582
var fakeMysql = {
6683
createPool: createPoolStub
6784
}
6885

69-
var wrappedFakeMysql = mysql(fakeMysql, null)
86+
var agent = fakeAgent(this.sandbox)
87+
var wrappedFakeMysql = wrap(fakeMysql, agent)
7088

7189
wrappedFakeMysql.createPool(fakeConfig)
7290

@@ -80,4 +98,26 @@ describe('The mysql wrapper', function () {
8098
POOL_OPERATIONS
8199
)
82100
})
101+
102+
it('should use wrapQuery to wrap query function', function (done) {
103+
var fakeWrapQuery = this.sandbox.stub(utils, 'wrapQuery')
104+
var agent = fakeAgent(this.sandbox)
105+
var queryStub = this.sandbox.stub()
106+
107+
var fakeMysql = {
108+
createConnection: function () {
109+
return {
110+
config: fakeConfig,
111+
query: queryStub
112+
}
113+
}
114+
}
115+
var wrappedFakeMysql = wrap(fakeMysql, agent)
116+
this.sandbox.stub(Shimmer, 'wrap', function (nodule, path, name, cb) {
117+
cb(queryStub, 'query').call({ config: fakeConfig })
118+
expect(fakeWrapQuery).to.have.been.calledWith(queryStub, [], agent)
119+
done()
120+
})
121+
wrappedFakeMysql.createConnection(fakeConfig)
122+
})
83123
})

lib/instrumentations/pg.js

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,6 @@ var consts = require('../consts')
44
var Shimmer = require('../utils/shimmer')
55
var utils = require('./utils')
66

7-
function tryParseSql (raw) {
8-
if (typeof raw !== 'string') {
9-
return
10-
}
11-
var matches = /\s*(select|update|insert|delete)/i.exec(raw)
12-
return matches ? matches[1] : undefined
13-
}
14-
157
function wrapNative (native, agent, version) {
168
// We support it only versions gte 4.0.0
179
if (!version) {
@@ -38,7 +30,7 @@ function wrapClient (pg, agent) {
3830
protocol: consts.PROTOCOLS.POSTGRES,
3931
url: url,
4032
host: host,
41-
method: tryParseSql(args[0])
33+
method: utils.tryParseSql(args[0])
4234
})
4335
}
4436
})
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
module.exports = {
22
wrapQuery: require('./wrapQuery'),
3-
redisTools: require('./redisTools')
3+
redisTools: require('./redisTools'),
4+
tryParseSql: require('./tryParseSql')
45
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
module.exports = function tryParseSql (raw) {
2+
if (typeof raw !== 'string') {
3+
return
4+
}
5+
var matches = /\s*(select|update|insert|delete)/i.exec(raw)
6+
return matches ? matches[1] : undefined
7+
}

0 commit comments

Comments
 (0)