dd-trace
Advanced tools
Comparing version 0.6.1-beta.5 to 0.7.0-beta.0
@@ -183,2 +183,3 @@ <h1 id="home">Datadog JavaScript Tracer API</h1> | ||
| depth | -1 | The maximum depth of fields/resolvers to instrument. Set to `0` to only instrument the operation or to -1 to instrument all fields/resolvers. | | ||
| collapse | false | Whether to collapse list items into a single element. (i.e. single `users.*.name` span instead of `users.0.name`, `users.1.name`, etc) | | ||
@@ -185,0 +186,0 @@ <h3 id="hapi">hapi</h3> |
@@ -1,1 +0,1 @@ | ||
module.exports = '0.6.1-beta.5' | ||
module.exports = '0.7.0-beta.0' |
{ | ||
"name": "dd-trace", | ||
"version": "0.6.1-beta.5", | ||
"version": "0.7.0-beta.0", | ||
"description": "Datadog APM tracing client for JavaScript", | ||
@@ -5,0 +5,0 @@ "main": "index.js", |
@@ -100,2 +100,5 @@ 'use strict' | ||
if (resolve._datadog_patched) return resolve | ||
if (config.collapse) { | ||
responsePathAsArray = withCollapse(responsePathAsArray) | ||
} | ||
@@ -106,5 +109,5 @@ function resolveWithTrace (source, args, contextValue, info) { | ||
const depth = path.filter(item => typeof item === 'string').length | ||
const fieldParent = getFieldParent(operation, path) | ||
if (config.depth >= 0 && config.depth < depth) { | ||
const fieldParent = getFieldParent(operation, path) | ||
const scope = tracer.scopeManager().activate(fieldParent) | ||
@@ -118,12 +121,5 @@ | ||
const childOf = createPathSpan(tracer, config, 'field', fieldParent, path) | ||
const field = assertField(tracer, config, operation, path) | ||
const scope = tracer.scopeManager().activate(field.resolveSpan) | ||
operation._datadog_fields[path.join('.')] = { | ||
span: childOf, | ||
parent: fieldParent | ||
} | ||
const span = createPathSpan(tracer, config, 'resolve', childOf, path) | ||
const scope = tracer.scopeManager().activate(span) | ||
return call(resolve, this, arguments, err => finish(scope, operation, path, err)) | ||
@@ -172,2 +168,25 @@ } | ||
function assertField (tracer, config, operation, path) { | ||
let field = getField(operation, path) | ||
if (!field) { | ||
field = operation._datadog_fields[path.join('.')] = { | ||
pending: 0, | ||
error: null | ||
} | ||
const fieldParent = getFieldParent(operation, path) | ||
const childOf = createPathSpan(tracer, config, 'field', fieldParent, path) | ||
const span = createPathSpan(tracer, config, 'resolve', childOf, path) | ||
field.parent = fieldParent | ||
field.span = childOf | ||
field.resolveSpan = span | ||
} | ||
field.pending++ | ||
return field | ||
} | ||
function getFieldParent (operation, path) { | ||
@@ -249,5 +268,9 @@ for (let i = path.length - 1; i > 0; i--) { | ||
'service.name': getService(tracer, config), | ||
'resource.name': [type, name].filter(val => val).join(' '), | ||
'graphql.document': source | ||
'resource.name': [type, name].filter(val => val).join(' ') | ||
} | ||
if (source) { | ||
tags['graphql.document'] = source | ||
} | ||
if (variableValues && config.variables) { | ||
@@ -259,2 +282,3 @@ const variables = config.variables(variableValues) | ||
} | ||
const span = tracer.startSpan(`graphql.${operation.operation}`, { | ||
@@ -291,2 +315,10 @@ tags, | ||
function finish (scope, operation, path, error) { | ||
const field = getField(operation, path) | ||
field.pending-- | ||
if (field.error || field.pending > 0) return | ||
field.error = error | ||
const span = scope.span() | ||
@@ -323,2 +355,9 @@ | ||
function withCollapse (responsePathAsArray) { | ||
return function () { | ||
return responsePathAsArray.apply(this, arguments) | ||
.map(segment => typeof segment === 'number' ? '*' : segment) | ||
} | ||
} | ||
function getField (operation, path) { | ||
@@ -325,0 +364,0 @@ return operation._datadog_fields[path.join('.')] |
@@ -8,22 +8,4 @@ 'use strict' | ||
return function internalSendCommandWithTrace (options) { | ||
const scope = tracer.scopeManager().active() | ||
const span = tracer.startSpan('redis.command', { | ||
childOf: scope && scope.span(), | ||
tags: { | ||
[Tags.SPAN_KIND]: Tags.SPAN_KIND_RPC_CLIENT, | ||
[Tags.DB_TYPE]: 'redis', | ||
'service.name': config.service || `${tracer._service}-redis`, | ||
'resource.name': options.command, | ||
'span.type': 'redis', | ||
'db.name': this.selected_db || '0' | ||
} | ||
}) | ||
const span = startSpan(tracer, config, this, options.command) | ||
if (this.connection_options) { | ||
span.addTags({ | ||
'out.host': String(this.connection_options.host), | ||
'out.port': String(this.connection_options.port) | ||
}) | ||
} | ||
options.callback = wrapCallback(tracer, span, options.callback) | ||
@@ -36,2 +18,49 @@ | ||
function createWrapSendCommand (tracer, config) { | ||
return function wrapSendCommand (sendCommand) { | ||
return function sendCommandWithTrace (command, args, callback) { | ||
const span = startSpan(tracer, config, this, command) | ||
if (callback) { | ||
callback = wrapCallback(tracer, span, callback) | ||
} else if (args) { | ||
args[(args.length || 1) - 1] = wrapCallback(tracer, span, args[args.length - 1]) | ||
} else { | ||
args = [wrapCallback(tracer, span)] | ||
} | ||
return sendCommand.call(this, command, args, callback) | ||
} | ||
} | ||
} | ||
function startSpan (tracer, config, client, command) { | ||
const scope = tracer.scopeManager().active() | ||
const span = tracer.startSpan('redis.command', { | ||
childOf: scope && scope.span(), | ||
tags: { | ||
[Tags.SPAN_KIND]: Tags.SPAN_KIND_RPC_CLIENT, | ||
[Tags.DB_TYPE]: 'redis', | ||
'service.name': config.service || `${tracer._service}-redis`, | ||
'resource.name': command, | ||
'span.type': 'redis', | ||
'db.name': client.selected_db || '0' | ||
} | ||
}) | ||
const connectionOptions = client.connection_options || client.connection_option || { | ||
host: client.options.host || '127.0.0.1', | ||
port: client.options.port || 6379 | ||
} | ||
if (connectionOptions) { | ||
span.addTags({ | ||
'out.host': String(connectionOptions.host), | ||
'out.port': String(connectionOptions.port) | ||
}) | ||
} | ||
return span | ||
} | ||
function wrapCallback (tracer, span, done) { | ||
@@ -49,3 +78,3 @@ return (err, res) => { | ||
if (done) { | ||
if (typeof done === 'function') { | ||
done(err, res) | ||
@@ -56,15 +85,23 @@ } | ||
function patch (redis, tracer, config) { | ||
this.wrap(redis.RedisClient.prototype, 'internal_send_command', createWrapInternalSendCommand(tracer, config)) | ||
} | ||
function unpatch (redis) { | ||
this.unwrap(redis.RedisClient.prototype, 'internal_send_command') | ||
} | ||
module.exports = { | ||
name: 'redis', | ||
versions: ['^2.6'], | ||
patch, | ||
unpatch | ||
} | ||
module.exports = [ | ||
{ | ||
name: 'redis', | ||
versions: ['^2.6'], | ||
patch (redis, tracer, config) { | ||
this.wrap(redis.RedisClient.prototype, 'internal_send_command', createWrapInternalSendCommand(tracer, config)) | ||
}, | ||
unpatch (redis) { | ||
this.unwrap(redis.RedisClient.prototype, 'internal_send_command') | ||
} | ||
}, | ||
{ | ||
name: 'redis', | ||
versions: ['>=0.12 <2.6'], | ||
patch (redis, tracer, config) { | ||
this.wrap(redis.RedisClient.prototype, 'send_command', createWrapSendCommand(tracer, config)) | ||
}, | ||
unpatch (redis) { | ||
this.unwrap(redis.RedisClient.prototype, 'send_command') | ||
} | ||
} | ||
] |
@@ -959,2 +959,58 @@ 'use strict' | ||
describe('with collapsing enabled', () => { | ||
before(() => { | ||
tracer = require('../..') | ||
return agent.load(plugin, 'graphql', { collapse: true }) | ||
}) | ||
after(() => { | ||
return agent.close() | ||
}) | ||
beforeEach(() => { | ||
graphql = require(`../../versions/graphql@${version}`).get() | ||
buildSchema() | ||
}) | ||
it('should collapse list field resolvers', done => { | ||
const source = `{ friends { name } }` | ||
agent | ||
.use(traces => { | ||
const spans = sort(traces[0]) | ||
expect(spans).to.have.length(8) | ||
const execute = spans[3] | ||
const friendsField = spans[4] | ||
const friendsResolve = spans[5] | ||
const friendNameField = spans[6] | ||
const friendNameResolve = spans[7] | ||
expect(execute).to.have.property('name', 'graphql.execute') | ||
expect(friendsField).to.have.property('name', 'graphql.field') | ||
expect(friendsField).to.have.property('resource', 'friends') | ||
expect(friendsField.parent_id.toString()).to.equal(execute.span_id.toString()) | ||
expect(friendsResolve).to.have.property('name', 'graphql.resolve') | ||
expect(friendsResolve).to.have.property('resource', 'friends') | ||
expect(friendsResolve.parent_id.toString()).to.equal(friendsField.span_id.toString()) | ||
expect(friendNameField).to.have.property('name', 'graphql.field') | ||
expect(friendNameField).to.have.property('resource', 'friends.*.name') | ||
expect(friendNameField.parent_id.toString()).to.equal(friendsField.span_id.toString()) | ||
expect(friendNameResolve).to.have.property('name', 'graphql.resolve') | ||
expect(friendNameResolve).to.have.property('resource', 'friends.*.name') | ||
expect(friendNameResolve.parent_id.toString()).to.equal(friendNameField.span_id.toString()) | ||
}) | ||
.then(done) | ||
.catch(done) | ||
graphql.graphql(schema, source).catch(done) | ||
}) | ||
}) | ||
withVersions(plugin, 'apollo-server-core', apolloVersion => { | ||
@@ -994,2 +1050,3 @@ let runQuery | ||
expect(spans[0]).to.have.property('resource', 'query MyQuery') | ||
expect(spans[0].meta).to.have.property('graphql.document') | ||
@@ -1010,2 +1067,3 @@ expect(spans[1]).to.have.property('name', 'graphql.parse') | ||
expect(spans[6]).to.have.property('resource', 'query MyQuery') | ||
expect(spans[6].meta).to.not.have.property('graphql.document') | ||
@@ -1012,0 +1070,0 @@ expect(spans[7]).to.have.property('name', 'graphql.validate') |
@@ -40,3 +40,2 @@ 'use strict' | ||
agent.use(() => client.get('foo')) // wait for initial info command | ||
agent | ||
@@ -56,2 +55,4 @@ .use(traces => { | ||
.catch(done) | ||
client.get('foo', () => {}) | ||
}) | ||
@@ -58,0 +59,0 @@ |
2437903
12481
99