Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add restify integration #289

Merged
merged 2 commits into from
Sep 21, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions docs/API.md
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,26 @@ query HelloWorld {
|------------------|------------------|----------------------------------------|
| service | redis | The service name for this integration. |

<h3 id="restify">restify</h3>

<h5 id="restify-tags">Tags</h5>

| Tag | Description |
|------------------|-----------------------------------------------------------|
| http.url | The complete URL of the request. |
| http.method | The HTTP method of the request. |
| http.status_code | The HTTP status code of the response. |
| http.headers.* | A recorded HTTP header. |

<h5 id="restify-config">Configuration Options</h5>

| Option | Default | Description |
|------------------|---------------------------|----------------------------------------|
| service | *Service name of the app* | The service name for this integration. |
| validateStatus | `code => code < 500` | Callback function to determine if there was an error. It should take a status code as its only parameter and return `true` for success or `false` for errors. |
| headers | `[]` | An array of headers to include in the span metadata. |


<h2 id="advanced-configuration">Advanced Configuration</h2>

<h3 id="tracer-settings">Tracer settings</h3>
Expand Down
3 changes: 2 additions & 1 deletion src/plugins/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,6 @@ module.exports = {
'mysql': require('./mysql'),
'mysql2': require('./mysql2'),
'pg': require('./pg'),
'redis': require('./redis')
'redis': require('./redis'),
'restify': require('./restify')
}
58 changes: 58 additions & 0 deletions src/plugins/restify.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
'use strict'

const web = require('./util/web')

function createWrapOnRequest (tracer, config) {
config = web.normalizeConfig(config)

return function wrapOnRequest (onRequest) {
return function onRequestWithTrace (req, res) {
web.instrument(tracer, config, req, res, 'restify.request')
web.beforeEnd(req, () => {
const route = req.getRoute()

if (route) {
web.enterRoute(req, route.path)
}
})

return onRequest.apply(this, arguments)
}
}
}

function createWrapAdd (tracer, config) {
return function wrapAdd (add) {
return function addWithTrace (handler) {
return add.call(this, function (req, res, next) {
web.reactivate(req)
handler.apply(this, arguments)
})
}
}
}

module.exports = [
{
name: 'restify',
versions: ['7.x'],
file: 'lib/server.js',
patch (Server, tracer, config) {
this.wrap(Server.prototype, '_onRequest', createWrapOnRequest(tracer, config))
},
unpatch (Server) {
this.unwrap(Server.prototype, '_onRequest')
}
},
{
name: 'restify',
versions: ['7.x'],
file: 'lib/chain.js',
patch (Chain, tracer, config) {
this.wrap(Chain.prototype, 'add', createWrapAdd(tracer, config))
},
unpatch (Chain) {
this.unwrap(Chain.prototype, 'add')
}
}
]
152 changes: 152 additions & 0 deletions test/plugins/restify.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
'use strict'

const axios = require('axios')
const getPort = require('get-port')
const agent = require('./agent')
const plugin = require('../../src/plugins/restify')

wrapIt()

describe('Plugin', () => {
let tracer
let restify
let appListener

describe('restify', () => {
withVersions(plugin, 'restify', version => {
beforeEach(() => {
tracer = require('../..')
})

afterEach(() => {
agent.wipe()
agent.close()
appListener.close()
})

describe('without configuration', () => {
beforeEach(() => {
return agent.load(plugin, 'restify')
.then(() => {
restify = require(`./versions/restify@${version}`).get()
})
})

it('should do automatic instrumentation', done => {
const server = restify.createServer()

getPort().then(port => {
agent
.use(traces => {
expect(traces[0][0]).to.have.property('name', 'restify.request')
expect(traces[0][0]).to.have.property('service', 'test')
expect(traces[0][0]).to.have.property('type', 'http')
expect(traces[0][0]).to.have.property('resource', 'GET')
expect(traces[0][0].meta).to.have.property('span.kind', 'server')
expect(traces[0][0].meta).to.have.property('http.url', `http://localhost:${port}/user`)
expect(traces[0][0].meta).to.have.property('http.method', 'GET')
expect(traces[0][0].meta).to.have.property('http.status_code', '404')
})
.then(done)
.catch(done)

appListener = server.listen(port, 'localhost', () => {
axios
.get(`http://localhost:${port}/user`)
.catch(() => {})
})
})
})

it('should support routing', done => {
const server = restify.createServer()

server.get('/user/:id', (req, res, next) => {
res.send(200)
return next()
})

getPort().then(port => {
agent
.use(traces => {
expect(traces[0][0]).to.have.property('resource', 'GET /user/:id')
expect(traces[0][0].meta).to.have.property('http.url', `http://localhost:${port}/user/123`)
})
.then(done)
.catch(done)

appListener = server.listen(port, 'localhost', () => {
axios
.get(`http://localhost:${port}/user/123`)
.catch(done)
})
})
})

it('should run handlers in the request scope', done => {
if (process.env.DD_CONTEXT_PROPAGATION === 'false') return done()

const server = restify.createServer()

server.pre((req, res, next) => {
expect(tracer.scopeManager().active()).to.not.be.null
next()
})

server.use((req, res, next) => {
expect(tracer.scopeManager().active()).to.not.be.null
next()
})

server.get('/user', (req, res, next) => {
expect(tracer.scopeManager().active()).to.not.be.null
res.send(200)
done()
next()
})

getPort().then(port => {
appListener = server.listen(port, 'localhost', () => {
axios
.get(`http://localhost:${port}/user`)
.catch(done)
})
})
})

it('should reactivate the request span in middleware scopes', done => {
if (process.env.DD_CONTEXT_PROPAGATION === 'false') return done()

const server = restify.createServer()

let span

server.use((req, res, next) => {
span = tracer.scopeManager().active().span()
tracer.scopeManager().activate({})
next()
})

server.use((req, res, next) => {
expect(tracer.scopeManager().active().span()).to.equal(span)
next()
})

server.get('/user', (req, res, next) => {
res.send(200)
done()
next()
})

getPort().then(port => {
appListener = server.listen(port, 'localhost', () => {
axios
.get(`http://localhost:${port}/user`)
.catch(done)
})
})
})
})
})
})
})
4 changes: 4 additions & 0 deletions test/setup.js
Original file line number Diff line number Diff line change
Expand Up @@ -283,13 +283,17 @@ function withVersions (plugin, moduleName, range, cb) {
// skip unsupported version
}

agent.wipe()

try {
const max = require(`./plugins/versions/${moduleName}@${version}`).version()
require(`./plugins/versions/${moduleName}@${version}`).get()
testVersions.set(max, { range: version, test: version })
} catch (e) {
// skip unsupported version
}

agent.wipe()
})
})

Expand Down