Comparing version 0.3.1 to 0.3.2
'use strict' | ||
const Benchmark = require('benchmark') | ||
const Buffer = require('safe-buffer').Buffer | ||
const EventEmitter = require('events') | ||
const proxyquire = require('proxyquire') | ||
const semver = require('semver') | ||
const Uint64BE = require('int64-buffer').Uint64BE | ||
const platform = require('../src/platform') | ||
const node = require('../src/platform/node') | ||
const cls = require('../src/platform/node/context/cls') | ||
const execSync = require('child_process').execSync | ||
const exec = cmd => execSync(cmd, { stdio: [0, 1, 2] }) | ||
platform.use(node) | ||
const Config = require('../src/config') | ||
const DatadogTracer = require('../src/tracer') | ||
const DatadogSpanContext = require('../src/opentracing/span_context') | ||
const TextMapPropagator = require('../src/opentracing/propagation/text_map') | ||
const Writer = proxyquire('../src/writer', { | ||
'./platform': { request: () => Promise.resolve() } | ||
}) | ||
const Sampler = require('../src/sampler') | ||
const format = require('../src/format') | ||
const encode = require('../src/encode') | ||
const config = new Config({ service: 'benchmark' }) | ||
Benchmark.options.maxTime = 0 | ||
Benchmark.options.minSamples = 5 | ||
const suite = new Benchmark.Suite() | ||
let tracer | ||
let spanContext | ||
let propagator | ||
let carrier | ||
let writer | ||
let sampler | ||
let emitter | ||
let queue | ||
let data | ||
const traceStub = require('./stubs/trace') | ||
const spanStub = require('./stubs/span') | ||
suite | ||
.add('DatadogTracer#trace', { | ||
onStart () { | ||
tracer = new DatadogTracer(config) | ||
}, | ||
fn () { | ||
tracer.trace('bench', () => {}) | ||
} | ||
}) | ||
.add('DatadogTracer#startSpan', { | ||
onStart () { | ||
tracer = new DatadogTracer(config) | ||
}, | ||
fn () { | ||
tracer.startSpan() | ||
} | ||
}) | ||
.add('TextMapPropagator#inject', { | ||
onStart () { | ||
propagator = new TextMapPropagator() | ||
carrier = {} | ||
spanContext = new DatadogSpanContext({ | ||
traceId: new Uint64BE(0x12345678, 0x12345678), | ||
spanId: new Uint64BE(0x12345678, 0x12345678), | ||
baggageItems: { foo: 'bar' } | ||
}) | ||
}, | ||
fn () { | ||
propagator.inject(spanContext, carrier) | ||
} | ||
}) | ||
.add('TextMapPropagator#extract', { | ||
onStart () { | ||
propagator = new TextMapPropagator() | ||
carrier = { | ||
'x-datadog-trace-id': '1234567891234567', | ||
'x-datadog-parent-id': '1234567891234567', | ||
'ot-baggage-foo': 'bar' | ||
} | ||
}, | ||
fn () { | ||
propagator.extract(carrier) | ||
} | ||
}) | ||
.add('Writer#append', { | ||
onStart () { | ||
writer = new Writer({}, 1000000) | ||
}, | ||
fn () { | ||
writer.append(spanStub) | ||
} | ||
}) | ||
.add('Writer#flush (1000 items)', { | ||
onStart () { | ||
writer = new Writer({}, 1001) | ||
for (let i = 0; i < 1000; i++) { | ||
writer.append(spanStub) | ||
} | ||
queue = writer._queue | ||
}, | ||
fn () { | ||
writer._queue = queue | ||
writer.flush() | ||
} | ||
}) | ||
.add('Sampler#isSampled', { | ||
onStart () { | ||
sampler = new Sampler(0.5) | ||
}, | ||
fn () { | ||
sampler.isSampled() | ||
} | ||
}) | ||
.add('format', { | ||
fn () { | ||
format(spanStub) | ||
} | ||
}) | ||
.add('encode', { | ||
fn () { | ||
encode(traceStub) | ||
} | ||
}) | ||
.add('platform#id (Node)', { | ||
fn () { | ||
platform.id() | ||
} | ||
}) | ||
.add('platform#now (Node)', { | ||
fn () { | ||
platform.now() | ||
} | ||
}) | ||
.add('platform#request (Node)', { | ||
onStart () { | ||
data = Buffer.alloc(1000000) | ||
}, | ||
fn () { | ||
platform | ||
.request({ | ||
protocol: 'http:', | ||
hostname: 'test', | ||
port: '8080', | ||
path: '/v0.3/traces', | ||
method: 'PUT', | ||
headers: { | ||
'Content-Type': 'application/msgpack' | ||
}, | ||
data | ||
}) | ||
.catch(() => {}) | ||
} | ||
}) | ||
.add('cls#run (Node)', { | ||
fn () { | ||
cls.run(() => {}) | ||
} | ||
}) | ||
.add('cls#bind (Node)', { | ||
fn () { | ||
cls.bind(() => {}) | ||
} | ||
}) | ||
.add('cls#bindEmitter (Node)', { | ||
onStart () { | ||
emitter = new EventEmitter() | ||
}, | ||
fn () { | ||
cls.bindEmitter(emitter) | ||
} | ||
}) | ||
.add('msgpack#prefix (Node)', { | ||
fn () { | ||
platform.msgpack.prefix(traceStub) | ||
} | ||
}) | ||
if (semver.gte(semver.valid(process.version), '8.2.0')) { | ||
const cls = require('../src/platform/node/context/cls_hooked') | ||
suite | ||
.add('clsHooked#run (Node)', { | ||
fn () { | ||
cls.run(() => {}) | ||
} | ||
}) | ||
.add('clsHooked#bind (Node)', { | ||
fn () { | ||
cls.bind(() => {}) | ||
} | ||
}) | ||
.add('clsHooked#bindEmitter (Node)', { | ||
onStart () { | ||
emitter = new EventEmitter() | ||
}, | ||
fn () { | ||
cls.bindEmitter(emitter) | ||
} | ||
}) | ||
} | ||
suite | ||
.on('cycle', event => { | ||
console.log(String(event.target)) // eslint-disable-line no-console | ||
}) | ||
.on('error', event => { | ||
console.log(String(event.target.error)) // eslint-disable-line no-console | ||
}) | ||
.run({ 'async': true }) | ||
exec('node benchmark/core') | ||
exec('node benchmark/platform/node') | ||
exec('node benchmark/dd-trace') |
@@ -178,2 +178,3 @@ <h1 id="home">Datadog JavaScript Tracer API</h1> | ||
| 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. | | ||
@@ -180,0 +181,0 @@ <h3 id="graphql">graphql</h3> |
@@ -1,1 +0,1 @@ | ||
module.exports = '0.3.1' | ||
module.exports = '0.3.2' |
{ | ||
"name": "dd-trace", | ||
"version": "0.3.1", | ||
"version": "0.3.2", | ||
"description": "Datadog APM tracing client for JavaScript (experimental)", | ||
@@ -5,0 +5,0 @@ "main": "index.js", |
@@ -97,11 +97,2 @@ 'use strict' | ||
.forEach(instrumentation => { | ||
const modulePath = [moduleBaseDir, instrumentation.file].filter(val => val).join('/') | ||
if (require.cache[modulePath]) { | ||
log.debug([ | ||
`Instrumented module "${moduleName}" was imported before calling tracer.init().`, | ||
`Please make sure to initialize the tracer before importing any instrumented module.` | ||
].join(' ')) | ||
} | ||
this._instrumented.set(instrumentation, moduleExports) | ||
@@ -108,0 +99,0 @@ instrumentation.patch.call(this, moduleExports, this._tracer._tracer, this._plugins.get(plugin).config) |
@@ -14,3 +14,5 @@ 'use strict' | ||
bind (callback) {} | ||
bind (callback) { | ||
return callback | ||
} | ||
@@ -17,0 +19,0 @@ bindEmitter (emitter) {} |
@@ -83,3 +83,3 @@ 'use strict' | ||
_finish (finishTime) { | ||
finishTime = parseInt(finishTime, 10) || platform.now() | ||
finishTime = parseFloat(finishTime) || platform.now() | ||
@@ -86,0 +86,0 @@ this._duration = finishTime - this._startTime |
@@ -28,3 +28,3 @@ 'use strict' | ||
this._recorder.init() | ||
this._sampler = new Sampler(1) | ||
this._sampler = new Sampler(config.sampleRate) | ||
this._propagators = { | ||
@@ -31,0 +31,0 @@ [opentracing.FORMAT_TEXT_MAP]: new TextMapPropagator(), |
@@ -12,2 +12,6 @@ 'use strict' | ||
function createWrapMethod (tracer, config) { | ||
const validateStatus = typeof config.validateStatus === 'function' | ||
? config.validateStatus | ||
: code => code < 500 | ||
function middleware (req, res, next) { | ||
@@ -33,2 +37,4 @@ const url = `${req.protocol}://${req.get('host')}${req.originalUrl}` | ||
span.setTag('resource.name', `${req.method} ${paths.join('')}`) | ||
} else { | ||
span.setTag('resource.name', req.method) | ||
} | ||
@@ -40,2 +46,6 @@ | ||
if (!validateStatus(res.statusCode)) { | ||
span.setTag(Tags.ERROR, true) | ||
} | ||
span.finish() | ||
@@ -42,0 +52,0 @@ |
@@ -5,3 +5,3 @@ 'use strict' | ||
function createWrapExecute (tracer, config, defaultFieldResolver) { | ||
function createWrapExecute (tracer, config, defaultFieldResolver, responsePathAsArray) { | ||
return function wrapExecute (execute) { | ||
@@ -14,22 +14,28 @@ return function executeWithTrace () { | ||
const fieldResolver = args.fieldResolver || defaultFieldResolver | ||
const operation = getOperation(document) | ||
if (!schema || !document || typeof fieldResolver !== 'function') { | ||
if (!schema || !operation || typeof fieldResolver !== 'function') { | ||
return execute.apply(this, arguments) | ||
} | ||
args.fieldResolver = wrapResolve(fieldResolver, tracer, config) | ||
args.fieldResolver = wrapFieldResolver(fieldResolver, tracer, config, responsePathAsArray) | ||
args.contextValue = contextValue | ||
Object.defineProperties(contextValue, { | ||
_datadog_operation: { value: {} }, | ||
_datadog_fields: { value: {} }, | ||
_datadog_source: { value: document._datadog_source } | ||
}) | ||
if (!schema._datadog_patched) { | ||
wrapFields(schema._queryType, tracer, config, responsePathAsArray) | ||
wrapFields(schema._mutationType, tracer, config, responsePathAsArray) | ||
if (!schema._datadog_patched) { | ||
wrapFields(schema._queryType._fields, tracer, config, []) | ||
schema._datadog_patched = true | ||
} | ||
return call(execute, this, [args], defer(tracer), () => finishOperation(contextValue)) | ||
Object.defineProperties(contextValue, { | ||
_datadog_operation: { | ||
value: { | ||
span: createOperationSpan(tracer, config, operation, document._datadog_source) | ||
} | ||
}, | ||
_datadog_fields: { value: {} } | ||
}) | ||
return call(execute, this, [args], defer(tracer), err => finishOperation(contextValue, err)) | ||
} | ||
@@ -45,3 +51,3 @@ } | ||
Object.defineProperties(document, { | ||
_datadog_source: { value: source } | ||
_datadog_source: { value: source.body || source } | ||
}) | ||
@@ -54,12 +60,22 @@ | ||
function wrapFields (fields, tracer, config) { | ||
Object.keys(fields).forEach(key => { | ||
const field = fields[key] | ||
function wrapFields (type, tracer, config, responsePathAsArray) { | ||
if (!type || type._datadog_patched) { | ||
return | ||
} | ||
if (typeof field.resolve === 'function') { | ||
field.resolve = wrapResolve(field.resolve, tracer, config) | ||
type._datadog_patched = true | ||
Object.keys(type._fields).forEach(key => { | ||
const field = type._fields[key] | ||
if (typeof field.resolve === 'function' && !field.resolve._datadog_patched) { | ||
field.resolve = wrapResolve(field.resolve, tracer, config, responsePathAsArray) | ||
} | ||
if (field.type && field.type._fields) { | ||
wrapFields(field.type._fields, tracer, config) | ||
if (field.type) { | ||
if (field.type._fields) { | ||
wrapFields(field.type, tracer, config, responsePathAsArray) | ||
} else if (field.type.ofType && field.type.ofType._fields) { | ||
wrapFields(field.type.ofType, tracer, config, responsePathAsArray) | ||
} | ||
} | ||
@@ -69,6 +85,11 @@ }) | ||
function wrapResolve (resolve, tracer, config) { | ||
return function resolveWithTrace (source, args, contextValue, info) { | ||
const path = getPath(info.path) | ||
const fieldParent = getFieldParent(tracer, config, contextValue, info, path) | ||
function wrapResolve (resolve, tracer, config, responsePathAsArray) { | ||
function resolveWithTrace (source, args, contextValue, info) { | ||
if (!contextValue || !contextValue._datadog_fields) { | ||
return resolve.apply(arguments) | ||
} | ||
const path = responsePathAsArray(info.path) | ||
const fieldParent = getFieldParent(contextValue, path) | ||
const childOf = createSpan('graphql.field', tracer, config, fieldParent, path) | ||
@@ -79,3 +100,3 @@ const deferred = defer(tracer) | ||
contextValue._datadog_fields[path] = { | ||
contextValue._datadog_fields[path.join('.')] = { | ||
span: childOf, | ||
@@ -93,4 +114,18 @@ parent: fieldParent | ||
} | ||
resolveWithTrace._datadog_patched = true | ||
return resolveWithTrace | ||
} | ||
function wrapFieldResolver (fieldResolver, tracer, config, responsePathAsArray) { | ||
return function fieldResolverWithTrace (source, args, contextValue, info) { | ||
if (source && typeof source[info.fieldName] === 'function') { | ||
return wrapResolve(fieldResolver, tracer, config, responsePathAsArray).apply(this, arguments) | ||
} | ||
return fieldResolver.apply(this, arguments) | ||
} | ||
} | ||
function call (fn, thisContext, args, deferred, callback) { | ||
@@ -134,12 +169,12 @@ try { | ||
function getFieldParent (tracer, config, contextValue, info, path) { | ||
if (!contextValue._datadog_operation.span) { | ||
contextValue._datadog_operation.span = createOperationSpan(tracer, config, contextValue, info) | ||
} | ||
function getFieldParent (contextValue, path) { | ||
for (let i = path.length - 1; i > 0; i--) { | ||
const field = getField(contextValue, path.slice(0, i)) | ||
if (path.length === 1) { | ||
return contextValue._datadog_operation.span | ||
if (field) { | ||
return field.span | ||
} | ||
} | ||
return contextValue._datadog_fields[path.slice(0, -1).join('.')].span | ||
return contextValue._datadog_operation.span | ||
} | ||
@@ -149,3 +184,3 @@ | ||
if (args.length === 1) { | ||
return args | ||
return args[0] | ||
} | ||
@@ -164,9 +199,9 @@ | ||
function createOperationSpan (tracer, config, contextValue, info) { | ||
const type = info.operation.operation | ||
const name = info.operation.name && info.operation.name.value | ||
function createOperationSpan (tracer, config, operation, source) { | ||
const type = operation.operation | ||
const name = operation.name && operation.name.value | ||
let span | ||
tracer.trace(`graphql.${info.operation.operation}`, parent => { | ||
tracer.trace(`graphql.${operation.operation}`, parent => { | ||
span = parent | ||
@@ -176,4 +211,3 @@ span.addTags({ | ||
'resource.name': [type, name].filter(val => val).join(' '), | ||
'span.type': 'custom', | ||
'graphql.document': contextValue._datadog_source | ||
'graphql.document': source | ||
}) | ||
@@ -199,4 +233,3 @@ }) | ||
'service.name': getService(tracer, config), | ||
'resource.name': path.join('.'), | ||
'span.type': 'custom' | ||
'resource.name': path.join('.') | ||
}) | ||
@@ -210,8 +243,12 @@ } | ||
for (let i = path.length - 2; i >= 0; i--) { | ||
contextValue._datadog_fields[path[i]].finishTime = platform.now() | ||
for (let i = path.length; i > 0; i--) { | ||
const field = getField(contextValue, path.slice(0, i)) | ||
if (field) { | ||
field.finishTime = platform.now() | ||
} | ||
} | ||
} | ||
function finishOperation (contextValue) { | ||
function finishOperation (contextValue, error) { | ||
for (const key in contextValue._datadog_fields) { | ||
@@ -221,5 +258,11 @@ contextValue._datadog_fields[key].span.finish(contextValue._datadog_fields[key].finishTime) | ||
addError(contextValue._datadog_operation.span, error) | ||
contextValue._datadog_operation.span.finish() | ||
} | ||
function getField (contextValue, path) { | ||
return contextValue._datadog_fields[path.join('.')] | ||
} | ||
function getService (tracer, config) { | ||
@@ -229,8 +272,11 @@ return config.service || `${tracer._service}-graphql` | ||
function getPath (path) { | ||
if (path.prev) { | ||
return getPath(path.prev).concat(path.key) | ||
} else { | ||
return [path.key] | ||
function getOperation (document) { | ||
if (!document || !Array.isArray(document.definitions)) { | ||
return | ||
} | ||
const types = ['query', 'mutation'] | ||
const definition = document.definitions.find(def => types.indexOf(def.operation) !== -1) | ||
return definition | ||
} | ||
@@ -256,3 +302,8 @@ | ||
patch (execute, tracer, config) { | ||
this.wrap(execute, 'execute', createWrapExecute(tracer, config, execute.defaultFieldResolver)) | ||
this.wrap(execute, 'execute', createWrapExecute( | ||
tracer, | ||
config, | ||
execute.defaultFieldResolver, | ||
execute.responsePathAsArray | ||
)) | ||
}, | ||
@@ -259,0 +310,0 @@ unpatch (execute) { |
@@ -25,3 +25,3 @@ 'use strict' | ||
span.setTag('out.port', String(this.config.port)) | ||
span.setTag('span.type', 'db') | ||
span.setTag('span.type', 'sql') | ||
span.setTag('db.user', this.config.user) | ||
@@ -48,2 +48,14 @@ | ||
function createWrapEnqueue (tracer, config) { | ||
return function wrapGetEnqueue (enqueue) { | ||
return function enqueueWithTrace (sequence) { | ||
if (sequence._callback) { | ||
sequence._callback = tracer.bind(sequence._callback) | ||
} | ||
return enqueue.apply(this, arguments) | ||
} | ||
} | ||
} | ||
function wrapCallback (tracer, span, done) { | ||
@@ -73,2 +85,10 @@ return tracer.bind((err, res) => { | ||
function patchProtocol (Protocol, tracer, config) { | ||
this.wrap(Protocol.prototype, '_enqueue', createWrapEnqueue(tracer, config)) | ||
} | ||
function unpatchProtocol (Protocol) { | ||
this.unwrap(Protocol.prototype, '_enqueue') | ||
} | ||
module.exports = [ | ||
@@ -81,3 +101,10 @@ { | ||
unpatch: unpatchConnection | ||
}, | ||
{ | ||
name: 'mysql', | ||
file: 'lib/protocol/Protocol.js', | ||
versions: ['2.x'], | ||
patch: patchProtocol, | ||
unpatch: unpatchProtocol | ||
} | ||
] |
@@ -25,3 +25,3 @@ 'use strict' | ||
span.setTag('out.port', String(this.config.port)) | ||
span.setTag('span.type', 'db') | ||
span.setTag('span.type', 'sql') | ||
span.setTag('db.user', this.config.user) | ||
@@ -28,0 +28,0 @@ |
@@ -24,3 +24,3 @@ 'use strict' | ||
span.setTag('resource.name', statement) | ||
span.setTag('span.type', 'db') | ||
span.setTag('span.type', 'sql') | ||
@@ -27,0 +27,0 @@ if (params) { |
@@ -33,2 +33,8 @@ 'use strict' | ||
}) | ||
it('should return the callback', () => { | ||
const callback = () => {} | ||
expect(tracer.bind(callback)).to.equal(callback) | ||
}) | ||
}) | ||
@@ -35,0 +41,0 @@ |
@@ -13,2 +13,4 @@ 'use strict' | ||
let recorder | ||
let Sampler | ||
let sampler | ||
let SpanContext | ||
@@ -39,2 +41,7 @@ let spanContext | ||
sampler = { | ||
isSampled: sinon.stub().returns(true) | ||
} | ||
Sampler = sinon.stub().returns(sampler) | ||
spanContext = {} | ||
@@ -56,2 +63,3 @@ carrier = {} | ||
bufferSize: 1000, | ||
sampleRate: 0.5, | ||
logger: 'logger', | ||
@@ -71,2 +79,3 @@ tags: {}, | ||
'../recorder': Recorder, | ||
'../sampler': Sampler, | ||
'./propagation/text_map': TextMapPropagator, | ||
@@ -88,2 +97,8 @@ './propagation/http': HttpPropagator, | ||
it('should support sampling', () => { | ||
tracer = new Tracer(config) | ||
expect(Sampler).to.have.been.calledWith(config.sampleRate) | ||
}) | ||
it('should support logging', () => { | ||
@@ -90,0 +105,0 @@ tracer = new Tracer(config) |
@@ -232,3 +232,3 @@ 'use strict' | ||
it('should fallback to the default resource name if a path pattern could not be found', done => { | ||
it('should fallback to the the verb if a path pattern could not be found', done => { | ||
const app = express() | ||
@@ -241,3 +241,3 @@ | ||
.use(traces => { | ||
expect(traces[0][0]).to.have.property('resource', 'express.request') | ||
expect(traces[0][0]).to.have.property('resource', 'GET') | ||
}) | ||
@@ -427,2 +427,32 @@ .then(done) | ||
}) | ||
it('should handle errors', done => { | ||
const app = express() | ||
app.use((req, res, next) => { | ||
next() | ||
}) | ||
app.get('/user', (req, res) => { | ||
res.status(500).send() | ||
}) | ||
getPort().then(port => { | ||
agent.use(traces => { | ||
expect(traces[0][0]).to.have.property('error', 1) | ||
expect(traces[0][0]).to.have.property('resource', 'GET /user') | ||
expect(traces[0][0].meta).to.have.property('http.status_code', '500') | ||
done() | ||
}) | ||
appListener = app.listen(port, 'localhost', () => { | ||
axios | ||
.get(`http://localhost:${port}/user`, { | ||
validateStatus: status => status === 500 | ||
}) | ||
.catch(done) | ||
}) | ||
}) | ||
}) | ||
}) | ||
@@ -435,3 +465,4 @@ | ||
config = { | ||
service: 'custom' | ||
service: 'custom', | ||
validateStatus: code => code < 400 | ||
} | ||
@@ -445,3 +476,3 @@ | ||
it('should be configured with the correct values', done => { | ||
it('should be configured with the correct service name', done => { | ||
const app = express() | ||
@@ -468,4 +499,29 @@ | ||
}) | ||
it('should be configured with the correct status code validator', done => { | ||
const app = express() | ||
app.get('/user', (req, res) => { | ||
res.status(400).send() | ||
}) | ||
getPort().then(port => { | ||
agent | ||
.use(traces => { | ||
expect(traces[0][0]).to.have.property('error', 1) | ||
}) | ||
.then(done) | ||
.catch(done) | ||
appListener = app.listen(port, 'localhost', () => { | ||
axios | ||
.get(`http://localhost:${port}/user`, { | ||
validateStatus: status => status === 400 | ||
}) | ||
.catch(done) | ||
}) | ||
}) | ||
}) | ||
}) | ||
}) | ||
}) |
@@ -15,2 +15,68 @@ 'use strict' | ||
function buildSchema () { | ||
const Human = new graphql.GraphQLObjectType({ | ||
name: 'Human', | ||
fields: { | ||
name: { | ||
type: graphql.GraphQLString, | ||
resolve (obj, args) { | ||
return 'test' | ||
} | ||
}, | ||
address: { | ||
type: new graphql.GraphQLObjectType({ | ||
name: 'Address', | ||
fields: { | ||
civicNumber: { | ||
type: graphql.GraphQLString, | ||
resolve: () => 123 | ||
}, | ||
street: { | ||
type: graphql.GraphQLString, | ||
resolve: () => 'foo street' | ||
} | ||
} | ||
}), | ||
resolve (obj, args) { | ||
return {} | ||
} | ||
}, | ||
pets: { | ||
type: new graphql.GraphQLList(new graphql.GraphQLObjectType({ | ||
name: 'Pet', | ||
fields: () => ({ | ||
type: { | ||
type: graphql.GraphQLString, | ||
resolve: () => 'dog' | ||
}, | ||
name: { | ||
type: graphql.GraphQLString, | ||
resolve: () => 'foo bar' | ||
}, | ||
owner: { | ||
type: Human, | ||
resolve: () => ({}) | ||
}, | ||
colours: { | ||
type: new graphql.GraphQLList(new graphql.GraphQLObjectType({ | ||
name: 'Colour', | ||
fields: { | ||
code: { | ||
type: graphql.GraphQLString, | ||
resolve: () => '#ffffff' | ||
} | ||
} | ||
})), | ||
resolve (obj, args) { | ||
return [{}, {}] | ||
} | ||
} | ||
}) | ||
})), | ||
resolve (obj, args) { | ||
return [{}, {}, {}] | ||
} | ||
} | ||
} | ||
}) | ||
schema = new graphql.GraphQLSchema({ | ||
@@ -32,18 +98,38 @@ query: new graphql.GraphQLObjectType({ | ||
human: { | ||
type: new graphql.GraphQLObjectType({ | ||
name: 'Human', | ||
fields: { | ||
name: { | ||
type: graphql.GraphQLString, | ||
resolve (obj, args) { | ||
return obj | ||
} | ||
} | ||
} | ||
}), | ||
type: Human, | ||
resolve (obj, args) { | ||
return Promise.resolve('test') | ||
return Promise.resolve({}) | ||
} | ||
}, | ||
friends: { | ||
type: new graphql.GraphQLList(Human), | ||
resolve () { | ||
return [ { name: 'alice' }, { name: 'bob' } ] | ||
} | ||
} | ||
} | ||
}), | ||
mutation: new graphql.GraphQLObjectType({ | ||
name: 'RootMutationType', | ||
fields: { | ||
human: { | ||
type: Human, | ||
resolve () { | ||
return Promise.resolve({ name: 'human name' }) | ||
} | ||
} | ||
} | ||
}), | ||
subscription: new graphql.GraphQLObjectType({ | ||
name: 'RootSubscriptionType', | ||
fields: { | ||
human: { | ||
type: Human, | ||
resolve () { | ||
return Promise.resolve({ name: 'human name' }) | ||
} | ||
} | ||
} | ||
}) | ||
@@ -130,3 +216,13 @@ }) | ||
it('should instrument nested field resolvers', done => { | ||
const source = `{ human { name } }` | ||
const source = ` | ||
{ | ||
human { | ||
name | ||
address { | ||
civicNumber | ||
street | ||
} | ||
} | ||
} | ||
` | ||
@@ -137,3 +233,3 @@ agent | ||
expect(spans).to.have.length(5) | ||
expect(spans).to.have.length(11) | ||
@@ -145,2 +241,8 @@ const query = spans[0] | ||
const humanNameResolve = spans[4] | ||
const addressField = spans[5] | ||
const addressResolve = spans[6] | ||
const addressCivicNumberField = spans[7] | ||
const addressCivicNumberResolve = spans[8] | ||
const addressStreetField = spans[9] | ||
const addressStreetResolve = spans[10] | ||
@@ -153,2 +255,3 @@ expect(query).to.have.property('name', 'graphql.query') | ||
expect(humanField.parent_id.toString()).to.equal(query.span_id.toString()) | ||
expect(humanField.duration.toNumber()).to.be.lte(query.duration.toNumber()) | ||
@@ -158,2 +261,3 @@ expect(humanResolve).to.have.property('name', 'graphql.resolve') | ||
expect(humanResolve.parent_id.toString()).to.equal(humanField.span_id.toString()) | ||
expect(humanResolve.duration.toNumber()).to.be.lte(humanField.duration.toNumber()) | ||
@@ -167,2 +271,32 @@ expect(humanNameField).to.have.property('name', 'graphql.field') | ||
expect(humanNameResolve.parent_id.toString()).to.equal(humanNameField.span_id.toString()) | ||
expect(addressField).to.have.property('name', 'graphql.field') | ||
expect(addressField).to.have.property('resource', 'human.address') | ||
expect(addressField.parent_id.toString()).to.equal(humanField.span_id.toString()) | ||
expect(addressField.duration.toNumber()).to.be.lte(humanField.duration.toNumber()) | ||
expect(addressResolve).to.have.property('name', 'graphql.resolve') | ||
expect(addressResolve).to.have.property('resource', 'human.address') | ||
expect(addressResolve.parent_id.toString()).to.equal(addressField.span_id.toString()) | ||
expect(addressResolve.duration.toNumber()).to.be.lte(addressField.duration.toNumber()) | ||
expect(addressCivicNumberField).to.have.property('name', 'graphql.field') | ||
expect(addressCivicNumberField).to.have.property('resource', 'human.address.civicNumber') | ||
expect(addressCivicNumberField.parent_id.toString()).to.equal(addressField.span_id.toString()) | ||
expect(addressCivicNumberField.duration.toNumber()).to.be.lte(addressField.duration.toNumber()) | ||
expect(addressCivicNumberResolve).to.have.property('name', 'graphql.resolve') | ||
expect(addressCivicNumberResolve).to.have.property('resource', 'human.address.civicNumber') | ||
expect(addressCivicNumberResolve.parent_id.toString()).to.equal(addressCivicNumberField.span_id.toString()) | ||
expect(addressCivicNumberResolve.duration.toNumber()).to.be.lte(addressCivicNumberField.duration.toNumber()) | ||
expect(addressStreetField).to.have.property('name', 'graphql.field') | ||
expect(addressStreetField).to.have.property('resource', 'human.address.street') | ||
expect(addressStreetField.parent_id.toString()).to.equal(addressField.span_id.toString()) | ||
expect(addressStreetField.duration.toNumber()).to.be.lte(addressField.duration.toNumber()) | ||
expect(addressStreetResolve).to.have.property('name', 'graphql.resolve') | ||
expect(addressStreetResolve).to.have.property('resource', 'human.address.street') | ||
expect(addressStreetResolve.parent_id.toString()).to.equal(addressStreetField.span_id.toString()) | ||
expect(addressStreetResolve.duration.toNumber()).to.be.lte(addressStreetField.duration.toNumber()) | ||
}) | ||
@@ -175,3 +309,81 @@ .then(done) | ||
it('should instrument the default field resolver', done => { | ||
it('should instrument list field resolvers', done => { | ||
const source = `{ friends { name } }` | ||
agent | ||
.use(traces => { | ||
const spans = sort(traces[0]) | ||
expect(spans).to.have.length(7) | ||
const query = spans[0] | ||
const friendsField = spans[1] | ||
const friendsResolve = spans[2] | ||
const friend0NameField = spans[3] | ||
const friend0NameResolve = spans[4] | ||
const friend1NameField = spans[5] | ||
const friend1NameResolve = spans[6] | ||
expect(query).to.have.property('name', 'graphql.query') | ||
expect(query).to.have.property('resource', 'query') | ||
expect(friendsField).to.have.property('name', 'graphql.field') | ||
expect(friendsField).to.have.property('resource', 'friends') | ||
expect(friendsField.parent_id.toString()).to.equal(query.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(friend0NameField).to.have.property('name', 'graphql.field') | ||
expect(friend0NameField).to.have.property('resource', 'friends.0.name') | ||
expect(friend0NameField.parent_id.toString()).to.equal(friendsField.span_id.toString()) | ||
expect(friend0NameResolve).to.have.property('name', 'graphql.resolve') | ||
expect(friend0NameResolve).to.have.property('resource', 'friends.0.name') | ||
expect(friend0NameResolve.parent_id.toString()).to.equal(friend0NameField.span_id.toString()) | ||
expect(friend1NameField).to.have.property('name', 'graphql.field') | ||
expect(friend1NameField).to.have.property('resource', 'friends.1.name') | ||
expect(friend1NameField.parent_id.toString()).to.equal(friendsField.span_id.toString()) | ||
expect(friend1NameResolve).to.have.property('name', 'graphql.resolve') | ||
expect(friend1NameResolve).to.have.property('resource', 'friends.1.name') | ||
expect(friend1NameResolve.parent_id.toString()).to.equal(friend1NameField.span_id.toString()) | ||
}) | ||
.then(done) | ||
.catch(done) | ||
graphql.graphql(schema, source).catch(done) | ||
}) | ||
it('should instrument mutations', done => { | ||
const source = `mutation { human { name } }` | ||
agent | ||
.use(traces => { | ||
const spans = sort(traces[0]) | ||
expect(spans).to.have.length(5) | ||
expect(spans[0]).to.have.property('name', 'graphql.mutation') | ||
}) | ||
.then(done) | ||
.catch(done) | ||
graphql.graphql(schema, source).catch(done) | ||
}) | ||
it('should handle a circular schema', done => { | ||
const source = `{ human { pets { owner { name } } } }` | ||
graphql.graphql(schema, source) | ||
.then((result) => { | ||
expect(result.data.human.pets[0].owner.name).to.equal('test') | ||
done() | ||
}) | ||
.catch(done) | ||
}) | ||
it('should ignore the default field resolver', done => { | ||
const schema = graphql.buildSchema(` | ||
@@ -189,4 +401,4 @@ type Query { | ||
expect(spans).to.have.length(3) | ||
expect(spans[2]).to.have.property('resource', 'hello') | ||
expect(spans).to.have.length(1) | ||
expect(spans[0]).to.have.property('resource', 'query') | ||
}) | ||
@@ -199,3 +411,3 @@ .then(done) | ||
it('should instrument a custom field resolver', done => { | ||
it('should ignore the execution field resolver without a rootValue resolver', done => { | ||
const schema = graphql.buildSchema(` | ||
@@ -219,4 +431,4 @@ type Query { | ||
expect(spans).to.have.length(3) | ||
expect(spans[2]).to.have.property('resource', 'hello') | ||
expect(spans).to.have.length(1) | ||
expect(spans[0]).to.have.property('resource', 'query') | ||
}) | ||
@@ -248,3 +460,3 @@ .then(done) | ||
it('should run the field resolver in the trace context', done => { | ||
it('should run rootValue resolvers in the current context', done => { | ||
const schema = graphql.buildSchema(` | ||
@@ -258,22 +470,2 @@ type Query { | ||
const rootValue = { hello: 'world' } | ||
const fieldResolver = (source, args, contextValue, info) => { | ||
expect(context.get('current')).to.not.be.undefined | ||
done() | ||
return source[info.fieldName] | ||
} | ||
graphql.graphql({ schema, source, rootValue, fieldResolver }).catch(done) | ||
}) | ||
it('should run resolvers in the current context', done => { | ||
const schema = graphql.buildSchema(` | ||
type Query { | ||
hello: String | ||
} | ||
`) | ||
const source = `{ hello }` | ||
const rootValue = { | ||
@@ -311,2 +503,13 @@ hello () { | ||
it('should handle unsupported operations', () => { | ||
const query = `query MyQuery { hello(name: "world") }` | ||
const subscription = `subscription { human { name } }` | ||
return graphql.graphql(schema, query) | ||
.then(() => graphql.graphql(schema, subscription)) | ||
.then(result => { | ||
expect(result).to.not.have.property('errors') | ||
}) | ||
}) | ||
it('should handle calling low level APIs directly', done => { | ||
@@ -329,6 +532,65 @@ const source = `query MyQuery { hello(name: "world") }` | ||
graphql.execute({ schema, document }) | ||
}) | ||
it('should handle Source objects', done => { | ||
const source = `query MyQuery { hello(name: "world") }` | ||
const document = graphql.parse(new graphql.Source(source)) | ||
agent | ||
.use(traces => { | ||
const spans = sort(traces[0]) | ||
expect(spans).to.have.length(3) | ||
expect(spans[0]).to.have.property('service', 'test-graphql') | ||
expect(spans[0]).to.have.property('name', 'graphql.query') | ||
expect(spans[0]).to.have.property('resource', 'query MyQuery') | ||
expect(spans[0].meta).to.have.property('graphql.document', source) | ||
}) | ||
.then(done) | ||
.catch(done) | ||
graphql.execute(schema, document) | ||
}) | ||
it('should handle exceptions', done => { | ||
it('should handle executor exceptions', done => { | ||
schema = new graphql.GraphQLSchema({ | ||
query: new graphql.GraphQLObjectType({ | ||
name: 'RootQueryType', | ||
fields: { | ||
hello: {} | ||
} | ||
}) | ||
}) | ||
const source = `{ hello }` | ||
const document = graphql.parse(source) | ||
let error | ||
agent | ||
.use(traces => { | ||
const spans = sort(traces[0]) | ||
expect(spans).to.have.length(1) | ||
expect(spans[0]).to.have.property('service', 'test-graphql') | ||
expect(spans[0]).to.have.property('name', 'graphql.query') | ||
expect(spans[0]).to.have.property('resource', 'query') | ||
expect(spans[0].meta).to.have.property('graphql.document', source) | ||
expect(spans[0]).to.have.property('error', 1) | ||
expect(spans[0].meta).to.have.property('error.type', error.name) | ||
expect(spans[0].meta).to.have.property('error.msg', error.message) | ||
expect(spans[0].meta).to.have.property('error.stack', error.stack) | ||
}) | ||
.then(done) | ||
.catch(done) | ||
try { | ||
graphql.execute(schema, document) | ||
} catch (e) { | ||
error = e | ||
} | ||
}) | ||
it('should handle resolver exceptions', done => { | ||
const error = new Error('test') | ||
@@ -344,4 +606,6 @@ | ||
const fieldResolver = (source, args, contextValue, info) => { | ||
throw error | ||
const rootValue = { | ||
hello: () => { | ||
throw error | ||
} | ||
} | ||
@@ -362,3 +626,3 @@ | ||
graphql.graphql({ schema, source, fieldResolver }).catch(done) | ||
graphql.graphql({ schema, source, rootValue }).catch(done) | ||
}) | ||
@@ -377,4 +641,6 @@ | ||
const fieldResolver = (source, args, contextValue, info) => { | ||
return Promise.reject(error) | ||
const rootValue = { | ||
hello: () => { | ||
return Promise.reject(error) | ||
} | ||
} | ||
@@ -395,3 +661,3 @@ | ||
graphql.graphql({ schema, source, fieldResolver }).catch(done) | ||
graphql.graphql({ schema, source, rootValue }).catch(done) | ||
}) | ||
@@ -398,0 +664,0 @@ }) |
@@ -10,3 +10,3 @@ 'use strict' | ||
let mysql | ||
let context | ||
let tracer | ||
@@ -16,3 +16,3 @@ describe('mysql', () => { | ||
plugin = require('../../src/plugins/mysql') | ||
context = require('../../src/platform').context() | ||
tracer = require('../..') | ||
}) | ||
@@ -48,11 +48,8 @@ | ||
it('should propagate context to callbacks', done => { | ||
context.run(() => { | ||
context.set('foo', 'bar') | ||
connection.query('SELECT 1 + 1 AS solution', callback) | ||
tracer.trace('test', span => { | ||
connection.query('SELECT 1 + 1 AS solution', () => { | ||
expect(tracer.currentSpan()).to.equal(span) | ||
done() | ||
}) | ||
}) | ||
function callback () { | ||
expect(context.get('foo')).to.equal('bar') | ||
done() | ||
} | ||
}) | ||
@@ -62,3 +59,3 @@ | ||
connection.query('SELECT 1 + 1 AS solution', () => { | ||
expect(context.get('current')).to.be.undefined | ||
expect(tracer.currentSpan()).to.be.null | ||
done() | ||
@@ -71,12 +68,9 @@ }) | ||
context.run(() => { | ||
context.set('foo', 'bar') | ||
tracer.trace('test', span => { | ||
query = connection.query('SELECT 1 + 1 AS solution') | ||
query.on('result', callback) | ||
query.on('result', () => { | ||
expect(tracer.currentSpan()).to.equal(span) | ||
done() | ||
}) | ||
}) | ||
function callback () { | ||
expect(context.get('foo')).to.equal('bar') | ||
done() | ||
} | ||
}) | ||
@@ -88,3 +82,3 @@ | ||
query.on('result', () => { | ||
expect(context.get('current')).to.be.undefined | ||
expect(tracer.currentSpan()).to.be.null | ||
done() | ||
@@ -98,3 +92,3 @@ }) | ||
expect(traces[0][0]).to.have.property('resource', 'SELECT 1 + 1 AS solution') | ||
expect(traces[0][0]).to.have.property('type', 'db') | ||
expect(traces[0][0]).to.have.property('type', 'sql') | ||
expect(traces[0][0].meta).to.have.property('db.name', 'db') | ||
@@ -201,3 +195,3 @@ expect(traces[0][0].meta).to.have.property('db.user', 'user') | ||
expect(traces[0][0]).to.have.property('resource', 'SELECT 1 + 1 AS solution') | ||
expect(traces[0][0]).to.have.property('type', 'db') | ||
expect(traces[0][0]).to.have.property('type', 'sql') | ||
expect(traces[0][0].meta).to.have.property('db.user', 'user') | ||
@@ -214,11 +208,8 @@ expect(traces[0][0].meta).to.have.property('db.type', 'mysql') | ||
it('should propagate context', done => { | ||
context.run(() => { | ||
context.set('foo', 'bar') | ||
pool.query('SELECT 1 + 1 AS solution', callback) | ||
tracer.trace('test', span => { | ||
pool.query('SELECT 1 + 1 AS solution', () => { | ||
expect(tracer.currentSpan()).to.equal(span) | ||
done() | ||
}) | ||
}) | ||
function callback () { | ||
expect(context.get('foo')).to.equal('bar') | ||
done() | ||
} | ||
}) | ||
@@ -228,3 +219,3 @@ | ||
pool.query('SELECT 1 + 1 AS solution', () => { | ||
expect(context.get('current')).to.be.undefined | ||
expect(tracer.currentSpan()).to.be.null | ||
done() | ||
@@ -231,0 +222,0 @@ }) |
@@ -93,3 +93,3 @@ 'use strict' | ||
expect(traces[0][0]).to.have.property('resource', 'SELECT 1 + 1 AS solution') | ||
expect(traces[0][0]).to.have.property('type', 'db') | ||
expect(traces[0][0]).to.have.property('type', 'sql') | ||
expect(traces[0][0].meta).to.have.property('db.name', 'db') | ||
@@ -201,3 +201,3 @@ expect(traces[0][0].meta).to.have.property('db.user', 'user') | ||
expect(traces[0][0]).to.have.property('resource', 'SELECT 1 + 1 AS solution') | ||
expect(traces[0][0]).to.have.property('type', 'db') | ||
expect(traces[0][0]).to.have.property('type', 'sql') | ||
expect(traces[0][0].meta).to.have.property('db.user', 'user') | ||
@@ -204,0 +204,0 @@ expect(traces[0][0].meta).to.have.property('db.type', 'mysql') |
@@ -45,3 +45,3 @@ 'use strict' | ||
expect(traces[0][0]).to.have.property('resource', 'SELECT $1::text as message') | ||
expect(traces[0][0]).to.have.property('type', 'db') | ||
expect(traces[0][0]).to.have.property('type', 'sql') | ||
expect(traces[0][0].meta).to.have.property('db.name', 'postgres') | ||
@@ -48,0 +48,0 @@ expect(traces[0][0].meta).to.have.property('db.user', 'postgres') |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
283249
123
7910
8