From b884eda41913ccc6dd0c5bd4e15c836bc5149b3c Mon Sep 17 00:00:00 2001 From: Florent Vilmart Date: Mon, 4 Oct 2021 14:22:19 -0400 Subject: [PATCH 01/22] Initial implementation with request --- package.json | 1 + packages/datadog-plugin-undici/src/index.js | 356 +++++++ .../datadog-plugin-undici/test/index.spec.js | 916 ++++++++++++++++++ packages/dd-trace/src/plugins/index.js | 84 +- packages/dd-trace/test/plugins/externals.json | 6 + 5 files changed, 1322 insertions(+), 41 deletions(-) create mode 100644 packages/datadog-plugin-undici/src/index.js create mode 100644 packages/datadog-plugin-undici/test/index.spec.js diff --git a/package.json b/package.json index ae5456ce411..25668f5924f 100644 --- a/package.json +++ b/package.json @@ -88,6 +88,7 @@ "source-map-resolve": "^0.6.0" }, "devDependencies": { + "abort-controller": "^3.0.0", "autocannon": "^4.5.2", "axios": "^0.21.2", "benchmark": "^2.1.4", diff --git a/packages/datadog-plugin-undici/src/index.js b/packages/datadog-plugin-undici/src/index.js new file mode 100644 index 00000000000..341d43cbfca --- /dev/null +++ b/packages/datadog-plugin-undici/src/index.js @@ -0,0 +1,356 @@ +'use strict' + +const url = require('url') +const opentracing = require('opentracing') +const log = require('../../dd-trace/src/log') +const constants = require('../../dd-trace/src/constants') +const tags = require('../../../ext/tags') +const kinds = require('../../../ext/kinds') +const formats = require('../../../ext/formats') +const urlFilter = require('../../dd-trace/src/plugins/util/urlfilter') +const analyticsSampler = require('../../dd-trace/src/analytics_sampler') + +const Reference = opentracing.Reference + +const HTTP_HEADERS = formats.HTTP_HEADERS +const HTTP_STATUS_CODE = tags.HTTP_STATUS_CODE +const HTTP_REQUEST_HEADERS = tags.HTTP_REQUEST_HEADERS +const HTTP_RESPONSE_HEADERS = tags.HTTP_RESPONSE_HEADERS +const SPAN_KIND = tags.SPAN_KIND +const CLIENT = kinds.CLIENT +const REFERENCE_CHILD_OF = opentracing.REFERENCE_CHILD_OF +const REFERENCE_NOOP = constants.REFERENCE_NOOP + +function patch (http, methodName, tracer, config) { + config = normalizeConfig(tracer, config) + this.wrap(http, methodName, fn => makeRequestTrace(fn)) + + function makeRequestTrace (request) { + return function requestTrace (options, callback) { + let args + + try { + args = normalizeArgs.apply(null, arguments) + } catch (e) { + log.error(e) + return request.apply(this, arguments) + } + + const protocol = options.protocol; + const hostname = options.hostname || options.host || 'localhost' + const host = options.port ? `${hostname}:${options.port}` : hostname + const path = options.path ? options.path.split(/[?#]/)[0] : '/' + const uri = `${options.origin}${path}` + + + const method = (options.method || 'GET').toUpperCase() + + const scope = tracer.scope() + const childOf = scope.active() + const type = config.filter(uri) ? REFERENCE_CHILD_OF : REFERENCE_NOOP + const span = tracer.startSpan('http.request', { + references: [ + new Reference(type, childOf) + ], + tags: { + [SPAN_KIND]: CLIENT, + 'service.name': getServiceName(tracer, config, options), + 'resource.name': method, + 'span.type': 'http', + 'http.method': method, + 'http.url': uri + } + }) + + if (!(hasAmazonSignature(options) || !config.propagationFilter(uri))) { + tracer.inject(span, HTTP_HEADERS, options.headers) + } + + analyticsSampler.sample(span, config.measured) + + + if (callback) { + callback = scope.bind(callback, childOf) + const req = scope.bind(request, span).call(this, options, function(err, res) { + if (err) { + addError(span, err) + } + finish(req, res, span, config) + callback(err, res); + }) + return req + } + + const req = scope.bind(request, span).call(this, options) + req.then((res) => { + finish(options, res, span, config) + return res + }).catch((err) => { + addError(span, err) + finish(options, err, span, config) + throw err; + }) + return req + // const emit = req.emit + + // req.emit = function (eventName, arg) { + // switch (eventName) { + // case 'response': { + // const res = arg + + // scope.bind(res) + + // res.on('end', () => finish(req, res, span, config)) + + // break + // } + // case 'error': + // addError(span, arg) + // case 'abort': // eslint-disable-line no-fallthrough + // case 'timeout': // eslint-disable-line no-fallthrough + // finish(req, null, span, config) + // } + + // return emit.apply(this, arguments) + // } + + // scope.bind(req) + + return req + } + } + + function finish (options, res, span, config) { + if (res) { + span.setTag(HTTP_STATUS_CODE, res.statusCode) + + if (!config.validateStatus(res.statusCode)) { + span.setTag('error', 1) + } + + addResponseHeaders(res, span, config) + } else { + span.setTag('error', 1) + } + + addRequestHeaders(options, span, config) + + config.hooks.request(span, options, res) + + span.finish() + } + + function addError (span, error) { + span.addTags({ + 'error.type': error.name, + 'error.msg': error.message, + 'error.stack': error.stack + }) + + return error + } + + function addRequestHeaders (req, span, config) { + config.headers.forEach(key => { + const value = req.headers[key] + + if (value) { + span.setTag(`${HTTP_REQUEST_HEADERS}.${key}`, value) + } + }) + } + + function addResponseHeaders (res, span, config) { + config.headers.forEach(key => { + const value = res.headers[key] + if (value) { + span.setTag(`${HTTP_RESPONSE_HEADERS}.${key}`, value) + } + }) + } + + function normalizeArgs (inputURL, inputOptions, cb) { + inputURL = normalizeOptions(inputURL) + + const [callback, inputOptionsNormalized] = normalizeCallback(inputOptions, cb, inputURL) + const options = combineOptions(inputURL, inputOptionsNormalized) + normalizeHeaders(options) + const uri = url.format(options) + + return { uri, options, callback } + } + + function normalizeCallback (inputOptions, callback, inputURL) { + if (typeof inputOptions === 'function') { + return [inputOptions, inputURL || {}] + } else { + return [callback, inputOptions] + } + } + + function combineOptions (inputURL, inputOptions) { + if (typeof inputOptions === 'object') { + return Object.assign(inputURL || {}, inputOptions) + } else { + return inputURL + } + } + + function normalizeHeaders (options) { + options.headers = options.headers || {} + } + + // https://github.com/nodejs/node/blob/7e911d8b03a838e5ac6bb06c5b313533e89673ef/lib/internal/url.js#L1271 + function urlToOptions (url) { + const agent = url.agent || http.globalAgent + const options = { + protocol: url.protocol || agent.protocol, + hostname: typeof url.hostname === 'string' && url.hostname.startsWith('[') + ? url.hostname.slice(1, -1) + : url.hostname || + url.host || + 'localhost', + hash: url.hash, + search: url.search, + pathname: url.pathname, + path: `${url.pathname || ''}${url.search || ''}`, + href: url.href + } + if (url.port !== '') { + options.port = Number(url.port) + } + if (url.username || url.password) { + options.auth = `${url.username}:${url.password}` + } + return options + } + + function normalizeOptions (inputURL) { + if (typeof inputURL === 'string') { + try { + return urlToOptions(new url.URL(inputURL)) + } catch (e) { + return url.parse(inputURL) + } + } else if (inputURL instanceof url.URL) { + return urlToOptions(inputURL) + } else { + return inputURL + } + } +} + +function getHost (options) { + return url.parse(options.origin).host +} + +function getServiceName (tracer, config, options) { + if (config.splitByDomain) { + return getHost(options) + } else if (config.service) { + return config.service + } + + return `${tracer._service}-http-client` +} + +function hasAmazonSignature (options) { + if (!options) { + return false + } + + if (options.headers) { + const headers = Object.keys(options.headers) + .reduce((prev, next) => Object.assign(prev, { + [next.toLowerCase()]: options.headers[next] + }), {}) + + if (headers['x-amz-signature']) { + return true + } + + if ([].concat(headers['authorization']).some(startsWith('AWS4-HMAC-SHA256'))) { + return true + } + } + + return options.path && options.path.toLowerCase().indexOf('x-amz-signature=') !== -1 +} + +function startsWith (searchString) { + return value => String(value).startsWith(searchString) +} + +function unpatch (http) { + this.unwrap(http, 'request') + this.unwrap(http, 'get') +} + +function getStatusValidator (config) { + if (typeof config.validateStatus === 'function') { + return config.validateStatus + } else if (config.hasOwnProperty('validateStatus')) { + log.error('Expected `validateStatus` to be a function.') + } + return code => code < 400 || code >= 500 +} + +function getFilter (tracer, config) { + const blocklist = tracer._url ? [getAgentFilter(tracer._url)] : [] + + config = Object.assign({}, config, { + blocklist: blocklist.concat(config.blocklist || []) + }) + + return urlFilter.getFilter(config) +} + +function getAgentFilter (url) { + // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#Escaping + const agentFilter = url.href.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') + + return RegExp(`^${agentFilter}.*$`, 'i') +} + +function normalizeConfig (tracer, config) { + const validateStatus = getStatusValidator(config) + const filter = getFilter(tracer, config) + const propagationFilter = getFilter(tracer, { blocklist: config.propagationBlocklist }) + const headers = getHeaders(config) + const hooks = getHooks(config) + + return Object.assign({}, config, { + validateStatus, + filter, + propagationFilter, + headers, + hooks + }) +} + +function getHeaders (config) { + if (!Array.isArray(config.headers)) return [] + + return config.headers + .filter(key => typeof key === 'string') + .map(key => key.toLowerCase()) +} + +function getHooks (config) { + const noop = () => {} + const request = (config.hooks && config.hooks.request) || noop + + return { request } +} + +module.exports = [ + { + name: 'undici', + versions: ['>=4'], + file: 'lib/api/index.js', + patch: function (undici, tracer, config) { + patch.call(this, undici, 'request', tracer, config) + }, + unpatch + } +] diff --git a/packages/datadog-plugin-undici/test/index.spec.js b/packages/datadog-plugin-undici/test/index.spec.js new file mode 100644 index 00000000000..267ac87b844 --- /dev/null +++ b/packages/datadog-plugin-undici/test/index.spec.js @@ -0,0 +1,916 @@ +'use strict' + +const AbortController = require('abort-controller') +const getPort = require('get-port') +const agent = require('../../dd-trace/test/plugins/agent') +const fs = require('fs') +const path = require('path') +const tags = require('../../../ext/tags') +const { expect } = require('chai') + +const HTTP_REQUEST_HEADERS = tags.HTTP_REQUEST_HEADERS +const HTTP_RESPONSE_HEADERS = tags.HTTP_RESPONSE_HEADERS +const plugin = require('../src') + +wrapIt() + +describe('undici', () => { + let express + let undici + let appListener + let tracer + + withVersions(plugin, 'undici', version => { + function server (app, port, listener) { + const server = require('http').createServer(app) + server.listen(port, 'localhost', listener) + return server + } + + beforeEach(() => { + tracer = require('../../dd-trace') + appListener = null + }) + + afterEach(() => { + if (appListener) { + appListener.close() + } + return agent.close() + }) + + describe('without configuration', () => { + beforeEach(() => { + return agent.load('undici') + .then(() => { + undici = require(`../../../versions/undici@${version}`).get() + express = require('express') + }) + }) + + it('should do automatic instrumentation', done => { + const app = express() + + app.get('/user', (req, res) => { + res.status(200).send() + }) + + getPort().then(port => { + agent + .use(traces => { + expect(traces[0][0]).to.have.property('service', 'test-http-client') + 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', 'client') + 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', '200') + }) + .then(done) + .catch(done) + + appListener = server(app, port, () => { + undici.request(`http://localhost:${port}/user`) + }) + }) + }) + + it('should support configuration as an URL object', done => { + const app = express() + + app.get('/user', (req, res) => { + res.status(200).send() + }) + + getPort().then(port => { + agent + .use(traces => { + expect(traces[0][0].meta).to.have.property('http.url', `http://localhost:${port}/user`) + }) + .then(done) + .catch(done) + + const uri = { + protocol: `http:`, + hostname: 'localhost', + port, + path: '/user' + } + + appListener = server(app, port, () => { + undici.request(uri) + }) + }) + }) + + it('should remove the query string from the URL', done => { + const app = express() + + app.get('/user', (req, res) => { + res.status(200).send() + }) + + getPort().then(port => { + agent + .use(traces => { + expect(traces[0][0].meta).to.have.property('http.status_code', '200') + expect(traces[0][0].meta).to.have.property('http.url', `http://localhost:${port}/user`) + }) + .then(done) + .catch(done) + + appListener = server(app, port, () => { + undici.request(`http://localhost:${port}/user?foo=bar`) + }) + }) + }) + + // TODO: is this a thing in undici? + it.skip('should support a string URL and an options object, which merges and takes precedence', done => { + const app = express() + + app.get('/user', (req, res) => { + res.status(200).send() + }) + + getPort().then(port => { + agent + .use(traces => { + expect(traces[0][0].meta).to.have.property('http.status_code', '200') + expect(traces[0][0].meta).to.have.property('http.url', `http://localhost:${port}/user`) + }) + .then(done) + .catch(done) + + appListener = server(app, port, () => { + const req = undici.request(`http://localhost:${port}/another-path`, { path: '/user' }) + + }) + }) + }) + + // TODO: is that valid undici? + it.skip('should support a URL object and an options object, which merges and takes precedence', done => { + const app = express() + + app.get('/user', (req, res) => { + res.status(200).send() + }) + + getPort().then(port => { + agent + .use(traces => { + expect(traces[0][0].meta).to.have.property('http.status_code', '200') + expect(traces[0][0].meta).to.have.property('http.url', `http://localhost:${port}/user`) + }) + .then(done) + .catch(done) + + const uri = { + protocol: `http:`, + hostname: 'localhost', + port, + pathname: '/another-path' + } + + appListener = server(app, port, () => { + const req = undici.request(uri, { path: '/user' }) + + }) + }) + }) + + it('should support configuration as an WHATWG URL object', async () => { + const app = express() + const port = await getPort() + const url = new URL(`http://localhost:${port}/user`) + + app.get('/user', (req, res) => res.status(200).send()) + + appListener = server(app, port, () => { + const req = undici.request(url) + }) + + await agent.use(traces => { + expect(traces[0][0].meta).to.have.property('http.status_code', '200') + expect(traces[0][0].meta).to.have.property('http.url', `http://localhost:${port}/user`) + }) + }) + + // TODO: is this a valid undici request? + it.skip('should use the correct defaults when not specified', done => { + const app = express() + + app.get('/user', (req, res) => { + res.status(200).send() + }) + + getPort().then(port => { + agent + .use(traces => { + expect(traces[0][0].meta).to.have.property('http.url', `http://localhost:${port}/`) + }) + .then(done) + .catch(done) + + appListener = server(app, port, () => { + const req = undici.request({ + protocol: `http:`, + port + }) + }) + }) + }) + + it('should not require consuming the data', done => { + const app = express() + + app.get('/user', (req, res) => { + res.status(200).send() + }) + + getPort().then(port => { + agent + .use(traces => { + expect(traces[0][0]).to.not.be.undefined + }) + .then(done) + .catch(done) + + appListener = server(app, port, () => { + const req = undici.request(`http://localhost:${port}/user`) + + }) + }) + }) + + it('should inject its parent span in the headers', done => { + const app = express() + + app.get('/user', (req, res) => { + expect(req.get('x-datadog-trace-id')).to.be.a('string') + expect(req.get('x-datadog-parent-id')).to.be.a('string') + + res.status(200).send() + }) + + getPort().then(port => { + agent + .use(traces => { + expect(traces[0][0].meta).to.have.property('http.status_code', '200') + }) + .then(done) + .catch(done) + + appListener = server(app, port, () => { + undici.request(`http://localhost:${port}/user`) + }) + }) + }) + + it('should skip injecting if the Authorization header contains an AWS signature', done => { + const app = express() + + app.get('/', (req, res) => { + try { + expect(req.get('x-datadog-trace-id')).to.be.undefined + expect(req.get('x-datadog-parent-id')).to.be.undefined + + res.status(200).send() + + done() + } catch (e) { + done(e) + } + }) + + getPort().then(port => { + appListener = server(app, port, () => { + undici.request(`http://localhost:${port}/`, { + headers: { + Authorization: 'AWS4-HMAC-SHA256 ...' + } + }) + }) + }) + }) + + it('should skip injecting if one of the Authorization headers contains an AWS signature', done => { + const app = express() + + app.get('/', (req, res) => { + try { + expect(req.get('x-datadog-trace-id')).to.be.undefined + expect(req.get('x-datadog-parent-id')).to.be.undefined + + res.status(200).send() + + done() + } catch (e) { + done(e) + } + }) + + getPort().then(port => { + appListener = server(app, port, () => { + undici.request(`http://localhost:${port}/`, { + headers: { + Authorization: 'AWS4-HMAC-SHA256 ...' + } + }) + }) + }) + }) + + it('should skip injecting if the X-Amz-Signature header is set', done => { + const app = express() + + app.get('/', (req, res) => { + try { + expect(req.get('x-datadog-trace-id')).to.be.undefined + expect(req.get('x-datadog-parent-id')).to.be.undefined + + res.status(200).send() + + done() + } catch (e) { + done(e) + } + }) + + getPort().then(port => { + appListener = server(app, port, () => { + undici.request(`http://localhost:${port}/`, { + headers: { + Authorization: 'AWS4-HMAC-SHA256 ...' + } + }) + }) + }) + }) + + it('should skip injecting if the X-Amz-Signature query param is set', done => { + const app = express() + + app.get('/', (req, res) => { + try { + expect(req.get('x-datadog-trace-id')).to.be.undefined + expect(req.get('x-datadog-parent-id')).to.be.undefined + + res.status(200).send() + + done() + } catch (e) { + done(e) + } + }) + + getPort().then(port => { + appListener = server(app, port, () => { + undici.request(`http://localhost:${port}/?X-Amz-Signature=abc123`, { + headers: { + Authorization: 'AWS4-HMAC-SHA256 ...' + } + }) + }) + }) + }) + + // TODO: the callbacks is run after the scope ends + it.skip('should run the callback in the parent context', done => { + if (process.env.DD_CONTEXT_PROPAGATION === 'false') return done() + + const app = express() + + app.get('/user', (req, res) => { + res.status(200).send('OK') + }) + + getPort().then(port => { + appListener = server(app, port, () => { + const req = undici.request(`http://localhost:${port}/user`, res => { + expect(tracer.scope().active()).to.be.null + done() + }) + + }) + }) + }) + + // TODO: There is no event listener yet + it.skip('should run the event listeners in the parent context', done => { + if (process.env.DD_CONTEXT_PROPAGATION === 'false') return done() + + const app = express() + + app.get('/user', (req, res) => { + res.status(200).send('OK') + }) + + getPort().then(port => { + appListener = server(app, port, () => { + const req = undici.request(`http://localhost:${port}/user`, res => { + done() + }) + }) + }) + }) + + it('should handle connection errors', done => { + getPort().then(port => { + let error + + agent + .use(traces => { + expect(traces[0][0].meta).to.have.property('error.type', error.name) + expect(traces[0][0].meta).to.have.property('error.msg', error.message) + expect(traces[0][0].meta).to.have.property('error.stack', error.stack) + }) + .then(done) + .catch(done) + + undici.request(`http://localhost:${port}/user`, (err) => { + // console.error(err) + error = err + }) + }) + }) + + it('should not record HTTP 5XX responses as errors by default', done => { + const app = express() + + app.get('/user', (req, res) => { + res.status(500).send() + }) + + getPort().then(port => { + agent + .use(traces => { + expect(traces[0][0]).to.have.property('error', 0) + }) + .then(done) + .catch(done) + + appListener = server(app, port, () => { + undici.request(`http://localhost:${port}/user`) + }) + }) + }) + + it('should record HTTP 4XX responses as errors by default', 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 = server(app, port, () => { + undici.request(`http://localhost:${port}/user`) + }) + }) + }) + + // TODO: how to destroy requests with undici + it.skip('should record destroyed requests as errors', done => { + const app = express() + + app.get('/user', (req, res) => {}) + + getPort().then(port => { + let error + + agent + .use(traces => { + expect(traces[0][0]).to.have.property('error', 1) + expect(traces[0][0].meta).to.have.property('error.msg', error.message) + expect(traces[0][0].meta).to.have.property('error.type', error.name) + expect(traces[0][0].meta).to.have.property('error.stack', error.stack) + expect(traces[0][0].meta).to.not.have.property('http.status_code') + }) + .then(done) + .catch(done) + + appListener = server(app, port, () => { + const client = new undici.Client(`http://localhost:${port}`) + const req = client.request({ + path: '/user', + method: 'GET' + }, (err, data) => { + error = err; + }) + client.destroy(); + }) + }) + }) + + it('should record aborted requests as errors', done => { + const app = express() + + app.get('/user', (req, res) => {}) + + getPort().then(port => { + agent + .use(traces => { + expect(traces[0][0]).to.have.property('error', 1) + expect(traces[0][0].meta).to.not.have.property('http.status_code') + }) + .then(done) + .catch(done) + + appListener = server(app, port, () => { + const abort = new AbortController(); + undici.request(`http://localhost:${port}/user`,{ + signal: abort.signal + }) + abort.abort() + }) + }) + }) + + // TODO: Get timeout working + it.skip('should record timeouts as errors', done => { + const app = express() + + app.get('/user', (req, res) => {}) + + getPort().then(port => { + agent + .use(traces => { + expect(traces[0][0]).to.have.property('error', 1) + expect(traces[0][0].meta).to.not.have.property('http.status_code') + }) + .then(done) + .catch(done) + + appListener = server(app, port, () => { + undici.request(`http://localhost:${port}/user`, { + bodyTimeout: 0, + headersTimeout: 0, + }) + }) + }) + }) + + it('should record when the request was aborted', done => { + const app = express() + + app.get('/abort', (req, res) => { + res.status(200).send() + }) + + getPort().then(port => { + agent + .use(traces => { + expect(traces[0][0]).to.have.property('service', 'test-http-client') + }) + .then(done) + .catch(done) + + appListener = server(app, port, () => { + const ac = new AbortController() + const req = undici.request(`http://localhost:${port}/abort`, { signal: ac.signal }) + + ac.abort() + }) + }) + }) + + // TODO: Is that a real thing? + it.skip('should use the correct fallback protocol', done => { + const app = express() + + app.get('/user', (req, res) => { + res.status(200).send() + }) + + getPort().then(port => { + agent + .use(traces => { + expect(traces[0][0].meta).to.have.property('http.status_code', '200') + expect(traces[0][0].meta).to.have.property('http.url', `http://localhost:${port}/user`) + }) + .then(done) + .catch(done) + + appListener = server(app, port, () => { + const req = undici.request({ + hostname: 'localhost', + port, + path: '/user?foo=bar' + }, res => { + res.on('data', () => {}) + }) + }) + }) + }) + + it('should skip requests to the agent', done => { + const app = express() + + app.get('/user', (req, res) => { + res.status(200).send() + }) + + getPort().then(port => { + const timer = setTimeout(done, 100) + + agent + .use(() => { + done(new Error('Request to the agent was traced.')) + clearTimeout(timer) + }) + + appListener = server(app, port, () => { + undici.request(tracer._tracer._url.href) + }) + }) + }) + }) + + describe('with service configuration', () => { + let config + + beforeEach(() => { + config = { + service: 'custom' + } + + return agent.load('undici', config) + .then(() => { + undici = require(`../../../versions/undici@${version}`).get() + express = require('express') + }) + }) + + it('should be configured with the correct values', done => { + const app = express() + + app.get('/user', (req, res) => { + res.status(200).send() + }) + + getPort().then(port => { + agent + .use(traces => { + expect(traces[0][0]).to.have.property('service', 'custom') + }) + .then(done) + .catch(done) + + appListener = server(app, port, () => { + undici.request(`http://localhost:${port}/user`) + }) + }) + }) + }) + + describe('with validateStatus configuration', () => { + let config + + beforeEach(() => { + config = { + validateStatus: status => status < 500 + } + + return agent.load('undici', config) + .then(() => { + undici = require(`../../../versions/undici@${version}`).get() + express = require('express') + }) + }) + + it('should use the supplied function to decide if a response is an error', done => { + const app = express() + + app.get('/user', (req, res) => { + res.status(500).send() + }) + + getPort().then(port => { + agent + .use(traces => { + expect(traces[0][0]).to.have.property('error', 1) + }) + .then(done) + .catch(done) + + appListener = server(app, port, () => { + undici.request(`http://localhost:${port}/user`) + }) + }) + }) + }) + + describe('with splitByDomain configuration', () => { + let config + + beforeEach(() => { + config = { + splitByDomain: true + } + return agent.load('undici', config) + .then(() => { + undici = require(`../../../versions/undici@${version}`).get() + express = require('express') + }) + }) + + it('should use the remote endpoint as the service name', done => { + const app = express() + + app.get('/user', (req, res) => { + res.status(200).send() + }) + + getPort().then(port => { + agent + .use(traces => { + expect(traces[0][0]).to.have.property('service', `localhost:${port}`) + }) + .then(done) + .catch(done) + + appListener = server(app, port, () => { + undici.request(`http://localhost:${port}/user`) + }) + }) + }) + }) + + describe('with headers configuration', () => { + let config + + beforeEach(() => { + config = { + headers: ['host', 'x-foo'] + } + return agent.load('undici', config) + .then(() => { + undici = require(`../../../versions/undici@${version}`).get() + express = require('express') + }) + }) + + // TODO: Injected headers are not available yet + it.skip('should add tags for the host header', done => { + const app = express() + + app.get('/user', (req, res) => { + res.setHeader('x-foo', 'bar') + res.status(200).send() + }) + + getPort().then(port => { + agent + .use(traces => { + const meta = traces[0][0].meta + expect(meta).to.have.property(`${HTTP_REQUEST_HEADERS}.host`, `localhost:${port}`) + }) + .then(done) + .catch(done) + + appListener = server(app, port, () => { + undici.request(`http://localhost:${port}/user`) + }) + }) + }) + + it('should add tags for the configured headers', done => { + const app = express() + + app.get('/user', (req, res) => { + res.setHeader('x-foo', 'bar') + res.status(200).send() + }) + + getPort().then(port => { + agent + .use(traces => { + const meta = traces[0][0].meta + expect(meta).to.have.property(`${HTTP_RESPONSE_HEADERS}.x-foo`, 'bar') + }) + .then(done) + .catch(done) + + appListener = server(app, port, () => { + undici.request(`http://localhost:${port}/user`) + }) + }) + }) + + it('should support adding request headers', done => { + const app = express() + + app.get('/user', (req, res) => { + res.status(200).send() + }) + + getPort().then(port => { + agent + .use(traces => { + const meta = traces[0][0].meta + expect(meta).to.have.property(`${HTTP_REQUEST_HEADERS}.x-foo`, `bar`) + }) + .then(done) + .catch(done) + + appListener = server(app, port, () => { + undici.request(`http://localhost:${port}/user`, { + headers: {'x-foo': 'bar'} + }) + }) + }) + }) + }) + + describe('with hooks configuration', () => { + let config + + beforeEach(() => { + config = { + hooks: { + request: (span, req, res) => { + span.setTag('resource.name', `${req.method} ${req._route}`) + } + } + } + return agent.load('undici', config) + .then(() => { + undici = require(`../../../versions/undici@${version}`).get() + express = require('express') + }) + }) + + // TODO: how does it translates? + it.skip('should run the request hook before the span is finished', done => { + const app = express() + + app.get('/user', (req, res) => { + res.status(200).send() + }) + + getPort().then(port => { + agent + .use(traces => { + expect(traces[0][0]).to.have.property('resource', 'GET /user') + }) + .then(done) + .catch(done) + + appListener = server(app, port, () => { + const req = undici.request(`http://localhost:${port}/user`) + + req._route = '/user' + + }) + }) + }) + }) + + describe('with propagationBlocklist configuration', () => { + let config + + beforeEach(() => { + config = { + propagationBlocklist: [/\/users/] + } + return agent.load('undici', config) + .then(() => { + undici = require(`../../../versions/undici@${version}`).get() + express = require('express') + }) + }) + + it('should skip injecting if the url matches an item in the propagationBlacklist', done => { + const app = express() + + app.get('/users', (req, res) => { + try { + expect(req.get('x-datadog-trace-id')).to.be.undefined + expect(req.get('x-datadog-parent-id')).to.be.undefined + + res.status(200).send() + + done() + } catch (e) { + done(e) + } + }) + + getPort().then(port => { + appListener = server(app, port, () => { + undici.request(`http://localhost:${port}/users`) + }) + }) + }) + }) + }) +}) diff --git a/packages/dd-trace/src/plugins/index.js b/packages/dd-trace/src/plugins/index.js index 3871bb9032b..a0fae672c7f 100644 --- a/packages/dd-trace/src/plugins/index.js +++ b/packages/dd-trace/src/plugins/index.js @@ -1,52 +1,54 @@ 'use strict' module.exports = { - 'amqp10': require('../../../datadog-plugin-amqp10/src'), - 'amqplib': require('../../../datadog-plugin-amqplib/src'), + amqp10: require('../../../datadog-plugin-amqp10/src'), + amqplib: require('../../../datadog-plugin-amqplib/src'), 'aws-sdk': require('../../../datadog-plugin-aws-sdk/src'), - 'bunyan': require('../../../datadog-plugin-bunyan/src'), + bunyan: require('../../../datadog-plugin-bunyan/src'), 'cassandra-driver': require('../../../datadog-plugin-cassandra-driver/src'), - 'connect': require('../../../datadog-plugin-connect/src'), - 'couchbase': require('../../../datadog-plugin-couchbase/src'), - 'cucumber': require('../../../datadog-plugin-cucumber/src'), - 'cypress': require('../../../datadog-plugin-cypress/src'), - 'dns': require('../../../datadog-plugin-dns/src'), - 'elasticsearch': require('../../../datadog-plugin-elasticsearch/src'), - 'express': require('../../../datadog-plugin-express/src'), - 'fastify': require('../../../datadog-plugin-fastify/src'), - 'fs': require('../../../datadog-plugin-fs/src'), + connect: require('../../../datadog-plugin-connect/src'), + couchbase: require('../../../datadog-plugin-couchbase/src'), + cucumber: require('../../../datadog-plugin-cucumber/src'), + cypress: require('../../../datadog-plugin-cypress/src'), + dns: require('../../../datadog-plugin-dns/src'), + elasticsearch: require('../../../datadog-plugin-elasticsearch/src'), + express: require('../../../datadog-plugin-express/src'), + fastify: require('../../../datadog-plugin-fastify/src'), + fs: require('../../../datadog-plugin-fs/src'), 'generic-pool': require('../../../datadog-plugin-generic-pool/src'), 'google-cloud-pubsub': require('../../../datadog-plugin-google-cloud-pubsub/src'), - 'graphql': require('../../../datadog-plugin-graphql/src'), - 'grpc': require('../../../datadog-plugin-grpc/src'), - 'hapi': require('../../../datadog-plugin-hapi/src'), - 'http': require('../../../datadog-plugin-http/src'), - 'http2': require('../../../datadog-plugin-http2/src'), - 'ioredis': require('../../../datadog-plugin-ioredis/src'), - 'jest': require('../../../datadog-plugin-jest/src'), - 'knex': require('../../../datadog-plugin-knex/src'), - 'koa': require('../../../datadog-plugin-koa/src'), - 'kafkajs': require('../../../datadog-plugin-kafkajs/src'), + graphql: require('../../../datadog-plugin-graphql/src'), + grpc: require('../../../datadog-plugin-grpc/src'), + hapi: require('../../../datadog-plugin-hapi/src'), + http: require('../../../datadog-plugin-http/src'), + http2: require('../../../datadog-plugin-http2/src'), + ioredis: require('../../../datadog-plugin-ioredis/src'), + jest: require('../../../datadog-plugin-jest/src'), + knex: require('../../../datadog-plugin-knex/src'), + koa: require('../../../datadog-plugin-koa/src'), + kafkajs: require('../../../datadog-plugin-kafkajs/src'), 'limitd-client': require('../../../datadog-plugin-limitd-client/src'), - 'memcached': require('../../../datadog-plugin-memcached/src'), + memcached: require('../../../datadog-plugin-memcached/src'), 'microgateway-core': require('../../../datadog-plugin-microgateway-core/src'), - 'mocha': require('../../../datadog-plugin-mocha/src'), - 'moleculer': require('../../../datadog-plugin-moleculer/src'), + mocha: require('../../../datadog-plugin-mocha/src'), + moleculer: require('../../../datadog-plugin-moleculer/src'), 'mongodb-core': require('../../../datadog-plugin-mongodb-core/src'), - 'mongoose': require('../../../datadog-plugin-mongoose/src'), - 'mysql': require('../../../datadog-plugin-mysql/src'), - 'mysql2': require('../../../datadog-plugin-mysql2/src'), - 'net': require('../../../datadog-plugin-net/src'), - 'next': require('../../../datadog-plugin-next/src'), - 'oracledb': require('../../../datadog-plugin-oracledb/src'), - 'paperplane': require('../../../datadog-plugin-paperplane/src'), - 'pg': require('../../../datadog-plugin-pg/src'), - 'pino': require('../../../datadog-plugin-pino/src'), - 'redis': require('../../../datadog-plugin-redis/src'), - 'restify': require('../../../datadog-plugin-restify/src'), - 'rhea': require('../../../datadog-plugin-rhea/src'), - 'router': require('../../../datadog-plugin-router/src'), - 'sharedb': require('../../../datadog-plugin-sharedb/src'), - 'tedious': require('../../../datadog-plugin-tedious/src'), - 'winston': require('../../../datadog-plugin-winston/src') + mongoose: require('../../../datadog-plugin-mongoose/src'), + mysql: require('../../../datadog-plugin-mysql/src'), + mysql2: require('../../../datadog-plugin-mysql2/src'), + net: require('../../../datadog-plugin-net/src'), + next: require('../../../datadog-plugin-next/src'), + oracledb: require('../../../datadog-plugin-oracledb/src'), + paperplane: require('../../../datadog-plugin-paperplane/src'), + pg: require('../../../datadog-plugin-pg/src'), + pino: require('../../../datadog-plugin-pino/src'), + redis: require('../../../datadog-plugin-redis/src'), + restify: require('../../../datadog-plugin-restify/src'), + rhea: require('../../../datadog-plugin-rhea/src'), + router: require('../../../datadog-plugin-router/src'), + sharedb: require('../../../datadog-plugin-sharedb/src'), + tedious: require('../../../datadog-plugin-tedious/src'), + undici: require('../../../datadog-plugin-undici/src'), + when: require('../../../datadog-plugin-when/src'), + winston: require('../../../datadog-plugin-winston/src') } diff --git a/packages/dd-trace/test/plugins/externals.json b/packages/dd-trace/test/plugins/externals.json index 2a01dc12eee..04ed7dbcb15 100644 --- a/packages/dd-trace/test/plugins/externals.json +++ b/packages/dd-trace/test/plugins/externals.json @@ -130,5 +130,11 @@ "name": "pino-pretty", "dep": true } + ], + "undici": [ + { + "name": "undici", + "versions": [">=4"] + } ] } From c5fe7df77ae2d8ba926341ce4569074d9329d329 Mon Sep 17 00:00:00 2001 From: Florent Vilmart Date: Tue, 5 Oct 2021 13:00:29 -0400 Subject: [PATCH 02/22] update min version --- packages/dd-trace/test/plugins/externals.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/dd-trace/test/plugins/externals.json b/packages/dd-trace/test/plugins/externals.json index 04ed7dbcb15..b82a2d697c5 100644 --- a/packages/dd-trace/test/plugins/externals.json +++ b/packages/dd-trace/test/plugins/externals.json @@ -134,7 +134,7 @@ "undici": [ { "name": "undici", - "versions": [">=4"] + "versions": [">=4.7.0"] } ] } From 375459aba539050b6dda40b74bf16092a28db92f Mon Sep 17 00:00:00 2001 From: Florent Vilmart Date: Tue, 5 Oct 2021 16:05:40 -0400 Subject: [PATCH 03/22] Implement through diagnostics channels --- packages/datadog-plugin-undici/src/index.js | 366 ++++++++---------- .../datadog-plugin-undici/test/index.spec.js | 229 +++++------ 2 files changed, 244 insertions(+), 351 deletions(-) diff --git a/packages/datadog-plugin-undici/src/index.js b/packages/datadog-plugin-undici/src/index.js index 341d43cbfca..e5ff5745a89 100644 --- a/packages/datadog-plugin-undici/src/index.js +++ b/packages/datadog-plugin-undici/src/index.js @@ -9,6 +9,7 @@ const kinds = require('../../../ext/kinds') const formats = require('../../../ext/formats') const urlFilter = require('../../dd-trace/src/plugins/util/urlfilter') const analyticsSampler = require('../../dd-trace/src/analytics_sampler') +const diagnosticsChannel = require('diagnostics_channel') const Reference = opentracing.Reference @@ -21,223 +22,162 @@ const CLIENT = kinds.CLIENT const REFERENCE_CHILD_OF = opentracing.REFERENCE_CHILD_OF const REFERENCE_NOOP = constants.REFERENCE_NOOP -function patch (http, methodName, tracer, config) { - config = normalizeConfig(tracer, config) - this.wrap(http, methodName, fn => makeRequestTrace(fn)) - - function makeRequestTrace (request) { - return function requestTrace (options, callback) { - let args - - try { - args = normalizeArgs.apply(null, arguments) - } catch (e) { - log.error(e) - return request.apply(this, arguments) - } - - const protocol = options.protocol; - const hostname = options.hostname || options.host || 'localhost' - const host = options.port ? `${hostname}:${options.port}` : hostname - const path = options.path ? options.path.split(/[?#]/)[0] : '/' - const uri = `${options.origin}${path}` - - - const method = (options.method || 'GET').toUpperCase() - - const scope = tracer.scope() - const childOf = scope.active() - const type = config.filter(uri) ? REFERENCE_CHILD_OF : REFERENCE_NOOP - const span = tracer.startSpan('http.request', { - references: [ - new Reference(type, childOf) - ], - tags: { - [SPAN_KIND]: CLIENT, - 'service.name': getServiceName(tracer, config, options), - 'resource.name': method, - 'span.type': 'http', - 'http.method': method, - 'http.url': uri - } - }) - - if (!(hasAmazonSignature(options) || !config.propagationFilter(uri))) { - tracer.inject(span, HTTP_HEADERS, options.headers) - } - - analyticsSampler.sample(span, config.measured) - - - if (callback) { - callback = scope.bind(callback, childOf) - const req = scope.bind(request, span).call(this, options, function(err, res) { - if (err) { - addError(span, err) - } - finish(req, res, span, config) - callback(err, res); - }) - return req - } - - const req = scope.bind(request, span).call(this, options) - req.then((res) => { - finish(options, res, span, config) - return res - }).catch((err) => { - addError(span, err) - finish(options, err, span, config) - throw err; - }) - return req - // const emit = req.emit - - // req.emit = function (eventName, arg) { - // switch (eventName) { - // case 'response': { - // const res = arg - - // scope.bind(res) - - // res.on('end', () => finish(req, res, span, config)) - - // break - // } - // case 'error': - // addError(span, arg) - // case 'abort': // eslint-disable-line no-fallthrough - // case 'timeout': // eslint-disable-line no-fallthrough - // finish(req, null, span, config) - // } - - // return emit.apply(this, arguments) - // } +function addError (span, error) { + span.addTags({ + 'error.type': error.name, + 'error.msg': error.message, + 'error.stack': error.stack + }) + return error +} - // scope.bind(req) +function parseHeaders (headers) { + const pairs = headers + .split('\r\n') + .map((r) => r.split(':').map((str) => str.trim())) - return req + const object = {} + pairs.forEach(([key, value]) => { + key = key.toLowerCase() + if (!value) { + return } - } - - function finish (options, res, span, config) { - if (res) { - span.setTag(HTTP_STATUS_CODE, res.statusCode) - - if (!config.validateStatus(res.statusCode)) { - span.setTag('error', 1) + if (object[key]) { + if (!Array.isArray(object[key])) { + object[key] = [object[key], value] + } else { + object[key].push(value) } - - addResponseHeaders(res, span, config) } else { - span.setTag('error', 1) + object[key] = value } + }) + return object +} - addRequestHeaders(options, span, config) - - config.hooks.request(span, options, res) - - span.finish() - } +function diagnostics (tracer, config) { + config = normalizeConfig(tracer, config) - function addError (span, error) { - span.addTags({ - 'error.type': error.name, - 'error.msg': error.message, - 'error.stack': error.stack + const requestChannel = diagnosticsChannel.channel('undici:request:create') + const headersChannel = diagnosticsChannel.channel('undici:request:headers') + const requestErrorChannel = diagnosticsChannel.channel( + 'undici:request:error' + ) + + // We use a weakmap here to store the request / spans + const requestSpansMap = new WeakMap() + + requestChannel.subscribe(handleRequestCreate) + requestErrorChannel.subscribe(handleRequestError) + headersChannel.subscribe(handleRequestHeaders) + + function handleRequestCreate ({ request }) { + const method = (request.method || 'GET').toUpperCase() + + const scope = tracer.scope() + const childOf = scope.active() + const path = request.path ? request.path.split(/[?#]/)[0] : '/' + const uri = `${request.origin}${path}` + const type = config.filter(uri) ? REFERENCE_CHILD_OF : REFERENCE_NOOP + + const span = tracer.startSpan('http.request', { + references: [new Reference(type, childOf)], + tags: { + [SPAN_KIND]: CLIENT, + 'resource.name': method, + 'span.type': 'http', + 'http.method': method, + 'http.url': uri, + 'service.name': getServiceName(tracer, config, request) + } }) - return error - } + requestSpansMap.set(request, span) - function addRequestHeaders (req, span, config) { - config.headers.forEach(key => { - const value = req.headers[key] + if (!(hasAmazonSignature(request) || !config.propagationFilter(uri))) { + const injectedHeaders = {} + tracer.inject(span, HTTP_HEADERS, injectedHeaders) + Object.entries(injectedHeaders).forEach(([key, value]) => { + request.addHeader(key, value) + }) + } - if (value) { - span.setTag(`${HTTP_REQUEST_HEADERS}.${key}`, value) - } - }) + analyticsSampler.sample(span, config.measured) } - function addResponseHeaders (res, span, config) { - config.headers.forEach(key => { - const value = res.headers[key] - if (value) { - span.setTag(`${HTTP_RESPONSE_HEADERS}.${key}`, value) - } - }) + function handleRequestError ({ request, error }) { + const span = requestSpansMap.get(request) + addError(span, error) + finish(request, null, span, config) } - function normalizeArgs (inputURL, inputOptions, cb) { - inputURL = normalizeOptions(inputURL) - - const [callback, inputOptionsNormalized] = normalizeCallback(inputOptions, cb, inputURL) - const options = combineOptions(inputURL, inputOptionsNormalized) - normalizeHeaders(options) - const uri = url.format(options) - - return { uri, options, callback } + function handleRequestHeaders ({ request, response }) { + const span = requestSpansMap.get(request) + finish(request, response, span, config) } - function normalizeCallback (inputOptions, callback, inputURL) { - if (typeof inputOptions === 'function') { - return [inputOptions, inputURL || {}] - } else { - return [callback, inputOptions] + return function unsubscribe () { + if (requestChannel.hasSubscribers) { + requestChannel.unsubscribe(handleRequestCreate) + } + if (headersChannel.hasSubscribers) { + headersChannel.unsubscribe(handleRequestHeaders) + } + if (requestErrorChannel.hasSubscribers) { + requestErrorChannel.unsubscribe(handleRequestError) } } +} + +function addRequestHeaders (req, span, config) { + const headers = parseHeaders(req.headers) + Object.entries(headers).forEach(([key, value]) => { + span.setTag(`${HTTP_REQUEST_HEADERS}.${key}`, value) + }) - function combineOptions (inputURL, inputOptions) { - if (typeof inputOptions === 'object') { - return Object.assign(inputURL || {}, inputOptions) + if (!headers.host) { + // req.servername holds the value of the host header + if (req.servername) { + span.setTag(`${HTTP_REQUEST_HEADERS}.host`, req.servername) } else { - return inputURL + // Undici's host header are written directly + // to the stream, and not passed to the `Request` object + // This workaround ensure we set the host if + // it was not explicitely provided + const { hostname, port } = url.parse(req.origin) + const host = `${hostname}${port ? `:${port}` : ''}` + span.setTag(`${HTTP_REQUEST_HEADERS}.host`, host) } } +} - function normalizeHeaders (options) { - options.headers = options.headers || {} +function addResponseHeaders (res, span, config) { + const resHeader = res.headers.map((x) => x.toString()) + while (resHeader.length) { + const key = resHeader.shift() + const value = resHeader.shift() + span.setTag(`${HTTP_RESPONSE_HEADERS}.${key}`, value) } +} - // https://github.com/nodejs/node/blob/7e911d8b03a838e5ac6bb06c5b313533e89673ef/lib/internal/url.js#L1271 - function urlToOptions (url) { - const agent = url.agent || http.globalAgent - const options = { - protocol: url.protocol || agent.protocol, - hostname: typeof url.hostname === 'string' && url.hostname.startsWith('[') - ? url.hostname.slice(1, -1) - : url.hostname || - url.host || - 'localhost', - hash: url.hash, - search: url.search, - pathname: url.pathname, - path: `${url.pathname || ''}${url.search || ''}`, - href: url.href - } - if (url.port !== '') { - options.port = Number(url.port) - } - if (url.username || url.password) { - options.auth = `${url.username}:${url.password}` - } - return options - } +function finish (req, res, span, config) { + if (res) { + span.setTag(HTTP_STATUS_CODE, res.statusCode) - function normalizeOptions (inputURL) { - if (typeof inputURL === 'string') { - try { - return urlToOptions(new url.URL(inputURL)) - } catch (e) { - return url.parse(inputURL) - } - } else if (inputURL instanceof url.URL) { - return urlToOptions(inputURL) - } else { - return inputURL + if (!config.validateStatus(res.statusCode)) { + span.setTag('error', 1) } + + addResponseHeaders(res, span, config) + } else { + span.setTag('error', 1) } + + addRequestHeaders(req, span, config) + + config.hooks.request(span, req, res) + + span.finish() } function getHost (options) { @@ -260,30 +200,38 @@ function hasAmazonSignature (options) { } if (options.headers) { - const headers = Object.keys(options.headers) - .reduce((prev, next) => Object.assign(prev, { - [next.toLowerCase()]: options.headers[next] - }), {}) + let headers = {} + if (typeof options.headers === 'string') { + headers = parseHeaders(options.headers) + } else { + headers = Object.keys(options.headers).reduce( + (prev, next) => + Object.assign(prev, { + [next.toLowerCase()]: options.headers[next] + }), + {} + ) + } if (headers['x-amz-signature']) { return true } - if ([].concat(headers['authorization']).some(startsWith('AWS4-HMAC-SHA256'))) { + if ( + [].concat(headers['authorization']).some(startsWith('AWS4-HMAC-SHA256')) + ) { return true } } - return options.path && options.path.toLowerCase().indexOf('x-amz-signature=') !== -1 + return ( + options.path && + options.path.toLowerCase().indexOf('x-amz-signature=') !== -1 + ) } function startsWith (searchString) { - return value => String(value).startsWith(searchString) -} - -function unpatch (http) { - this.unwrap(http, 'request') - this.unwrap(http, 'get') + return (value) => String(value).startsWith(searchString) } function getStatusValidator (config) { @@ -292,7 +240,7 @@ function getStatusValidator (config) { } else if (config.hasOwnProperty('validateStatus')) { log.error('Expected `validateStatus` to be a function.') } - return code => code < 400 || code >= 500 + return (code) => code < 400 || code >= 500 } function getFilter (tracer, config) { @@ -315,7 +263,9 @@ function getAgentFilter (url) { function normalizeConfig (tracer, config) { const validateStatus = getStatusValidator(config) const filter = getFilter(tracer, config) - const propagationFilter = getFilter(tracer, { blocklist: config.propagationBlocklist }) + const propagationFilter = getFilter(tracer, { + blocklist: config.propagationBlocklist + }) const headers = getHeaders(config) const hooks = getHooks(config) @@ -332,8 +282,8 @@ function getHeaders (config) { if (!Array.isArray(config.headers)) return [] return config.headers - .filter(key => typeof key === 'string') - .map(key => key.toLowerCase()) + .filter((key) => typeof key === 'string') + .map((key) => key.toLowerCase()) } function getHooks (config) { @@ -346,11 +296,9 @@ function getHooks (config) { module.exports = [ { name: 'undici', - versions: ['>=4'], - file: 'lib/api/index.js', - patch: function (undici, tracer, config) { - patch.call(this, undici, 'request', tracer, config) - }, - unpatch + versions: ['>=4.7.1-alpha'], + patch: function (_, tracer, config) { + this.unpatch = diagnostics.call(this, tracer, config) + } } ] diff --git a/packages/datadog-plugin-undici/test/index.spec.js b/packages/datadog-plugin-undici/test/index.spec.js index 267ac87b844..e56d4ded607 100644 --- a/packages/datadog-plugin-undici/test/index.spec.js +++ b/packages/datadog-plugin-undici/test/index.spec.js @@ -125,61 +125,6 @@ describe('undici', () => { }) }) - // TODO: is this a thing in undici? - it.skip('should support a string URL and an options object, which merges and takes precedence', done => { - const app = express() - - app.get('/user', (req, res) => { - res.status(200).send() - }) - - getPort().then(port => { - agent - .use(traces => { - expect(traces[0][0].meta).to.have.property('http.status_code', '200') - expect(traces[0][0].meta).to.have.property('http.url', `http://localhost:${port}/user`) - }) - .then(done) - .catch(done) - - appListener = server(app, port, () => { - const req = undici.request(`http://localhost:${port}/another-path`, { path: '/user' }) - - }) - }) - }) - - // TODO: is that valid undici? - it.skip('should support a URL object and an options object, which merges and takes precedence', done => { - const app = express() - - app.get('/user', (req, res) => { - res.status(200).send() - }) - - getPort().then(port => { - agent - .use(traces => { - expect(traces[0][0].meta).to.have.property('http.status_code', '200') - expect(traces[0][0].meta).to.have.property('http.url', `http://localhost:${port}/user`) - }) - .then(done) - .catch(done) - - const uri = { - protocol: `http:`, - hostname: 'localhost', - port, - pathname: '/another-path' - } - - appListener = server(app, port, () => { - const req = undici.request(uri, { path: '/user' }) - - }) - }) - }) - it('should support configuration as an WHATWG URL object', async () => { const app = express() const port = await getPort() @@ -197,31 +142,6 @@ describe('undici', () => { }) }) - // TODO: is this a valid undici request? - it.skip('should use the correct defaults when not specified', done => { - const app = express() - - app.get('/user', (req, res) => { - res.status(200).send() - }) - - getPort().then(port => { - agent - .use(traces => { - expect(traces[0][0].meta).to.have.property('http.url', `http://localhost:${port}/`) - }) - .then(done) - .catch(done) - - appListener = server(app, port, () => { - const req = undici.request({ - protocol: `http:`, - port - }) - }) - }) - }) - it('should not require consuming the data', done => { const app = express() @@ -430,7 +350,6 @@ describe('undici', () => { .catch(done) undici.request(`http://localhost:${port}/user`, (err) => { - // console.error(err) error = err }) }) @@ -478,8 +397,7 @@ describe('undici', () => { }) }) - // TODO: how to destroy requests with undici - it.skip('should record destroyed requests as errors', done => { + it('should record destroyed requests as errors', done => { const app = express() app.get('/user', (req, res) => {}) @@ -583,8 +501,7 @@ describe('undici', () => { }) }) - // TODO: Is that a real thing? - it.skip('should use the correct fallback protocol', done => { + it('should skip requests to the agent', done => { const app = express() app.get('/user', (req, res) => { @@ -592,47 +509,19 @@ describe('undici', () => { }) getPort().then(port => { + const timer = setTimeout(done, 100) + agent - .use(traces => { - expect(traces[0][0].meta).to.have.property('http.status_code', '200') - expect(traces[0][0].meta).to.have.property('http.url', `http://localhost:${port}/user`) + .use(() => { + done(new Error('Request to the agent was traced.')) + clearTimeout(timer) }) - .then(done) - .catch(done) appListener = server(app, port, () => { - const req = undici.request({ - hostname: 'localhost', - port, - path: '/user?foo=bar' - }, res => { - res.on('data', () => {}) - }) + undici.request(tracer._tracer._url.href) }) }) }) - - it('should skip requests to the agent', done => { - const app = express() - - app.get('/user', (req, res) => { - res.status(200).send() - }) - - getPort().then(port => { - const timer = setTimeout(done, 100) - - agent - .use(() => { - done(new Error('Request to the agent was traced.')) - clearTimeout(timer) - }) - - appListener = server(app, port, () => { - undici.request(tracer._tracer._url.href) - }) - }) - }) }) describe('with service configuration', () => { @@ -759,26 +648,86 @@ describe('undici', () => { }) }) - // TODO: Injected headers are not available yet - it.skip('should add tags for the host header', done => { - const app = express() - app.get('/user', (req, res) => { - res.setHeader('x-foo', 'bar') - res.status(200).send() + describe('host header', () => { + + // TODO: Injected headers are not available yet + // for request + it('should add tags for the host header', done => { + const app = express() + + app.get('/user', (req, res) => { + res.setHeader('x-foo', 'bar') + res.status(200).send() + }) + + getPort().then(port => { + agent + .use(traces => { + const meta = traces[0][0].meta + expect(meta).to.have.property(`${HTTP_REQUEST_HEADERS}.host`, `localhost:${port}`) + }) + .then(done) + .catch(done) + + appListener = server(app, port, () => { + undici.request(`http://localhost:${port}/user`) + }) + }) }) - getPort().then(port => { - agent - .use(traces => { - const meta = traces[0][0].meta - expect(meta).to.have.property(`${HTTP_REQUEST_HEADERS}.host`, `localhost:${port}`) + it('should add tags for the host header through Client', done => { + const app = express() + + app.get('/user', (req, res) => { + res.setHeader('x-foo', 'bar') + res.status(200).send() + }) + + getPort().then(port => { + agent + .use(traces => { + const meta = traces[0][0].meta + expect(meta).to.have.property(`${HTTP_REQUEST_HEADERS}.host`, `localhost:${port}`) + }) + .then(done) + .catch(done) + + appListener = server(app, port, () => { + const client = new undici.Client(`http://localhost:${port}`) + + client.request({ + path: '/user', + method: 'GET' + }) }) - .then(done) - .catch(done) + }) + }) - appListener = server(app, port, () => { - undici.request(`http://localhost:${port}/user`) + it('should pass overwritten host header', done => { + const app = express() + + app.get('/user', (req, res) => { + res.setHeader('x-foo', 'bar') + res.status(200).send() + }) + + getPort().then(port => { + agent + .use(traces => { + const meta = traces[0][0].meta + expect(meta).to.have.property(`${HTTP_REQUEST_HEADERS}.host`, `my-service`) + }) + .then(done) + .catch(done) + + appListener = server(app, port, () => { + undici.request(`http://localhost:${port}/user`, { + headers: { + host: 'my-service', + } + }) + }) }) }) }) @@ -838,7 +787,7 @@ describe('undici', () => { config = { hooks: { request: (span, req, res) => { - span.setTag('resource.name', `${req.method} ${req._route}`) + span.setTag('resource.name', `${req.method} -- ${req.path}`) } } } @@ -849,8 +798,7 @@ describe('undici', () => { }) }) - // TODO: how does it translates? - it.skip('should run the request hook before the span is finished', done => { + it('should run the request hook before the span is finished', done => { const app = express() app.get('/user', (req, res) => { @@ -860,16 +808,13 @@ describe('undici', () => { getPort().then(port => { agent .use(traces => { - expect(traces[0][0]).to.have.property('resource', 'GET /user') + expect(traces[0][0]).to.have.property('resource', 'GET -- /user') }) .then(done) .catch(done) appListener = server(app, port, () => { - const req = undici.request(`http://localhost:${port}/user`) - - req._route = '/user' - + undici.request(`http://localhost:${port}/user`) }) }) }) From 85d1b89450c607e3e3d4ec79df8efe9f9f0c0cfd Mon Sep 17 00:00:00 2001 From: Florent Vilmart Date: Tue, 5 Oct 2021 16:15:33 -0400 Subject: [PATCH 04/22] TS + docs --- docs/API.md | 2 + docs/test.ts | 372 +++++++++++++++++++++++++--------------------- index.d.ts | 412 ++++++++++++++++++++++++++++++++------------------- 3 files changed, 469 insertions(+), 317 deletions(-) diff --git a/docs/API.md b/docs/API.md index cc3683d34d2..59146dcae9a 100644 --- a/docs/API.md +++ b/docs/API.md @@ -90,6 +90,7 @@ tracer.use('pg', {
+

Available Plugins

@@ -138,6 +139,7 @@ tracer.use('pg', { * [restify](./interfaces/plugins.restify.html) * [router](./interfaces/plugins.router.html) * [tedious](./interfaces/plugins.tedious.html) +* [undici](./interfaces/plugins.undici.html) * [when](./interfaces/plugins.when.html) * [winston](./interfaces/plugins.winston.html) diff --git a/docs/test.ts b/docs/test.ts index 53aa209ede0..e6293a033e1 100644 --- a/docs/test.ts +++ b/docs/test.ts @@ -1,8 +1,21 @@ -import ddTrace, { tracer, Tracer, TracerOptions, Span, SpanContext, SpanOptions, Scope } from '..'; -import { formats, kinds, priority, tags, types } from '../ext'; -import { BINARY, HTTP_HEADERS, LOG, TEXT_MAP } from '../ext/formats'; -import { SERVER, CLIENT, PRODUCER, CONSUMER } from '../ext/kinds' -import { USER_REJECT, AUTO_REJECT, AUTO_KEEP, USER_KEEP } from '../ext/priority' +import ddTrace, { + tracer, + Tracer, + TracerOptions, + Span, + SpanContext, + SpanOptions, + Scope, +} from ".."; +import { formats, kinds, priority, tags, types } from "../ext"; +import { BINARY, HTTP_HEADERS, LOG, TEXT_MAP } from "../ext/formats"; +import { SERVER, CLIENT, PRODUCER, CONSUMER } from "../ext/kinds"; +import { + USER_REJECT, + AUTO_REJECT, + AUTO_KEEP, + USER_KEEP, +} from "../ext/priority"; import { ERROR, HTTP_METHOD, @@ -18,9 +31,9 @@ import { SERVICE_NAME, SPAN_KIND, SPAN_TYPE, -} from '../ext/tags' -import { HTTP, WEB } from '../ext/types' -import * as opentracing from 'opentracing'; +} from "../ext/tags"; +import { HTTP, WEB } from "../ext/types"; +import * as opentracing from "opentracing"; opentracing.initGlobalTracer(tracer); @@ -36,87 +49,87 @@ tracer.init({ enabled: true, logInjection: true, startupLogs: false, - env: 'test', - version: '1.0.0', - url: 'http://localhost', + env: "test", + version: "1.0.0", + url: "http://localhost", runtimeMetrics: true, ingestion: { sampleRate: 0.5, - rateLimit: 500 + rateLimit: 500, }, experimental: { b3: true, runtimeId: true, - exporter: 'log', + exporter: "log", sampler: { sampleRate: 1, rateLimit: 1000, rules: [ - { sampleRate: 0.5, service: 'foo', name: 'foo.request' }, - { sampleRate: 0.1, service: /foo/, name: /foo\.request/ } - ] - } + { sampleRate: 0.5, service: "foo", name: "foo.request" }, + { sampleRate: 0.1, service: /foo/, name: /foo\.request/ }, + ], + }, }, - hostname: 'agent', + hostname: "agent", logger: { - error (message: string | Error) {}, - warn (message: string) {}, - info (message: string) {}, - debug (message: string) {} + error(message: string | Error) {}, + warn(message: string) {}, + info(message: string) {}, + debug(message: string) {}, }, plugins: false, port: 7777, dogstatsd: { - hostname: 'dsd-agent', - port: 8888 + hostname: "dsd-agent", + port: 8888, }, flushInterval: 1000, lookup: () => {}, sampleRate: 0.1, - service: 'test', + service: "test", tags: { - foo: 'bar' + foo: "bar", }, reportHostname: true, - logLevel: 'debug' + logLevel: "debug", }); const httpOptions = { - service: 'test', - allowlist: ['url', /url/, url => true], - blocklist: ['url', /url/, url => true], - validateStatus: code => code < 400, - headers: ['host'], - middleware: true + service: "test", + allowlist: ["url", /url/, (url) => true], + blocklist: ["url", /url/, (url) => true], + validateStatus: (code) => code < 400, + headers: ["host"], + middleware: true, }; const httpServerOptions = { ...httpOptions, hooks: { - request: (span, req, res) => {} - } + request: (span, req, res) => {}, + }, }; const httpClientOptions = { ...httpOptions, splitByDomain: true, - propagationBlocklist: ['url', /url/, url => true], + propagationBlocklist: ["url", /url/, (url) => true], hooks: { - request: (span, req, res) => {} - } + request: (span, req, res) => {}, + }, }; const http2ServerOptions = { - ...httpOptions + ...httpOptions, }; const http2ClientOptions = { ...httpOptions, - splitByDomain: true + splitByDomain: true, }; const graphqlOptions = { - service: 'test', + service: "test", depth: 2, variables: ({ foo, baz }) => ({ foo }), collapse: false, @@ -124,19 +137,19 @@ const graphqlOptions = { hooks: { execute: (span, args, res) => {}, validate: (span, document, errors) => {}, - parse: (span, source, document) => {} - } + parse: (span, source, document) => {}, + }, }; const elasticsearchOptions = { - service: 'test', + service: "test", hooks: { query: (span, params) => {}, }, }; const awsSdkOptions = { - service: 'test', + service: "test", splitByAwsService: false, hooks: { request: (span, response) => {}, @@ -144,18 +157,18 @@ const awsSdkOptions = { s3: false, sqs: { consumer: true, - producer: false - } + producer: false, + }, }; const redisOptions = { - service: 'test', - allowlist: ['info', /auth/i, command => true], - blocklist: ['info', /auth/i, command => true], + service: "test", + allowlist: ["info", /auth/i, (command) => true], + blocklist: ["info", /auth/i, (command) => true], }; const sharedbOptions = { - service: 'test', + service: "test", hooks: { receive: (span, request) => {}, reply: (span, request, reply) => {}, @@ -163,127 +176,135 @@ const sharedbOptions = { }; const moleculerOptions = { - service: 'test', + service: "test", client: false, params: true, server: { - meta: true - } -} - -tracer.use('amqp10'); -tracer.use('amqplib'); -tracer.use('aws-sdk', awsSdkOptions); -tracer.use('bunyan'); -tracer.use('couchbase'); -tracer.use('cassandra-driver'); -tracer.use('connect'); -tracer.use('connect', httpServerOptions); -tracer.use('cypress'); -tracer.use('cucumber') -tracer.use('cucumber', { service: 'cucumber-service' }); -tracer.use('dns'); -tracer.use('elasticsearch', elasticsearchOptions); -tracer.use('express'); -tracer.use('express', httpServerOptions); -tracer.use('fastify'); -tracer.use('fastify', httpServerOptions); -tracer.use('fs'); -tracer.use('generic-pool'); -tracer.use('google-cloud-pubsub'); -tracer.use('graphql', graphqlOptions); -tracer.use('graphql', { variables: ['foo', 'bar'] }); -tracer.use('grpc'); -tracer.use('grpc', { metadata: ['foo', 'bar'] }); -tracer.use('grpc', { metadata: meta => meta }); -tracer.use('grpc', { client: { metadata: [] } }); -tracer.use('grpc', { server: { metadata: [] } }); -tracer.use('hapi'); -tracer.use('hapi', httpServerOptions); -tracer.use('http'); -tracer.use('http', { - server: httpServerOptions + meta: true, + }, +}; + +tracer.use("amqp10"); +tracer.use("amqplib"); +tracer.use("aws-sdk", awsSdkOptions); +tracer.use("bunyan"); +tracer.use("couchbase"); +tracer.use("cassandra-driver"); +tracer.use("connect"); +tracer.use("connect", httpServerOptions); +tracer.use("cypress"); +tracer.use("cucumber"); +tracer.use("cucumber", { service: "cucumber-service" }); +tracer.use("dns"); +tracer.use("elasticsearch", elasticsearchOptions); +tracer.use("express"); +tracer.use("express", httpServerOptions); +tracer.use("fastify"); +tracer.use("fastify", httpServerOptions); +tracer.use("fs"); +tracer.use("generic-pool"); +tracer.use("google-cloud-pubsub"); +tracer.use("graphql", graphqlOptions); +tracer.use("graphql", { variables: ["foo", "bar"] }); +tracer.use("grpc"); +tracer.use("grpc", { metadata: ["foo", "bar"] }); +tracer.use("grpc", { metadata: (meta) => meta }); +tracer.use("grpc", { client: { metadata: [] } }); +tracer.use("grpc", { server: { metadata: [] } }); +tracer.use("hapi"); +tracer.use("hapi", httpServerOptions); +tracer.use("http"); +tracer.use("http", { + server: httpServerOptions, }); -tracer.use('http', { - client: httpClientOptions +tracer.use("http", { + client: httpClientOptions, }); -tracer.use('http2'); -tracer.use('http2', { - server: http2ServerOptions +tracer.use("http2"); +tracer.use("http2", { + server: http2ServerOptions, }); -tracer.use('http2', { - client: http2ClientOptions +tracer.use("http2", { + client: http2ClientOptions, }); -tracer.use('ioredis'); -tracer.use('ioredis', redisOptions); -tracer.use('ioredis', { splitByInstance: true }); -tracer.use('jest'); -tracer.use('jest', { service: 'jest-service' }); -tracer.use('kafkajs'); -tracer.use('knex'); -tracer.use('koa'); -tracer.use('koa', httpServerOptions); -tracer.use('limitd-client'); -tracer.use('memcached'); -tracer.use('microgateway-core', httpServerOptions); -tracer.use('mocha'); -tracer.use('mocha', { service: 'mocha-service' }); -tracer.use('moleculer', moleculerOptions); -tracer.use('mongodb-core'); -tracer.use('mongoose'); -tracer.use('mysql'); -tracer.use('mysql2'); -tracer.use('net'); -tracer.use('next'); -tracer.use('oracledb'); -tracer.use('oracledb', { service: params => `${params.host}-${params.database}` }); -tracer.use('paperplane'); -tracer.use('paperplane', httpServerOptions); -tracer.use('pg'); -tracer.use('pg', { service: params => `${params.host}-${params.database}` }); -tracer.use('pino'); -tracer.use('redis'); -tracer.use('redis', redisOptions); -tracer.use('restify'); -tracer.use('restify', httpServerOptions); -tracer.use('rhea'); -tracer.use('router'); -tracer.use('sharedb', sharedbOptions); -tracer.use('tedious'); -tracer.use('winston'); - -tracer.use('express', false) -tracer.use('express', { enabled: false }) -tracer.use('express', { service: 'name' }); -tracer.use('express', { measured: true }); - -span = tracer.startSpan('test'); -span = tracer.startSpan('test', {}); -span = tracer.startSpan('test', { +tracer.use("ioredis"); +tracer.use("ioredis", redisOptions); +tracer.use("ioredis", { splitByInstance: true }); +tracer.use("jest"); +tracer.use("jest", { service: "jest-service" }); +tracer.use("kafkajs"); +tracer.use("knex"); +tracer.use("koa"); +tracer.use("koa", httpServerOptions); +tracer.use("limitd-client"); +tracer.use("memcached"); +tracer.use("microgateway-core", httpServerOptions); +tracer.use("mocha"); +tracer.use("mocha", { service: "mocha-service" }); +tracer.use("moleculer", moleculerOptions); +tracer.use("mongodb-core"); +tracer.use("mongoose"); +tracer.use("mysql"); +tracer.use("mysql2"); +tracer.use("net"); +tracer.use("next"); +tracer.use("oracledb"); +tracer.use("oracledb", { + service: (params) => `${params.host}-${params.database}`, +}); +tracer.use("paperplane"); +tracer.use("paperplane", httpServerOptions); +tracer.use("pg"); +tracer.use("pg", { service: (params) => `${params.host}-${params.database}` }); +tracer.use("pino"); +tracer.use("redis"); +tracer.use("redis", redisOptions); +tracer.use("restify"); +tracer.use("restify", httpServerOptions); +tracer.use("rhea"); +tracer.use("router"); +tracer.use("sharedb", sharedbOptions); +tracer.use("tedious"); +tracer.use("undici", httpClientOptions); +tracer.use("when"); +tracer.use("winston"); + +tracer.use("express", false); +tracer.use("express", { enabled: false }); +tracer.use("express", { service: "name" }); +tracer.use("express", { measured: true }); + +span = tracer.startSpan("test"); +span = tracer.startSpan("test", {}); +span = tracer.startSpan("test", { childOf: span || span.context(), references: [], startTime: 123456789.1234, tags: { - foo: 'bar' - } + foo: "bar", + }, }); -tracer.trace('test', () => {}) -tracer.trace('test', { tags: { foo: 'bar' }}, () => {}) -tracer.trace('test', { service: 'foo', resource: 'bar', type: 'baz' }, () => {}) -tracer.trace('test', { measured: true }, () => {}) -tracer.trace('test', (span: Span) => {}) -tracer.trace('test', (span: Span, fn: () => void) => {}) -tracer.trace('test', (span: Span, fn: (err: Error) => string) => {}) +tracer.trace("test", () => {}); +tracer.trace("test", { tags: { foo: "bar" } }, () => {}); +tracer.trace( + "test", + { service: "foo", resource: "bar", type: "baz" }, + () => {} +); +tracer.trace("test", { measured: true }, () => {}); +tracer.trace("test", (span: Span) => {}); +tracer.trace("test", (span: Span, fn: () => void) => {}); +tracer.trace("test", (span: Span, fn: (err: Error) => string) => {}); -promise = tracer.trace('test', () => Promise.resolve()) +promise = tracer.trace("test", () => Promise.resolve()); -tracer.wrap('test', () => {}) -tracer.wrap('test', (foo: string) => 'test') +tracer.wrap("test", () => {}); +tracer.wrap("test", (foo: string) => "test"); -promise = tracer.wrap('test', () => Promise.resolve())() +promise = tracer.wrap("test", () => Promise.resolve())(); -const carrier = {} +const carrier = {}; tracer.inject(span || span.context(), HTTP_HEADERS, carrier); context = tracer.extract(HTTP_HEADERS, carrier); @@ -291,16 +312,25 @@ context = tracer.extract(HTTP_HEADERS, carrier); traceId = context.toTraceId(); spanId = context.toSpanId(); -const scope = tracer.scope() +const scope = tracer.scope(); span = scope.active(); -const activateStringType: string = scope.activate(span, () => 'test'); +const activateStringType: string = scope.activate(span, () => "test"); const activateVoidType: void = scope.activate(span, () => {}); -const bindFunctionStringType: (arg1: string, arg2: number) => string = scope.bind((arg1: string, arg2: number): string => 'test'); -const bindFunctionVoidType: (arg1: string, arg2: number) => void = scope.bind((arg1: string, arg2: number): void => {}); -const bindFunctionVoidTypeWithSpan: (arg1: string, arg2: number) => void = scope.bind((arg1: string, arg2: number): string => 'test', span); +const bindFunctionStringType: ( + arg1: string, + arg2: number +) => string = scope.bind((arg1: string, arg2: number): string => "test"); +const bindFunctionVoidType: ( + arg1: string, + arg2: number +) => void = scope.bind((arg1: string, arg2: number): void => {}); +const bindFunctionVoidTypeWithSpan: ( + arg1: string, + arg2: number +) => void = scope.bind((arg1: string, arg2: number): string => "test", span); Promise.resolve(); @@ -308,23 +338,29 @@ scope.bind(promise); scope.bind(promise, span); const simpleEmitter = { - emit (eventName: string, arg1: boolean, arg2: number): void {} + emit(eventName: string, arg1: boolean, arg2: number): void {}, }; scope.bind(simpleEmitter); scope.bind(simpleEmitter, span); const emitter = { - emit (eventName: string, arg1: boolean, arg2: number): void {}, - on (eventName: string, listener: (arg1: boolean, arg2: number) => void) {}, - off (eventName: string, listener: (arg1: boolean, arg2: number) => void) {}, - addListener (eventName: string, listener: (arg1: boolean, arg2: number) => void) {}, - removeListener (eventName: string, listener: (arg1: boolean, arg2: number) => void) {} + emit(eventName: string, arg1: boolean, arg2: number): void {}, + on(eventName: string, listener: (arg1: boolean, arg2: number) => void) {}, + off(eventName: string, listener: (arg1: boolean, arg2: number) => void) {}, + addListener( + eventName: string, + listener: (arg1: boolean, arg2: number) => void + ) {}, + removeListener( + eventName: string, + listener: (arg1: boolean, arg2: number) => void + ) {}, }; scope.bind(emitter); scope.bind(emitter, span); -tracer.wrap('x', () => { +tracer.wrap("x", () => { const rumData: string = tracer.getRumData(); -}) +}); diff --git a/index.d.ts b/index.d.ts index 38d2bab0334..00fd25dcb3b 100644 --- a/index.d.ts +++ b/index.d.ts @@ -1,5 +1,5 @@ import { ClientRequest, IncomingMessage, ServerResponse } from "http"; -import { LookupFunction } from 'net'; +import { LookupFunction } from "net"; import * as opentracing from "opentracing"; import { SpanOptions } from "opentracing/lib/tracer"; @@ -81,8 +81,15 @@ export declare interface Tracer extends opentracing.Tracer { * If the `orphanable` option is set to false, the function will not be traced * unless there is already an active span or `childOf` option. */ - trace(name: string, fn: (span?: Span, fn?: (error?: Error) => any) => T): T; - trace(name: string, options: TraceOptions & SpanOptions, fn: (span?: Span, done?: (error?: Error) => string) => T): T; + trace( + name: string, + fn: (span?: Span, fn?: (error?: Error) => any) => T + ): T; + trace( + name: string, + options: TraceOptions & SpanOptions, + fn: (span?: Span, done?: (error?: Error) => string) => T + ): T; /** * Wrap a function to automatically create a span activated on its @@ -98,9 +105,21 @@ export declare interface Tracer extends opentracing.Tracer { * * The function doesn't accept a callback and doesn't return a promise, in * which case the span will finish at the end of the function execution. */ - wrap any>(name: string, fn: T, requiresParent?: boolean): T; - wrap any>(name: string, options: TraceOptions & SpanOptions, fn: T): T; - wrap any>(name: string, options: (...args: any[]) => TraceOptions & SpanOptions, fn: T): T; + wrap any>( + name: string, + fn: T, + requiresParent?: boolean + ): T; + wrap any>( + name: string, + options: TraceOptions & SpanOptions, + fn: T + ): T; + wrap any>( + name: string, + options: (...args: any[]) => TraceOptions & SpanOptions, + fn: T + ): T; /** * Create and return a string that can be included in the of a @@ -115,18 +134,18 @@ export declare interface TraceOptions extends Analyzable { * The resource you are tracing. The resource name must not be longer than * 5000 characters. */ - resource?: string, + resource?: string; /** * The service you are tracing. The service name must not be longer than * 100 characters. */ - service?: string, + service?: string; /** * The type of request. */ - type?: string + type?: string; } /** @@ -169,17 +188,17 @@ export declare interface SamplingRule { /** * Sampling rate for this rule. */ - sampleRate: Number + sampleRate: Number; /** * Service on which to apply this rule. The rule will apply to all services if not provided. */ - service?: string | RegExp + service?: string | RegExp; /** * Operation name on which to apply this rule. The rule will apply to all operation names if not provided. */ - name?: string | RegExp + name?: string | RegExp; } /** @@ -203,13 +222,13 @@ export declare interface TracerOptions { * traces with logs. * @default false */ - logInjection?: boolean, + logInjection?: boolean; /** * Whether to enable startup logs. * @default true */ - startupLogs?: boolean, + startupLogs?: boolean; /** * The service name to be used for this program. If not set, the service name @@ -238,7 +257,7 @@ export declare interface TracerOptions { /** * Whether to enable profiling. */ - profiling?: boolean + profiling?: boolean; /** * Options specific for the Dogstatsd agent. @@ -247,13 +266,13 @@ export declare interface TracerOptions { /** * The hostname of the Dogstatsd agent that the metrics will submitted to. */ - hostname?: string + hostname?: string; /** * The port of the Dogstatsd agent that the metrics will submitted to. * @default 8125 */ - port?: number + port?: number; }; /** @@ -282,19 +301,19 @@ export declare interface TracerOptions { * Whether to enable runtime metrics. * @default false */ - runtimeMetrics?: boolean + runtimeMetrics?: boolean; /** * Custom function for DNS lookups when sending requests to the agent. * @default dns.lookup() */ - lookup?: LookupFunction + lookup?: LookupFunction; /** * Protocol version to use for requests to the agent. The version configured must be supported by the agent version installed or all traces will be dropped. * @default 0.4 */ - protocolVersion?: string + protocolVersion?: string; /** * Configuration of the ingestion between the agent and the backend. @@ -303,61 +322,63 @@ export declare interface TracerOptions { /** * Controls the ingestion sample rate (between 0 and 1) between the agent and the backend. */ - sampleRate?: number + sampleRate?: number; /** * Controls the ingestion rate limit between the agent and the backend. */ - rateLimit?: number + rateLimit?: number; }; /** * Experimental features can be enabled all at once by using true or individually using key / value pairs. * @default {} */ - experimental?: boolean | { - b3?: boolean - - /** - * Whether to add an auto-generated `runtime-id` tag to metrics. - * @default false - */ - runtimeId?: boolean - - /** - * Whether to write traces to log output, rather than send to an agent - * @default false - */ - exporter?: 'log' | 'agent' - - /** - * Configuration of the priority sampler. Supports a global config and rules by span name or service name. The first matching rule is applied, and if no rule matches it falls back to the global config or on the rates provided by the agent if there is no global config. - */ - sampler?: { - /** - * Sample rate to apply globally when no other rule is matched. Omit to fallback on the dynamic rates returned by the agent instead. - */ - sampleRate?: Number, - - /** - * Global rate limit that is applied on the global sample rate and all rules. - * @default 100 - */ - rateLimit?: Number, - - /** - * Sampling rules to apply to priority sampling. - * @default [] - */ - rules?: SamplingRule[] - } - - /** - * Whether to enable the experimental `getRumData` method. - * @default false - */ - enableGetRumData?: boolean - }; + experimental?: + | boolean + | { + b3?: boolean; + + /** + * Whether to add an auto-generated `runtime-id` tag to metrics. + * @default false + */ + runtimeId?: boolean; + + /** + * Whether to write traces to log output, rather than send to an agent + * @default false + */ + exporter?: "log" | "agent"; + + /** + * Configuration of the priority sampler. Supports a global config and rules by span name or service name. The first matching rule is applied, and if no rule matches it falls back to the global config or on the rates provided by the agent if there is no global config. + */ + sampler?: { + /** + * Sample rate to apply globally when no other rule is matched. Omit to fallback on the dynamic rates returned by the agent instead. + */ + sampleRate?: Number; + + /** + * Global rate limit that is applied on the global sample rate and all rules. + * @default 100 + */ + rateLimit?: Number; + + /** + * Sampling rules to apply to priority sampling. + * @default [] + */ + rules?: SamplingRule[]; + }; + + /** + * Whether to enable the experimental `getRumData` method. + * @default false + */ + enableGetRumData?: boolean; + }; /** * Whether to load all built-in plugins. @@ -387,25 +408,30 @@ export declare interface TracerOptions { * implementation for the runtime. Only change this if you know what you are * doing. */ - scope?: 'async_hooks' | 'async_local_storage' | 'async_resource' | 'sync' | 'noop' + scope?: + | "async_hooks" + | "async_local_storage" + | "async_resource" + | "sync" + | "noop"; /** * Whether to report the hostname of the service host. This is used when the agent is deployed on a different host and cannot determine the hostname automatically. * @default false */ - reportHostname?: boolean + reportHostname?: boolean; /** * A string representing the minimum tracer log level to use when debug logging is enabled * @default 'debug' */ - logLevel?: 'error' | 'debug' + logLevel?: "error" | "debug"; /** * If false, require a parent in order to trace. * @default true */ - orphanable?: boolean + orphanable?: boolean; } /** @hidden */ @@ -413,8 +439,14 @@ interface EventEmitter { emit(eventName: string | symbol, ...args: any[]): any; on?(eventName: string | symbol, listener: (...args: any[]) => any): any; off?(eventName: string | symbol, listener: (...args: any[]) => any): any; - addListener?(eventName: string | symbol, listener: (...args: any[]) => any): any; - removeListener?(eventName: string | symbol, listener: (...args: any[]) => any): any; + addListener?( + eventName: string | symbol, + listener: (...args: any[]) => any + ): any; + removeListener?( + eventName: string | symbol, + listener: (...args: any[]) => any + ): any; } /** @hidden */ @@ -449,7 +481,7 @@ export declare interface Scope { * @param {Function} fn Function that will have the span activated on its scope. * @returns The return value of the provided function. */ - activate(span: Span, fn: ((...args: any[]) => T)): T; + activate(span: Span, fn: (...args: any[]) => T): T; /** * Binds a target to the provided span, or the active span if omitted. @@ -466,54 +498,56 @@ export declare interface Scope { /** @hidden */ interface Plugins { - "amqp10": plugins.amqp10; - "amqplib": plugins.amqplib; + amqp10: plugins.amqp10; + amqplib: plugins.amqplib; "aws-sdk": plugins.aws_sdk; - "bunyan": plugins.bunyan; + bunyan: plugins.bunyan; "cassandra-driver": plugins.cassandra_driver; - "connect": plugins.connect; - "couchbase": plugins.couchbase; - "cucumber": plugins.cucumber; - "cypress": plugins.cypress; - "dns": plugins.dns; - "elasticsearch": plugins.elasticsearch; - "express": plugins.express; - "fastify": plugins.fastify; - "fs": plugins.fs; + connect: plugins.connect; + couchbase: plugins.couchbase; + cucumber: plugins.cucumber; + cypress: plugins.cypress; + dns: plugins.dns; + elasticsearch: plugins.elasticsearch; + express: plugins.express; + fastify: plugins.fastify; + fs: plugins.fs; "generic-pool": plugins.generic_pool; "google-cloud-pubsub": plugins.google_cloud_pubsub; - "graphql": plugins.graphql; - "grpc": plugins.grpc; - "hapi": plugins.hapi; - "http": plugins.http; - "http2": plugins.http2; - "ioredis": plugins.ioredis; - "jest": plugins.jest; - "kafkajs": plugins.kafkajs - "knex": plugins.knex; - "koa": plugins.koa; + graphql: plugins.graphql; + grpc: plugins.grpc; + hapi: plugins.hapi; + http: plugins.http; + http2: plugins.http2; + ioredis: plugins.ioredis; + jest: plugins.jest; + kafkajs: plugins.kafkajs; + knex: plugins.knex; + koa: plugins.koa; "limitd-client": plugins.limitd_client; - "memcached": plugins.memcached; + memcached: plugins.memcached; "microgateway-core": plugins.microgateway_core; - "mocha": plugins.mocha; - "moleculer": plugins.moleculer; + mocha: plugins.mocha; + moleculer: plugins.moleculer; "mongodb-core": plugins.mongodb_core; - "mongoose": plugins.mongoose; - "mysql": plugins.mysql; - "mysql2": plugins.mysql2; - "net": plugins.net; - "next": plugins.next; - "oracledb": plugins.oracledb; - "paperplane": plugins.paperplane; - "pg": plugins.pg; - "pino": plugins.pino; - "redis": plugins.redis; - "restify": plugins.restify; - "rhea": plugins.rhea; - "router": plugins.router; - "sharedb": plugins.sharedb; - "tedious": plugins.tedious; - "winston": plugins.winston; + mongoose: plugins.mongoose; + mysql: plugins.mysql; + mysql2: plugins.mysql2; + net: plugins.net; + next: plugins.next; + oracledb: plugins.oracledb; + paperplane: plugins.paperplane; + pg: plugins.pg; + pino: plugins.pino; + redis: plugins.redis; + restify: plugins.restify; + rhea: plugins.rhea; + router: plugins.router; + sharedb: plugins.sharedb; + tedious: plugins.tedious; + undici: plugins.undici; + when: plugins.when; + winston: plugins.winston; } /** @hidden */ @@ -549,7 +583,11 @@ declare namespace plugins { * * @default /^.*$/ */ - allowlist?: string | RegExp | ((url: string) => boolean) | (string | RegExp | ((url: string) => boolean))[]; + allowlist?: + | string + | RegExp + | ((url: string) => boolean) + | (string | RegExp | ((url: string) => boolean))[]; /** * Deprecated in favor of `allowlist`. @@ -557,7 +595,11 @@ declare namespace plugins { * @deprecated * @hidden */ - whitelist?: string | RegExp | ((url: string) => boolean) | (string | RegExp | ((url: string) => boolean))[]; + whitelist?: + | string + | RegExp + | ((url: string) => boolean) + | (string | RegExp | ((url: string) => boolean))[]; /** * List of URLs that should not be instrumented. Takes precedence over @@ -565,7 +607,11 @@ declare namespace plugins { * * @default [] */ - blocklist?: string | RegExp | ((url: string) => boolean) | (string | RegExp | ((url: string) => boolean))[]; + blocklist?: + | string + | RegExp + | ((url: string) => boolean) + | (string | RegExp | ((url: string) => boolean))[]; /** * Deprecated in favor of `blocklist`. @@ -573,7 +619,11 @@ declare namespace plugins { * @deprecated * @hidden */ - blacklist?: string | RegExp | ((url: string) => boolean) | (string | RegExp | ((url: string) => boolean))[]; + blacklist?: + | string + | RegExp + | ((url: string) => boolean) + | (string | RegExp | ((url: string) => boolean))[]; /** * An array of headers to include in the span metadata. @@ -610,7 +660,11 @@ declare namespace plugins { /** * Hook to execute just before the request span finishes. */ - request?: (span?: opentracing.Span, req?: IncomingMessage, res?: ServerResponse) => any; + request?: ( + span?: opentracing.Span, + req?: IncomingMessage, + res?: ServerResponse + ) => any; }; /** @@ -646,13 +700,21 @@ declare namespace plugins { /** * Hook to execute just before the request span finishes. */ - request?: (span?: opentracing.Span, req?: ClientRequest, res?: IncomingMessage) => any; + request?: ( + span?: opentracing.Span, + req?: ClientRequest, + res?: IncomingMessage + ) => any; }; /** * List of urls to which propagation headers should not be injected */ - propagationBlocklist?: string | RegExp | ((url: string) => boolean) | (string | RegExp | ((url: string) => boolean))[]; + propagationBlocklist?: + | string + | RegExp + | ((url: string) => boolean) + | (string | RegExp | ((url: string) => boolean))[]; } /** @hidden */ @@ -693,7 +755,9 @@ declare namespace plugins { * the key/value pairs to record. For example, using * `variables => variables` would record all variables. */ - metadata?: string[] | ((variables: { [key: string]: any }) => { [key: string]: any }); + metadata?: + | string[] + | ((variables: { [key: string]: any }) => { [key: string]: any }); } /** @hidden */ @@ -841,14 +905,14 @@ declare namespace plugins { /** @hidden */ interface ExecutionArgs { - schema: any, - document: any, - rootValue?: any, - contextValue?: any, - variableValues?: any, - operationName?: string, - fieldResolver?: any, - typeResolver?: any, + schema: any; + document: any; + rootValue?: any; + contextValue?: any; + variableValues?: any; + operationName?: string; + fieldResolver?: any; + typeResolver?: any; } /** @@ -899,7 +963,9 @@ declare namespace plugins { * the key/value pairs to record. For example, using * `variables => variables` would record all variables. */ - variables?: string[] | ((variables: { [key: string]: any }) => { [key: string]: any }); + variables?: + | string[] + | ((variables: { [key: string]: any }) => { [key: string]: any }); /** * Whether to collapse list items into a single element. (i.e. single @@ -928,7 +994,7 @@ declare namespace plugins { execute?: (span?: Span, args?: ExecutionArgs, res?: any) => void; validate?: (span?: Span, document?: any, errors?: any) => void; parse?: (span?: Span, source?: any, document?: any) => void; - } + }; } /** @@ -939,12 +1005,12 @@ declare namespace plugins { /** * Configuration for gRPC clients. */ - client?: Grpc, + client?: Grpc; /** * Configuration for gRPC servers. */ - server?: Grpc + server?: Grpc; } /** @@ -965,12 +1031,12 @@ declare namespace plugins { /** * Configuration for HTTP clients. */ - client?: HttpClient | boolean, + client?: HttpClient | boolean; /** * Configuration for HTTP servers. */ - server?: HttpServer | boolean + server?: HttpServer | boolean; /** * Hooks to run before spans are finished. @@ -999,12 +1065,12 @@ declare namespace plugins { /** * Configuration for HTTP clients. */ - client?: Http2Client | boolean, + client?: Http2Client | boolean; /** * Configuration for HTTP servers. */ - server?: Http2Server | boolean + server?: Http2Server | boolean; } /** @@ -1017,7 +1083,11 @@ declare namespace plugins { * * @default /^.*$/ */ - allowlist?: string | RegExp | ((command: string) => boolean) | (string | RegExp | ((command: string) => boolean))[]; + allowlist?: + | string + | RegExp + | ((command: string) => boolean) + | (string | RegExp | ((command: string) => boolean))[]; /** * Deprecated in favor of `allowlist`. @@ -1025,7 +1095,11 @@ declare namespace plugins { * @deprecated * @hidden */ - whitelist?: string | RegExp | ((command: string) => boolean) | (string | RegExp | ((command: string) => boolean))[]; + whitelist?: + | string + | RegExp + | ((command: string) => boolean) + | (string | RegExp | ((command: string) => boolean))[]; /** * List of commands that should not be instrumented. Takes precedence over @@ -1033,7 +1107,11 @@ declare namespace plugins { * * @default [] */ - blocklist?: string | RegExp | ((command: string) => boolean) | (string | RegExp | ((command: string) => boolean))[]; + blocklist?: + | string + | RegExp + | ((command: string) => boolean) + | (string | RegExp | ((command: string) => boolean))[]; /** * Deprecated in favor of `blocklist`. @@ -1041,7 +1119,11 @@ declare namespace plugins { * @deprecated * @hidden */ - blacklist?: string | RegExp | ((command: string) => boolean) | (string | RegExp | ((command: string) => boolean))[]; + blacklist?: + | string + | RegExp + | ((command: string) => boolean) + | (string | RegExp | ((command: string) => boolean))[]; /** * Whether to use a different service name for each Redis instance based @@ -1104,7 +1186,7 @@ declare namespace plugins { * This plugin automatically instruments the * [moleculer](https://moleculer.services/) module. */ - interface moleculer extends Moleculer { + interface moleculer extends Moleculer { /** * Configuration for Moleculer clients. Set to false to disable client * instrumentation. @@ -1156,11 +1238,15 @@ declare namespace plugins { /** * Hooks to run before spans are finished. */ - hooks?: { + hooks?: { /** * Hook to execute just before the request span finishes. */ - request?: (span?: opentracing.Span, req?: IncomingMessage, res?: ServerResponse) => any; + request?: ( + span?: opentracing.Span, + req?: IncomingMessage, + res?: ServerResponse + ) => any; }; } @@ -1179,7 +1265,7 @@ declare namespace plugins { * This plugin automatically instruments the * [paperplane](https://github.com/articulate/paperplane) module. */ - interface paperplane extends HttpServer {} + interface paperplane extends HttpServer {} /** * This plugin automatically instruments the @@ -1210,7 +1296,11 @@ declare namespace plugins { * * @default /^.*$/ */ - allowlist?: string | RegExp | ((command: string) => boolean) | (string | RegExp | ((command: string) => boolean))[]; + allowlist?: + | string + | RegExp + | ((command: string) => boolean) + | (string | RegExp | ((command: string) => boolean))[]; /** * Deprecated in favor of `allowlist`. @@ -1218,7 +1308,11 @@ declare namespace plugins { * deprecated * @hidden */ - whitelist?: string | RegExp | ((command: string) => boolean) | (string | RegExp | ((command: string) => boolean))[]; + whitelist?: + | string + | RegExp + | ((command: string) => boolean) + | (string | RegExp | ((command: string) => boolean))[]; /** * List of commands that should not be instrumented. Takes precedence over @@ -1226,7 +1320,11 @@ declare namespace plugins { * * @default [] */ - blocklist?: string | RegExp | ((command: string) => boolean) | (string | RegExp | ((command: string) => boolean))[]; + blocklist?: + | string + | RegExp + | ((command: string) => boolean) + | (string | RegExp | ((command: string) => boolean))[]; /** * Deprecated in favor of `blocklist`. @@ -1234,7 +1332,11 @@ declare namespace plugins { * @deprecated * @hidden */ - blacklist?: string | RegExp | ((command: string) => boolean) | (string | RegExp | ((command: string) => boolean))[]; + blacklist?: + | string + | RegExp + | ((command: string) => boolean) + | (string | RegExp | ((command: string) => boolean))[]; } /** @@ -1282,6 +1384,18 @@ declare namespace plugins { */ interface tedious extends Instrumentation {} + /** + * This plugin automatically instruments + * [undici](https://github.com/nodejs/undici/) + */ + interface undici extends HttpClient {} + + /** + * This plugin patches the [when](https://github.com/cujojs/when) + * module to bind the promise callback the the caller context. + */ + interface when extends Integration {} + /** * This plugin patches the [winston](https://github.com/winstonjs/winston) * to automatically inject trace identifiers in log records when the From 2c7d84b779c1a9c9ee5ca9f685ebd25eff08ffb5 Mon Sep 17 00:00:00 2001 From: Florent Vilmart Date: Tue, 5 Oct 2021 16:27:36 -0400 Subject: [PATCH 05/22] lint --- LICENSE-3rdparty.csv | 1 + .../datadog-plugin-undici/test/index.spec.js | 42 ++++++++----------- 2 files changed, 19 insertions(+), 24 deletions(-) diff --git a/LICENSE-3rdparty.csv b/LICENSE-3rdparty.csv index aeeeab13a1f..8179a34ed94 100644 --- a/LICENSE-3rdparty.csv +++ b/LICENSE-3rdparty.csv @@ -24,6 +24,7 @@ require,retry,MIT,Copyright 2011 Tim Koschützki Felix Geisendörfer require,semver,ISC,Copyright Isaac Z. Schlueter and Contributors require,source-map,BSD-3-Clause,Copyright 2009-2011 Mozilla Foundation and contributors require,source-map-resolve,MIT,Copyright 2014-2020 Simon Lydell 2019 Jinxiang +dev,abort-controller,MIT,Copyright (c) 2017 Toru Nagashima dev,autocannon,MIT,Copyright 2016 Matteo Collina dev,axios,MIT,Copyright 2014-present Matt Zabriskie dev,benchmark,MIT,Copyright 2010-2016 Mathias Bynens Robert Kieffer John-David Dalton diff --git a/packages/datadog-plugin-undici/test/index.spec.js b/packages/datadog-plugin-undici/test/index.spec.js index e56d4ded607..6c1c09f6023 100644 --- a/packages/datadog-plugin-undici/test/index.spec.js +++ b/packages/datadog-plugin-undici/test/index.spec.js @@ -3,8 +3,6 @@ const AbortController = require('abort-controller') const getPort = require('get-port') const agent = require('../../dd-trace/test/plugins/agent') -const fs = require('fs') -const path = require('path') const tags = require('../../../ext/tags') const { expect } = require('chai') @@ -133,7 +131,7 @@ describe('undici', () => { app.get('/user', (req, res) => res.status(200).send()) appListener = server(app, port, () => { - const req = undici.request(url) + undici.request(url) }) await agent.use(traces => { @@ -158,8 +156,7 @@ describe('undici', () => { .catch(done) appListener = server(app, port, () => { - const req = undici.request(`http://localhost:${port}/user`) - + undici.request(`http://localhost:${port}/user`) }) }) }) @@ -308,11 +305,10 @@ describe('undici', () => { getPort().then(port => { appListener = server(app, port, () => { - const req = undici.request(`http://localhost:${port}/user`, res => { + undici.request(`http://localhost:${port}/user`, res => { expect(tracer.scope().active()).to.be.null done() }) - }) }) }) @@ -329,7 +325,7 @@ describe('undici', () => { getPort().then(port => { appListener = server(app, port, () => { - const req = undici.request(`http://localhost:${port}/user`, res => { + undici.request(`http://localhost:${port}/user`, res => { done() }) }) @@ -418,13 +414,13 @@ describe('undici', () => { appListener = server(app, port, () => { const client = new undici.Client(`http://localhost:${port}`) - const req = client.request({ + client.request({ path: '/user', method: 'GET' }, (err, data) => { - error = err; + error = err }) - client.destroy(); + client.destroy() }) }) }) @@ -444,8 +440,8 @@ describe('undici', () => { .catch(done) appListener = server(app, port, () => { - const abort = new AbortController(); - undici.request(`http://localhost:${port}/user`,{ + const abort = new AbortController() + undici.request(`http://localhost:${port}/user`, { signal: abort.signal }) abort.abort() @@ -471,7 +467,7 @@ describe('undici', () => { appListener = server(app, port, () => { undici.request(`http://localhost:${port}/user`, { bodyTimeout: 0, - headersTimeout: 0, + headersTimeout: 0 }) }) }) @@ -494,7 +490,7 @@ describe('undici', () => { appListener = server(app, port, () => { const ac = new AbortController() - const req = undici.request(`http://localhost:${port}/abort`, { signal: ac.signal }) + undici.request(`http://localhost:${port}/abort`, { signal: ac.signal }) ac.abort() }) @@ -648,9 +644,7 @@ describe('undici', () => { }) }) - describe('host header', () => { - // TODO: Injected headers are not available yet // for request it('should add tags for the host header', done => { @@ -694,12 +688,12 @@ describe('undici', () => { .catch(done) appListener = server(app, port, () => { - const client = new undici.Client(`http://localhost:${port}`) + const client = new undici.Client(`http://localhost:${port}`) - client.request({ - path: '/user', - method: 'GET' - }) + client.request({ + path: '/user', + method: 'GET' + }) }) }) }) @@ -724,7 +718,7 @@ describe('undici', () => { appListener = server(app, port, () => { undici.request(`http://localhost:${port}/user`, { headers: { - host: 'my-service', + host: 'my-service' } }) }) @@ -773,7 +767,7 @@ describe('undici', () => { appListener = server(app, port, () => { undici.request(`http://localhost:${port}/user`, { - headers: {'x-foo': 'bar'} + headers: { 'x-foo': 'bar' } }) }) }) From b1079f0c3f813bc7d6a6a4aa8b1e62c56e22a9c9 Mon Sep 17 00:00:00 2001 From: Florent Vilmart Date: Tue, 5 Oct 2021 16:30:56 -0400 Subject: [PATCH 06/22] move import of diag channels --- packages/datadog-plugin-undici/src/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/datadog-plugin-undici/src/index.js b/packages/datadog-plugin-undici/src/index.js index e5ff5745a89..a254558d82f 100644 --- a/packages/datadog-plugin-undici/src/index.js +++ b/packages/datadog-plugin-undici/src/index.js @@ -9,7 +9,6 @@ const kinds = require('../../../ext/kinds') const formats = require('../../../ext/formats') const urlFilter = require('../../dd-trace/src/plugins/util/urlfilter') const analyticsSampler = require('../../dd-trace/src/analytics_sampler') -const diagnosticsChannel = require('diagnostics_channel') const Reference = opentracing.Reference @@ -56,6 +55,7 @@ function parseHeaders (headers) { } function diagnostics (tracer, config) { + const diagnosticsChannel = require('diagnostics_channel') config = normalizeConfig(tracer, config) const requestChannel = diagnosticsChannel.channel('undici:request:create') From 9a9d792bc36826b8d4276c6a40a46c7a2a721b26 Mon Sep 17 00:00:00 2001 From: Florent Vilmart Date: Tue, 5 Oct 2021 16:35:34 -0400 Subject: [PATCH 07/22] adds plugin to circleCI --- .circleci/config.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/.circleci/config.yml b/.circleci/config.yml index 5add181eef9..d936512bff9 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -627,6 +627,11 @@ jobs: environment: - PLUGINS=sharedb + node-undici: + <<: *node-plugin-base + docker: + - image: node:<< parameters.node-version >> + node-when: *node-plugin-base node-winston: *node-plugin-base @@ -818,6 +823,10 @@ workflows: - node-router: *matrix-supported-node-versions - node-sharedb: *matrix-supported-node-versions - node-tedious: *matrix-supported-node-versions + - node-undici: + matrix: + parameters: + node-version: ["14"] - node-when: *matrix-supported-node-versions - node-winston: *matrix-supported-node-versions - codecov: @@ -873,6 +882,7 @@ workflows: - node-router - node-sharedb - node-tedious + - node-undici - node-when - node-winston bench: &bench-jobs From 1782b62fa1a77de32eb148d0d347c6304e3fd7d5 Mon Sep 17 00:00:00 2001 From: Florent Vilmart Date: Tue, 5 Oct 2021 16:40:05 -0400 Subject: [PATCH 08/22] use existing undici version --- .circleci/config.yml | 4 ++-- packages/datadog-plugin-undici/src/index.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index d936512bff9..e63a32c1160 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -627,7 +627,7 @@ jobs: environment: - PLUGINS=sharedb - node-undici: + node-undici: <<: *node-plugin-base docker: - image: node:<< parameters.node-version >> @@ -823,7 +823,7 @@ workflows: - node-router: *matrix-supported-node-versions - node-sharedb: *matrix-supported-node-versions - node-tedious: *matrix-supported-node-versions - - node-undici: + - node-undici: matrix: parameters: node-version: ["14"] diff --git a/packages/datadog-plugin-undici/src/index.js b/packages/datadog-plugin-undici/src/index.js index a254558d82f..8e093060d64 100644 --- a/packages/datadog-plugin-undici/src/index.js +++ b/packages/datadog-plugin-undici/src/index.js @@ -296,7 +296,7 @@ function getHooks (config) { module.exports = [ { name: 'undici', - versions: ['>=4.7.1-alpha'], + versions: ['>=4.7.0'], // FIXME: pending an undici release patch: function (_, tracer, config) { this.unpatch = diagnostics.call(this, tracer, config) } From 87bece6f1a724f93d2729e688854897913816a65 Mon Sep 17 00:00:00 2001 From: Florent Vilmart Date: Thu, 7 Oct 2021 09:08:04 -0400 Subject: [PATCH 09/22] Use undici 4.7.1 --- packages/datadog-plugin-undici/src/index.js | 2 +- packages/dd-trace/test/plugins/externals.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/datadog-plugin-undici/src/index.js b/packages/datadog-plugin-undici/src/index.js index 8e093060d64..6f078d6a6c8 100644 --- a/packages/datadog-plugin-undici/src/index.js +++ b/packages/datadog-plugin-undici/src/index.js @@ -296,7 +296,7 @@ function getHooks (config) { module.exports = [ { name: 'undici', - versions: ['>=4.7.0'], // FIXME: pending an undici release + versions: ['>=4.7.1'], patch: function (_, tracer, config) { this.unpatch = diagnostics.call(this, tracer, config) } diff --git a/packages/dd-trace/test/plugins/externals.json b/packages/dd-trace/test/plugins/externals.json index b82a2d697c5..49dffb06b96 100644 --- a/packages/dd-trace/test/plugins/externals.json +++ b/packages/dd-trace/test/plugins/externals.json @@ -134,7 +134,7 @@ "undici": [ { "name": "undici", - "versions": [">=4.7.0"] + "versions": [">=4.7.1"] } ] } From 501adc3f7a0ac783baea82716e208a98cc58dde2 Mon Sep 17 00:00:00 2001 From: Florent Vilmart Date: Wed, 13 Oct 2021 15:18:30 -0400 Subject: [PATCH 10/22] Ensure plugin do not throw if diagnostics_channel is not available --- packages/datadog-plugin-undici/src/index.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/datadog-plugin-undici/src/index.js b/packages/datadog-plugin-undici/src/index.js index 6f078d6a6c8..e219f500cc1 100644 --- a/packages/datadog-plugin-undici/src/index.js +++ b/packages/datadog-plugin-undici/src/index.js @@ -55,7 +55,13 @@ function parseHeaders (headers) { } function diagnostics (tracer, config) { - const diagnosticsChannel = require('diagnostics_channel') + let diagnosticsChannel + try { + diagnosticsChannel = require('diagnostics_channel') + } catch (e) { + log.error("Unable to configure undici, cannot require 'diagnostics_channel'") + return () => {} + } config = normalizeConfig(tracer, config) const requestChannel = diagnosticsChannel.channel('undici:request:create') From f50f36616ba25c63c42ae68e002c75c9bdce554b Mon Sep 17 00:00:00 2001 From: Florent Vilmart Date: Tue, 19 Oct 2021 11:40:09 -0400 Subject: [PATCH 11/22] ensure we keep refs to the channels --- packages/datadog-plugin-undici/src/index.js | 37 +- .../datadog-plugin-undici/test/index.spec.js | 378 ++++++++++-------- 2 files changed, 243 insertions(+), 172 deletions(-) diff --git a/packages/datadog-plugin-undici/src/index.js b/packages/datadog-plugin-undici/src/index.js index e219f500cc1..0d73aca1c01 100644 --- a/packages/datadog-plugin-undici/src/index.js +++ b/packages/datadog-plugin-undici/src/index.js @@ -53,30 +53,37 @@ function parseHeaders (headers) { }) return object } +const channels = { + requestChannel: undefined, + headersChannel: undefined, + errorChannel: undefined +} function diagnostics (tracer, config) { let diagnosticsChannel try { diagnosticsChannel = require('diagnostics_channel') } catch (e) { - log.error("Unable to configure undici, cannot require 'diagnostics_channel'") + log.error( + "Unable to configure undici, cannot require 'diagnostics_channel'" + ) return () => {} } config = normalizeConfig(tracer, config) - const requestChannel = diagnosticsChannel.channel('undici:request:create') - const headersChannel = diagnosticsChannel.channel('undici:request:headers') - const requestErrorChannel = diagnosticsChannel.channel( - 'undici:request:error' + channels.requestChannel = diagnosticsChannel.channel('undici:request:create') + channels.headersChannel = diagnosticsChannel.channel( + 'undici:request:headers' ) + channels.errorChannel = diagnosticsChannel.channel('undici:request:error') + + channels.requestChannel.subscribe(handleRequestCreate) + channels.errorChannel.subscribe(handleRequestError) + channels.headersChannel.subscribe(handleRequestHeaders) // We use a weakmap here to store the request / spans const requestSpansMap = new WeakMap() - requestChannel.subscribe(handleRequestCreate) - requestErrorChannel.subscribe(handleRequestError) - headersChannel.subscribe(handleRequestHeaders) - function handleRequestCreate ({ request }) { const method = (request.method || 'GET').toUpperCase() @@ -123,14 +130,14 @@ function diagnostics (tracer, config) { } return function unsubscribe () { - if (requestChannel.hasSubscribers) { - requestChannel.unsubscribe(handleRequestCreate) + if (channels.requestChannel.hasSubscribers) { + channels.requestChannel.unsubscribe(handleRequestCreate) } - if (headersChannel.hasSubscribers) { - headersChannel.unsubscribe(handleRequestHeaders) + if (channels.headersChannel.hasSubscribers) { + channels.headersChannel.unsubscribe(handleRequestHeaders) } - if (requestErrorChannel.hasSubscribers) { - requestErrorChannel.unsubscribe(handleRequestError) + if (channels.errorChannel.hasSubscribers) { + channels.errorChannel.unsubscribe(handleRequestError) } } } diff --git a/packages/datadog-plugin-undici/test/index.spec.js b/packages/datadog-plugin-undici/test/index.spec.js index 6c1c09f6023..80e76fd7f5e 100644 --- a/packages/datadog-plugin-undici/test/index.spec.js +++ b/packages/datadog-plugin-undici/test/index.spec.js @@ -10,15 +10,13 @@ const HTTP_REQUEST_HEADERS = tags.HTTP_REQUEST_HEADERS const HTTP_RESPONSE_HEADERS = tags.HTTP_RESPONSE_HEADERS const plugin = require('../src') -wrapIt() - describe('undici', () => { let express let undici let appListener let tracer - withVersions(plugin, 'undici', version => { + withVersions(plugin, 'undici', (version) => { function server (app, port, listener) { const server = require('http').createServer(app) server.listen(port, 'localhost', listener) @@ -39,30 +37,38 @@ describe('undici', () => { describe('without configuration', () => { beforeEach(() => { - return agent.load('undici') - .then(() => { - undici = require(`../../../versions/undici@${version}`).get() - express = require('express') - }) + return agent.load('undici').then(() => { + undici = require(`../../../versions/undici@${version}`).get() + express = require('express') + }) }) - it('should do automatic instrumentation', done => { + it('should do automatic instrumentation', (done) => { const app = express() app.get('/user', (req, res) => { res.status(200).send() }) - getPort().then(port => { + getPort().then((port) => { agent - .use(traces => { - expect(traces[0][0]).to.have.property('service', 'test-http-client') + .use((traces) => { + expect(traces[0][0]).to.have.property( + 'service', + 'test-http-client' + ) 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', 'client') - expect(traces[0][0].meta).to.have.property('http.url', `http://localhost:${port}/user`) + 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', '200') + expect(traces[0][0].meta).to.have.property( + 'http.status_code', + '200' + ) }) .then(done) .catch(done) @@ -73,17 +79,20 @@ describe('undici', () => { }) }) - it('should support configuration as an URL object', done => { + it('should support configuration as an URL object', (done) => { const app = express() app.get('/user', (req, res) => { res.status(200).send() }) - getPort().then(port => { + getPort().then((port) => { agent - .use(traces => { - expect(traces[0][0].meta).to.have.property('http.url', `http://localhost:${port}/user`) + .use((traces) => { + expect(traces[0][0].meta).to.have.property( + 'http.url', + `http://localhost:${port}/user` + ) }) .then(done) .catch(done) @@ -101,18 +110,24 @@ describe('undici', () => { }) }) - it('should remove the query string from the URL', done => { + it('should remove the query string from the URL', (done) => { const app = express() app.get('/user', (req, res) => { res.status(200).send() }) - getPort().then(port => { + getPort().then((port) => { agent - .use(traces => { - expect(traces[0][0].meta).to.have.property('http.status_code', '200') - expect(traces[0][0].meta).to.have.property('http.url', `http://localhost:${port}/user`) + .use((traces) => { + expect(traces[0][0].meta).to.have.property( + 'http.status_code', + '200' + ) + expect(traces[0][0].meta).to.have.property( + 'http.url', + `http://localhost:${port}/user` + ) }) .then(done) .catch(done) @@ -134,22 +149,25 @@ describe('undici', () => { undici.request(url) }) - await agent.use(traces => { + await agent.use((traces) => { expect(traces[0][0].meta).to.have.property('http.status_code', '200') - expect(traces[0][0].meta).to.have.property('http.url', `http://localhost:${port}/user`) + expect(traces[0][0].meta).to.have.property( + 'http.url', + `http://localhost:${port}/user` + ) }) }) - it('should not require consuming the data', done => { + it('should not require consuming the data', (done) => { const app = express() app.get('/user', (req, res) => { res.status(200).send() }) - getPort().then(port => { + getPort().then((port) => { agent - .use(traces => { + .use((traces) => { expect(traces[0][0]).to.not.be.undefined }) .then(done) @@ -161,7 +179,7 @@ describe('undici', () => { }) }) - it('should inject its parent span in the headers', done => { + it('should inject its parent span in the headers', (done) => { const app = express() app.get('/user', (req, res) => { @@ -171,10 +189,13 @@ describe('undici', () => { res.status(200).send() }) - getPort().then(port => { + getPort().then((port) => { agent - .use(traces => { - expect(traces[0][0].meta).to.have.property('http.status_code', '200') + .use((traces) => { + expect(traces[0][0].meta).to.have.property( + 'http.status_code', + '200' + ) }) .then(done) .catch(done) @@ -185,7 +206,7 @@ describe('undici', () => { }) }) - it('should skip injecting if the Authorization header contains an AWS signature', done => { + it('should skip injecting if the Authorization header contains an AWS signature', (done) => { const app = express() app.get('/', (req, res) => { @@ -201,7 +222,7 @@ describe('undici', () => { } }) - getPort().then(port => { + getPort().then((port) => { appListener = server(app, port, () => { undici.request(`http://localhost:${port}/`, { headers: { @@ -212,7 +233,7 @@ describe('undici', () => { }) }) - it('should skip injecting if one of the Authorization headers contains an AWS signature', done => { + it('should skip injecting if one of the Authorization headers contains an AWS signature', (done) => { const app = express() app.get('/', (req, res) => { @@ -228,7 +249,7 @@ describe('undici', () => { } }) - getPort().then(port => { + getPort().then((port) => { appListener = server(app, port, () => { undici.request(`http://localhost:${port}/`, { headers: { @@ -239,7 +260,7 @@ describe('undici', () => { }) }) - it('should skip injecting if the X-Amz-Signature header is set', done => { + it('should skip injecting if the X-Amz-Signature header is set', (done) => { const app = express() app.get('/', (req, res) => { @@ -255,7 +276,7 @@ describe('undici', () => { } }) - getPort().then(port => { + getPort().then((port) => { appListener = server(app, port, () => { undici.request(`http://localhost:${port}/`, { headers: { @@ -266,7 +287,7 @@ describe('undici', () => { }) }) - it('should skip injecting if the X-Amz-Signature query param is set', done => { + it('should skip injecting if the X-Amz-Signature query param is set', (done) => { const app = express() app.get('/', (req, res) => { @@ -282,7 +303,7 @@ describe('undici', () => { } }) - getPort().then(port => { + getPort().then((port) => { appListener = server(app, port, () => { undici.request(`http://localhost:${port}/?X-Amz-Signature=abc123`, { headers: { @@ -294,7 +315,7 @@ describe('undici', () => { }) // TODO: the callbacks is run after the scope ends - it.skip('should run the callback in the parent context', done => { + it.skip('should run the callback in the parent context', (done) => { if (process.env.DD_CONTEXT_PROPAGATION === 'false') return done() const app = express() @@ -303,9 +324,9 @@ describe('undici', () => { res.status(200).send('OK') }) - getPort().then(port => { + getPort().then((port) => { appListener = server(app, port, () => { - undici.request(`http://localhost:${port}/user`, res => { + undici.request(`http://localhost:${port}/user`, (res) => { expect(tracer.scope().active()).to.be.null done() }) @@ -314,7 +335,7 @@ describe('undici', () => { }) // TODO: There is no event listener yet - it.skip('should run the event listeners in the parent context', done => { + it.skip('should run the event listeners in the parent context', (done) => { if (process.env.DD_CONTEXT_PROPAGATION === 'false') return done() const app = express() @@ -323,24 +344,33 @@ describe('undici', () => { res.status(200).send('OK') }) - getPort().then(port => { + getPort().then((port) => { appListener = server(app, port, () => { - undici.request(`http://localhost:${port}/user`, res => { + undici.request(`http://localhost:${port}/user`, (res) => { done() }) }) }) }) - it('should handle connection errors', done => { - getPort().then(port => { + it('should handle connection errors', (done) => { + getPort().then((port) => { let error agent - .use(traces => { - expect(traces[0][0].meta).to.have.property('error.type', error.name) - expect(traces[0][0].meta).to.have.property('error.msg', error.message) - expect(traces[0][0].meta).to.have.property('error.stack', error.stack) + .use((traces) => { + expect(traces[0][0].meta).to.have.property( + 'error.type', + error.name + ) + expect(traces[0][0].meta).to.have.property( + 'error.msg', + error.message + ) + expect(traces[0][0].meta).to.have.property( + 'error.stack', + error.stack + ) }) .then(done) .catch(done) @@ -351,16 +381,16 @@ describe('undici', () => { }) }) - it('should not record HTTP 5XX responses as errors by default', done => { + it('should not record HTTP 5XX responses as errors by default', (done) => { const app = express() app.get('/user', (req, res) => { res.status(500).send() }) - getPort().then(port => { + getPort().then((port) => { agent - .use(traces => { + .use((traces) => { expect(traces[0][0]).to.have.property('error', 0) }) .then(done) @@ -372,16 +402,16 @@ describe('undici', () => { }) }) - it('should record HTTP 4XX responses as errors by default', done => { + it('should record HTTP 4XX responses as errors by default', (done) => { const app = express() app.get('/user', (req, res) => { res.status(400).send() }) - getPort().then(port => { + getPort().then((port) => { agent - .use(traces => { + .use((traces) => { expect(traces[0][0]).to.have.property('error', 1) }) .then(done) @@ -393,48 +423,64 @@ describe('undici', () => { }) }) - it('should record destroyed requests as errors', done => { + it('should record destroyed requests as errors', (done) => { const app = express() app.get('/user', (req, res) => {}) - getPort().then(port => { + getPort().then((port) => { let error agent - .use(traces => { + .use((traces) => { expect(traces[0][0]).to.have.property('error', 1) - expect(traces[0][0].meta).to.have.property('error.msg', error.message) - expect(traces[0][0].meta).to.have.property('error.type', error.name) - expect(traces[0][0].meta).to.have.property('error.stack', error.stack) - expect(traces[0][0].meta).to.not.have.property('http.status_code') + expect(traces[0][0].meta).to.have.property( + 'error.msg', + error.message + ) + expect(traces[0][0].meta).to.have.property( + 'error.type', + error.name + ) + expect(traces[0][0].meta).to.have.property( + 'error.stack', + error.stack + ) + expect(traces[0][0].meta).to.not.have.property( + 'http.status_code' + ) }) .then(done) .catch(done) appListener = server(app, port, () => { const client = new undici.Client(`http://localhost:${port}`) - client.request({ - path: '/user', - method: 'GET' - }, (err, data) => { - error = err - }) + client.request( + { + path: '/user', + method: 'GET' + }, + (err, data) => { + error = err + } + ) client.destroy() }) }) }) - it('should record aborted requests as errors', done => { + it('should record aborted requests as errors', (done) => { const app = express() app.get('/user', (req, res) => {}) - getPort().then(port => { + getPort().then((port) => { agent - .use(traces => { + .use((traces) => { expect(traces[0][0]).to.have.property('error', 1) - expect(traces[0][0].meta).to.not.have.property('http.status_code') + expect(traces[0][0].meta).to.not.have.property( + 'http.status_code' + ) }) .then(done) .catch(done) @@ -450,16 +496,18 @@ describe('undici', () => { }) // TODO: Get timeout working - it.skip('should record timeouts as errors', done => { + it.skip('should record timeouts as errors', (done) => { const app = express() app.get('/user', (req, res) => {}) - getPort().then(port => { + getPort().then((port) => { agent - .use(traces => { + .use((traces) => { expect(traces[0][0]).to.have.property('error', 1) - expect(traces[0][0].meta).to.not.have.property('http.status_code') + expect(traces[0][0].meta).to.not.have.property( + 'http.status_code' + ) }) .then(done) .catch(done) @@ -473,45 +521,49 @@ describe('undici', () => { }) }) - it('should record when the request was aborted', done => { + it('should record when the request was aborted', (done) => { const app = express() app.get('/abort', (req, res) => { res.status(200).send() }) - getPort().then(port => { + getPort().then((port) => { agent - .use(traces => { - expect(traces[0][0]).to.have.property('service', 'test-http-client') + .use((traces) => { + expect(traces[0][0]).to.have.property( + 'service', + 'test-http-client' + ) }) .then(done) .catch(done) appListener = server(app, port, () => { const ac = new AbortController() - undici.request(`http://localhost:${port}/abort`, { signal: ac.signal }) + undici.request(`http://localhost:${port}/abort`, { + signal: ac.signal + }) ac.abort() }) }) }) - it('should skip requests to the agent', done => { + it('should skip requests to the agent', (done) => { const app = express() app.get('/user', (req, res) => { res.status(200).send() }) - getPort().then(port => { + getPort().then((port) => { const timer = setTimeout(done, 100) - agent - .use(() => { - done(new Error('Request to the agent was traced.')) - clearTimeout(timer) - }) + agent.use(() => { + done(new Error('Request to the agent was traced.')) + clearTimeout(timer) + }) appListener = server(app, port, () => { undici.request(tracer._tracer._url.href) @@ -528,23 +580,22 @@ describe('undici', () => { service: 'custom' } - return agent.load('undici', config) - .then(() => { - undici = require(`../../../versions/undici@${version}`).get() - express = require('express') - }) + return agent.load('undici', config).then(() => { + undici = require(`../../../versions/undici@${version}`).get() + express = require('express') + }) }) - it('should be configured with the correct values', done => { + it('should be configured with the correct values', (done) => { const app = express() app.get('/user', (req, res) => { res.status(200).send() }) - getPort().then(port => { + getPort().then((port) => { agent - .use(traces => { + .use((traces) => { expect(traces[0][0]).to.have.property('service', 'custom') }) .then(done) @@ -562,26 +613,25 @@ describe('undici', () => { beforeEach(() => { config = { - validateStatus: status => status < 500 + validateStatus: (status) => status < 500 } - return agent.load('undici', config) - .then(() => { - undici = require(`../../../versions/undici@${version}`).get() - express = require('express') - }) + return agent.load('undici', config).then(() => { + undici = require(`../../../versions/undici@${version}`).get() + express = require('express') + }) }) - it('should use the supplied function to decide if a response is an error', done => { + it('should use the supplied function to decide if a response is an error', (done) => { const app = express() app.get('/user', (req, res) => { res.status(500).send() }) - getPort().then(port => { + getPort().then((port) => { agent - .use(traces => { + .use((traces) => { expect(traces[0][0]).to.have.property('error', 1) }) .then(done) @@ -601,24 +651,26 @@ describe('undici', () => { config = { splitByDomain: true } - return agent.load('undici', config) - .then(() => { - undici = require(`../../../versions/undici@${version}`).get() - express = require('express') - }) + return agent.load('undici', config).then(() => { + undici = require(`../../../versions/undici@${version}`).get() + express = require('express') + }) }) - it('should use the remote endpoint as the service name', done => { + it('should use the remote endpoint as the service name', (done) => { const app = express() app.get('/user', (req, res) => { res.status(200).send() }) - getPort().then(port => { + getPort().then((port) => { agent - .use(traces => { - expect(traces[0][0]).to.have.property('service', `localhost:${port}`) + .use((traces) => { + expect(traces[0][0]).to.have.property( + 'service', + `localhost:${port}` + ) }) .then(done) .catch(done) @@ -637,17 +689,16 @@ describe('undici', () => { config = { headers: ['host', 'x-foo'] } - return agent.load('undici', config) - .then(() => { - undici = require(`../../../versions/undici@${version}`).get() - express = require('express') - }) + return agent.load('undici', config).then(() => { + undici = require(`../../../versions/undici@${version}`).get() + express = require('express') + }) }) describe('host header', () => { // TODO: Injected headers are not available yet // for request - it('should add tags for the host header', done => { + it('should add tags for the host header', (done) => { const app = express() app.get('/user', (req, res) => { @@ -655,11 +706,14 @@ describe('undici', () => { res.status(200).send() }) - getPort().then(port => { + getPort().then((port) => { agent - .use(traces => { + .use((traces) => { const meta = traces[0][0].meta - expect(meta).to.have.property(`${HTTP_REQUEST_HEADERS}.host`, `localhost:${port}`) + expect(meta).to.have.property( + `${HTTP_REQUEST_HEADERS}.host`, + `localhost:${port}` + ) }) .then(done) .catch(done) @@ -670,7 +724,7 @@ describe('undici', () => { }) }) - it('should add tags for the host header through Client', done => { + it('should add tags for the host header through Client', (done) => { const app = express() app.get('/user', (req, res) => { @@ -678,11 +732,14 @@ describe('undici', () => { res.status(200).send() }) - getPort().then(port => { + getPort().then((port) => { agent - .use(traces => { + .use((traces) => { const meta = traces[0][0].meta - expect(meta).to.have.property(`${HTTP_REQUEST_HEADERS}.host`, `localhost:${port}`) + expect(meta).to.have.property( + `${HTTP_REQUEST_HEADERS}.host`, + `localhost:${port}` + ) }) .then(done) .catch(done) @@ -698,7 +755,7 @@ describe('undici', () => { }) }) - it('should pass overwritten host header', done => { + it('should pass overwritten host header', (done) => { const app = express() app.get('/user', (req, res) => { @@ -706,11 +763,14 @@ describe('undici', () => { res.status(200).send() }) - getPort().then(port => { + getPort().then((port) => { agent - .use(traces => { + .use((traces) => { const meta = traces[0][0].meta - expect(meta).to.have.property(`${HTTP_REQUEST_HEADERS}.host`, `my-service`) + expect(meta).to.have.property( + `${HTTP_REQUEST_HEADERS}.host`, + `my-service` + ) }) .then(done) .catch(done) @@ -726,7 +786,7 @@ describe('undici', () => { }) }) - it('should add tags for the configured headers', done => { + it('should add tags for the configured headers', (done) => { const app = express() app.get('/user', (req, res) => { @@ -734,11 +794,14 @@ describe('undici', () => { res.status(200).send() }) - getPort().then(port => { + getPort().then((port) => { agent - .use(traces => { + .use((traces) => { const meta = traces[0][0].meta - expect(meta).to.have.property(`${HTTP_RESPONSE_HEADERS}.x-foo`, 'bar') + expect(meta).to.have.property( + `${HTTP_RESPONSE_HEADERS}.x-foo`, + 'bar' + ) }) .then(done) .catch(done) @@ -749,18 +812,21 @@ describe('undici', () => { }) }) - it('should support adding request headers', done => { + it('should support adding request headers', (done) => { const app = express() app.get('/user', (req, res) => { res.status(200).send() }) - getPort().then(port => { + getPort().then((port) => { agent - .use(traces => { + .use((traces) => { const meta = traces[0][0].meta - expect(meta).to.have.property(`${HTTP_REQUEST_HEADERS}.x-foo`, `bar`) + expect(meta).to.have.property( + `${HTTP_REQUEST_HEADERS}.x-foo`, + `bar` + ) }) .then(done) .catch(done) @@ -785,23 +851,22 @@ describe('undici', () => { } } } - return agent.load('undici', config) - .then(() => { - undici = require(`../../../versions/undici@${version}`).get() - express = require('express') - }) + return agent.load('undici', config).then(() => { + undici = require(`../../../versions/undici@${version}`).get() + express = require('express') + }) }) - it('should run the request hook before the span is finished', done => { + it('should run the request hook before the span is finished', (done) => { const app = express() app.get('/user', (req, res) => { res.status(200).send() }) - getPort().then(port => { + getPort().then((port) => { agent - .use(traces => { + .use((traces) => { expect(traces[0][0]).to.have.property('resource', 'GET -- /user') }) .then(done) @@ -821,14 +886,13 @@ describe('undici', () => { config = { propagationBlocklist: [/\/users/] } - return agent.load('undici', config) - .then(() => { - undici = require(`../../../versions/undici@${version}`).get() - express = require('express') - }) + return agent.load('undici', config).then(() => { + undici = require(`../../../versions/undici@${version}`).get() + express = require('express') + }) }) - it('should skip injecting if the url matches an item in the propagationBlacklist', done => { + it('should skip injecting if the url matches an item in the propagationBlacklist', (done) => { const app = express() app.get('/users', (req, res) => { @@ -844,7 +908,7 @@ describe('undici', () => { } }) - getPort().then(port => { + getPort().then((port) => { appListener = server(app, port, () => { undici.request(`http://localhost:${port}/users`) }) From ad6f6b3dc22b40f573c8f891636c036ebea02262 Mon Sep 17 00:00:00 2001 From: Florent Vilmart Date: Mon, 31 Jan 2022 14:32:08 -0500 Subject: [PATCH 12/22] Revert linter changes --- docs/test.ts | 373 +++++++++++-------------- packages/dd-trace/src/plugins/index.js | 85 +++--- 2 files changed, 211 insertions(+), 247 deletions(-) diff --git a/docs/test.ts b/docs/test.ts index e6293a033e1..6bc21fa0a1d 100644 --- a/docs/test.ts +++ b/docs/test.ts @@ -1,21 +1,8 @@ -import ddTrace, { - tracer, - Tracer, - TracerOptions, - Span, - SpanContext, - SpanOptions, - Scope, -} from ".."; -import { formats, kinds, priority, tags, types } from "../ext"; -import { BINARY, HTTP_HEADERS, LOG, TEXT_MAP } from "../ext/formats"; -import { SERVER, CLIENT, PRODUCER, CONSUMER } from "../ext/kinds"; -import { - USER_REJECT, - AUTO_REJECT, - AUTO_KEEP, - USER_KEEP, -} from "../ext/priority"; +import ddTrace, { tracer, Tracer, TracerOptions, Span, SpanContext, SpanOptions, Scope } from '..'; +import { formats, kinds, priority, tags, types } from '../ext'; +import { BINARY, HTTP_HEADERS, LOG, TEXT_MAP } from '../ext/formats'; +import { SERVER, CLIENT, PRODUCER, CONSUMER } from '../ext/kinds' +import { USER_REJECT, AUTO_REJECT, AUTO_KEEP, USER_KEEP } from '../ext/priority' import { ERROR, HTTP_METHOD, @@ -31,9 +18,9 @@ import { SERVICE_NAME, SPAN_KIND, SPAN_TYPE, -} from "../ext/tags"; -import { HTTP, WEB } from "../ext/types"; -import * as opentracing from "opentracing"; +} from '../ext/tags' +import { HTTP, WEB } from '../ext/types' +import * as opentracing from 'opentracing'; opentracing.initGlobalTracer(tracer); @@ -49,87 +36,87 @@ tracer.init({ enabled: true, logInjection: true, startupLogs: false, - env: "test", - version: "1.0.0", - url: "http://localhost", + env: 'test', + version: '1.0.0', + url: 'http://localhost', runtimeMetrics: true, ingestion: { sampleRate: 0.5, - rateLimit: 500, + rateLimit: 500 }, experimental: { b3: true, runtimeId: true, - exporter: "log", + exporter: 'log', sampler: { sampleRate: 1, rateLimit: 1000, rules: [ - { sampleRate: 0.5, service: "foo", name: "foo.request" }, - { sampleRate: 0.1, service: /foo/, name: /foo\.request/ }, - ], - }, + { sampleRate: 0.5, service: 'foo', name: 'foo.request' }, + { sampleRate: 0.1, service: /foo/, name: /foo\.request/ } + ] + } }, - hostname: "agent", + hostname: 'agent', logger: { - error(message: string | Error) {}, - warn(message: string) {}, - info(message: string) {}, - debug(message: string) {}, + error (message: string | Error) {}, + warn (message: string) {}, + info (message: string) {}, + debug (message: string) {} }, plugins: false, port: 7777, dogstatsd: { - hostname: "dsd-agent", - port: 8888, + hostname: 'dsd-agent', + port: 8888 }, flushInterval: 1000, lookup: () => {}, sampleRate: 0.1, - service: "test", + service: 'test', tags: { - foo: "bar", + foo: 'bar' }, reportHostname: true, - logLevel: "debug", + logLevel: 'debug' }); const httpOptions = { - service: "test", - allowlist: ["url", /url/, (url) => true], - blocklist: ["url", /url/, (url) => true], - validateStatus: (code) => code < 400, - headers: ["host"], - middleware: true, + service: 'test', + allowlist: ['url', /url/, url => true], + blocklist: ['url', /url/, url => true], + validateStatus: code => code < 400, + headers: ['host'], + middleware: true }; const httpServerOptions = { ...httpOptions, hooks: { - request: (span, req, res) => {}, - }, + request: (span, req, res) => {} + } }; const httpClientOptions = { ...httpOptions, splitByDomain: true, - propagationBlocklist: ["url", /url/, (url) => true], + propagationBlocklist: ['url', /url/, url => true], hooks: { - request: (span, req, res) => {}, - }, + request: (span, req, res) => {} + } }; const http2ServerOptions = { - ...httpOptions, + ...httpOptions }; const http2ClientOptions = { ...httpOptions, - splitByDomain: true, + splitByDomain: true }; const graphqlOptions = { - service: "test", + service: 'test', depth: 2, variables: ({ foo, baz }) => ({ foo }), collapse: false, @@ -137,19 +124,19 @@ const graphqlOptions = { hooks: { execute: (span, args, res) => {}, validate: (span, document, errors) => {}, - parse: (span, source, document) => {}, - }, + parse: (span, source, document) => {} + } }; const elasticsearchOptions = { - service: "test", + service: 'test', hooks: { query: (span, params) => {}, }, }; const awsSdkOptions = { - service: "test", + service: 'test', splitByAwsService: false, hooks: { request: (span, response) => {}, @@ -157,18 +144,18 @@ const awsSdkOptions = { s3: false, sqs: { consumer: true, - producer: false, - }, + producer: false + } }; const redisOptions = { - service: "test", - allowlist: ["info", /auth/i, (command) => true], - blocklist: ["info", /auth/i, (command) => true], + service: 'test', + allowlist: ['info', /auth/i, command => true], + blocklist: ['info', /auth/i, command => true], }; const sharedbOptions = { - service: "test", + service: 'test', hooks: { receive: (span, request) => {}, reply: (span, request, reply) => {}, @@ -176,135 +163,128 @@ const sharedbOptions = { }; const moleculerOptions = { - service: "test", + service: 'test', client: false, params: true, server: { - meta: true, - }, -}; - -tracer.use("amqp10"); -tracer.use("amqplib"); -tracer.use("aws-sdk", awsSdkOptions); -tracer.use("bunyan"); -tracer.use("couchbase"); -tracer.use("cassandra-driver"); -tracer.use("connect"); -tracer.use("connect", httpServerOptions); -tracer.use("cypress"); -tracer.use("cucumber"); -tracer.use("cucumber", { service: "cucumber-service" }); -tracer.use("dns"); -tracer.use("elasticsearch", elasticsearchOptions); -tracer.use("express"); -tracer.use("express", httpServerOptions); -tracer.use("fastify"); -tracer.use("fastify", httpServerOptions); -tracer.use("fs"); -tracer.use("generic-pool"); -tracer.use("google-cloud-pubsub"); -tracer.use("graphql", graphqlOptions); -tracer.use("graphql", { variables: ["foo", "bar"] }); -tracer.use("grpc"); -tracer.use("grpc", { metadata: ["foo", "bar"] }); -tracer.use("grpc", { metadata: (meta) => meta }); -tracer.use("grpc", { client: { metadata: [] } }); -tracer.use("grpc", { server: { metadata: [] } }); -tracer.use("hapi"); -tracer.use("hapi", httpServerOptions); -tracer.use("http"); -tracer.use("http", { - server: httpServerOptions, + meta: true + } +} + +tracer.use('amqp10'); +tracer.use('amqplib'); +tracer.use('aws-sdk', awsSdkOptions); +tracer.use('bunyan'); +tracer.use('couchbase'); +tracer.use('cassandra-driver'); +tracer.use('connect'); +tracer.use('connect', httpServerOptions); +tracer.use('cypress'); +tracer.use('cucumber') +tracer.use('cucumber', { service: 'cucumber-service' }); +tracer.use('dns'); +tracer.use('elasticsearch', elasticsearchOptions); +tracer.use('express'); +tracer.use('express', httpServerOptions); +tracer.use('fastify'); +tracer.use('fastify', httpServerOptions); +tracer.use('fs'); +tracer.use('generic-pool'); +tracer.use('google-cloud-pubsub'); +tracer.use('graphql', graphqlOptions); +tracer.use('graphql', { variables: ['foo', 'bar'] }); +tracer.use('grpc'); +tracer.use('grpc', { metadata: ['foo', 'bar'] }); +tracer.use('grpc', { metadata: meta => meta }); +tracer.use('grpc', { client: { metadata: [] } }); +tracer.use('grpc', { server: { metadata: [] } }); +tracer.use('hapi'); +tracer.use('hapi', httpServerOptions); +tracer.use('http'); +tracer.use('http', { + server: httpServerOptions }); -tracer.use("http", { - client: httpClientOptions, +tracer.use('http', { + client: httpClientOptions }); -tracer.use("http2"); -tracer.use("http2", { - server: http2ServerOptions, +tracer.use('http2'); +tracer.use('http2', { + server: http2ServerOptions }); -tracer.use("http2", { - client: http2ClientOptions, +tracer.use('http2', { + client: http2ClientOptions }); -tracer.use("ioredis"); -tracer.use("ioredis", redisOptions); -tracer.use("ioredis", { splitByInstance: true }); -tracer.use("jest"); -tracer.use("jest", { service: "jest-service" }); -tracer.use("kafkajs"); -tracer.use("knex"); -tracer.use("koa"); -tracer.use("koa", httpServerOptions); -tracer.use("limitd-client"); -tracer.use("memcached"); -tracer.use("microgateway-core", httpServerOptions); -tracer.use("mocha"); -tracer.use("mocha", { service: "mocha-service" }); -tracer.use("moleculer", moleculerOptions); -tracer.use("mongodb-core"); -tracer.use("mongoose"); -tracer.use("mysql"); -tracer.use("mysql2"); -tracer.use("net"); -tracer.use("next"); -tracer.use("oracledb"); -tracer.use("oracledb", { - service: (params) => `${params.host}-${params.database}`, -}); -tracer.use("paperplane"); -tracer.use("paperplane", httpServerOptions); -tracer.use("pg"); -tracer.use("pg", { service: (params) => `${params.host}-${params.database}` }); -tracer.use("pino"); -tracer.use("redis"); -tracer.use("redis", redisOptions); -tracer.use("restify"); -tracer.use("restify", httpServerOptions); -tracer.use("rhea"); -tracer.use("router"); -tracer.use("sharedb", sharedbOptions); -tracer.use("tedious"); -tracer.use("undici", httpClientOptions); -tracer.use("when"); -tracer.use("winston"); - -tracer.use("express", false); -tracer.use("express", { enabled: false }); -tracer.use("express", { service: "name" }); -tracer.use("express", { measured: true }); - -span = tracer.startSpan("test"); -span = tracer.startSpan("test", {}); -span = tracer.startSpan("test", { +tracer.use('ioredis'); +tracer.use('ioredis', redisOptions); +tracer.use('ioredis', { splitByInstance: true }); +tracer.use('jest'); +tracer.use('jest', { service: 'jest-service' }); +tracer.use('kafkajs'); +tracer.use('knex'); +tracer.use('koa'); +tracer.use('koa', httpServerOptions); +tracer.use('limitd-client'); +tracer.use('memcached'); +tracer.use('microgateway-core', httpServerOptions); +tracer.use('mocha'); +tracer.use('mocha', { service: 'mocha-service' }); +tracer.use('moleculer', moleculerOptions); +tracer.use('mongodb-core'); +tracer.use('mongoose'); +tracer.use('mysql'); +tracer.use('mysql2'); +tracer.use('net'); +tracer.use('next'); +tracer.use('oracledb'); +tracer.use('oracledb', { service: params => `${params.host}-${params.database}` }); +tracer.use('paperplane'); +tracer.use('paperplane', httpServerOptions); +tracer.use('pg'); +tracer.use('pg', { service: params => `${params.host}-${params.database}` }); +tracer.use('pino'); +tracer.use('redis'); +tracer.use('redis', redisOptions); +tracer.use('restify'); +tracer.use('restify', httpServerOptions); +tracer.use('rhea'); +tracer.use('router'); +tracer.use('sharedb', sharedbOptions); +tracer.use('tedious'); +tracer.use('undici', httpClientOptions); +tracer.use('winston'); + +tracer.use('express', false) +tracer.use('express', { enabled: false }) +tracer.use('express', { service: 'name' }); +tracer.use('express', { measured: true }); + +span = tracer.startSpan('test'); +span = tracer.startSpan('test', {}); +span = tracer.startSpan('test', { childOf: span || span.context(), references: [], startTime: 123456789.1234, tags: { - foo: "bar", - }, + foo: 'bar' + } }); -tracer.trace("test", () => {}); -tracer.trace("test", { tags: { foo: "bar" } }, () => {}); -tracer.trace( - "test", - { service: "foo", resource: "bar", type: "baz" }, - () => {} -); -tracer.trace("test", { measured: true }, () => {}); -tracer.trace("test", (span: Span) => {}); -tracer.trace("test", (span: Span, fn: () => void) => {}); -tracer.trace("test", (span: Span, fn: (err: Error) => string) => {}); +tracer.trace('test', () => {}) +tracer.trace('test', { tags: { foo: 'bar' }}, () => {}) +tracer.trace('test', { service: 'foo', resource: 'bar', type: 'baz' }, () => {}) +tracer.trace('test', { measured: true }, () => {}) +tracer.trace('test', (span: Span) => {}) +tracer.trace('test', (span: Span, fn: () => void) => {}) +tracer.trace('test', (span: Span, fn: (err: Error) => string) => {}) -promise = tracer.trace("test", () => Promise.resolve()); +promise = tracer.trace('test', () => Promise.resolve()) -tracer.wrap("test", () => {}); -tracer.wrap("test", (foo: string) => "test"); +tracer.wrap('test', () => {}) +tracer.wrap('test', (foo: string) => 'test') -promise = tracer.wrap("test", () => Promise.resolve())(); +promise = tracer.wrap('test', () => Promise.resolve())() -const carrier = {}; +const carrier = {} tracer.inject(span || span.context(), HTTP_HEADERS, carrier); context = tracer.extract(HTTP_HEADERS, carrier); @@ -312,25 +292,16 @@ context = tracer.extract(HTTP_HEADERS, carrier); traceId = context.toTraceId(); spanId = context.toSpanId(); -const scope = tracer.scope(); +const scope = tracer.scope() span = scope.active(); -const activateStringType: string = scope.activate(span, () => "test"); +const activateStringType: string = scope.activate(span, () => 'test'); const activateVoidType: void = scope.activate(span, () => {}); -const bindFunctionStringType: ( - arg1: string, - arg2: number -) => string = scope.bind((arg1: string, arg2: number): string => "test"); -const bindFunctionVoidType: ( - arg1: string, - arg2: number -) => void = scope.bind((arg1: string, arg2: number): void => {}); -const bindFunctionVoidTypeWithSpan: ( - arg1: string, - arg2: number -) => void = scope.bind((arg1: string, arg2: number): string => "test", span); +const bindFunctionStringType: (arg1: string, arg2: number) => string = scope.bind((arg1: string, arg2: number): string => 'test'); +const bindFunctionVoidType: (arg1: string, arg2: number) => void = scope.bind((arg1: string, arg2: number): void => {}); +const bindFunctionVoidTypeWithSpan: (arg1: string, arg2: number) => void = scope.bind((arg1: string, arg2: number): string => 'test', span); Promise.resolve(); @@ -338,29 +309,23 @@ scope.bind(promise); scope.bind(promise, span); const simpleEmitter = { - emit(eventName: string, arg1: boolean, arg2: number): void {}, + emit (eventName: string, arg1: boolean, arg2: number): void {} }; scope.bind(simpleEmitter); scope.bind(simpleEmitter, span); const emitter = { - emit(eventName: string, arg1: boolean, arg2: number): void {}, - on(eventName: string, listener: (arg1: boolean, arg2: number) => void) {}, - off(eventName: string, listener: (arg1: boolean, arg2: number) => void) {}, - addListener( - eventName: string, - listener: (arg1: boolean, arg2: number) => void - ) {}, - removeListener( - eventName: string, - listener: (arg1: boolean, arg2: number) => void - ) {}, + emit (eventName: string, arg1: boolean, arg2: number): void {}, + on (eventName: string, listener: (arg1: boolean, arg2: number) => void) {}, + off (eventName: string, listener: (arg1: boolean, arg2: number) => void) {}, + addListener (eventName: string, listener: (arg1: boolean, arg2: number) => void) {}, + removeListener (eventName: string, listener: (arg1: boolean, arg2: number) => void) {} }; scope.bind(emitter); scope.bind(emitter, span); -tracer.wrap("x", () => { +tracer.wrap('x', () => { const rumData: string = tracer.getRumData(); -}); +}) diff --git a/packages/dd-trace/src/plugins/index.js b/packages/dd-trace/src/plugins/index.js index a0fae672c7f..e4c40d99b7c 100644 --- a/packages/dd-trace/src/plugins/index.js +++ b/packages/dd-trace/src/plugins/index.js @@ -1,54 +1,53 @@ 'use strict' module.exports = { - amqp10: require('../../../datadog-plugin-amqp10/src'), - amqplib: require('../../../datadog-plugin-amqplib/src'), + 'amqp10': require('../../../datadog-plugin-amqp10/src'), + 'amqplib': require('../../../datadog-plugin-amqplib/src'), 'aws-sdk': require('../../../datadog-plugin-aws-sdk/src'), - bunyan: require('../../../datadog-plugin-bunyan/src'), + 'bunyan': require('../../../datadog-plugin-bunyan/src'), 'cassandra-driver': require('../../../datadog-plugin-cassandra-driver/src'), - connect: require('../../../datadog-plugin-connect/src'), - couchbase: require('../../../datadog-plugin-couchbase/src'), - cucumber: require('../../../datadog-plugin-cucumber/src'), - cypress: require('../../../datadog-plugin-cypress/src'), - dns: require('../../../datadog-plugin-dns/src'), - elasticsearch: require('../../../datadog-plugin-elasticsearch/src'), - express: require('../../../datadog-plugin-express/src'), - fastify: require('../../../datadog-plugin-fastify/src'), - fs: require('../../../datadog-plugin-fs/src'), + 'connect': require('../../../datadog-plugin-connect/src'), + 'couchbase': require('../../../datadog-plugin-couchbase/src'), + 'cucumber': require('../../../datadog-plugin-cucumber/src'), + 'cypress': require('../../../datadog-plugin-cypress/src'), + 'dns': require('../../../datadog-plugin-dns/src'), + 'elasticsearch': require('../../../datadog-plugin-elasticsearch/src'), + 'express': require('../../../datadog-plugin-express/src'), + 'fastify': require('../../../datadog-plugin-fastify/src'), + 'fs': require('../../../datadog-plugin-fs/src'), 'generic-pool': require('../../../datadog-plugin-generic-pool/src'), 'google-cloud-pubsub': require('../../../datadog-plugin-google-cloud-pubsub/src'), - graphql: require('../../../datadog-plugin-graphql/src'), - grpc: require('../../../datadog-plugin-grpc/src'), - hapi: require('../../../datadog-plugin-hapi/src'), - http: require('../../../datadog-plugin-http/src'), - http2: require('../../../datadog-plugin-http2/src'), - ioredis: require('../../../datadog-plugin-ioredis/src'), - jest: require('../../../datadog-plugin-jest/src'), - knex: require('../../../datadog-plugin-knex/src'), - koa: require('../../../datadog-plugin-koa/src'), - kafkajs: require('../../../datadog-plugin-kafkajs/src'), + 'graphql': require('../../../datadog-plugin-graphql/src'), + 'grpc': require('../../../datadog-plugin-grpc/src'), + 'hapi': require('../../../datadog-plugin-hapi/src'), + 'http': require('../../../datadog-plugin-http/src'), + 'http2': require('../../../datadog-plugin-http2/src'), + 'ioredis': require('../../../datadog-plugin-ioredis/src'), + 'jest': require('../../../datadog-plugin-jest/src'), + 'knex': require('../../../datadog-plugin-knex/src'), + 'koa': require('../../../datadog-plugin-koa/src'), + 'kafkajs': require('../../../datadog-plugin-kafkajs/src'), 'limitd-client': require('../../../datadog-plugin-limitd-client/src'), - memcached: require('../../../datadog-plugin-memcached/src'), + 'memcached': require('../../../datadog-plugin-memcached/src'), 'microgateway-core': require('../../../datadog-plugin-microgateway-core/src'), - mocha: require('../../../datadog-plugin-mocha/src'), - moleculer: require('../../../datadog-plugin-moleculer/src'), + 'mocha': require('../../../datadog-plugin-mocha/src'), + 'moleculer': require('../../../datadog-plugin-moleculer/src'), 'mongodb-core': require('../../../datadog-plugin-mongodb-core/src'), - mongoose: require('../../../datadog-plugin-mongoose/src'), - mysql: require('../../../datadog-plugin-mysql/src'), - mysql2: require('../../../datadog-plugin-mysql2/src'), - net: require('../../../datadog-plugin-net/src'), - next: require('../../../datadog-plugin-next/src'), - oracledb: require('../../../datadog-plugin-oracledb/src'), - paperplane: require('../../../datadog-plugin-paperplane/src'), - pg: require('../../../datadog-plugin-pg/src'), - pino: require('../../../datadog-plugin-pino/src'), - redis: require('../../../datadog-plugin-redis/src'), - restify: require('../../../datadog-plugin-restify/src'), - rhea: require('../../../datadog-plugin-rhea/src'), - router: require('../../../datadog-plugin-router/src'), - sharedb: require('../../../datadog-plugin-sharedb/src'), - tedious: require('../../../datadog-plugin-tedious/src'), - undici: require('../../../datadog-plugin-undici/src'), - when: require('../../../datadog-plugin-when/src'), - winston: require('../../../datadog-plugin-winston/src') + 'mongoose': require('../../../datadog-plugin-mongoose/src'), + 'mysql': require('../../../datadog-plugin-mysql/src'), + 'mysql2': require('../../../datadog-plugin-mysql2/src'), + 'net': require('../../../datadog-plugin-net/src'), + 'next': require('../../../datadog-plugin-next/src'), + 'oracledb': require('../../../datadog-plugin-oracledb/src'), + 'paperplane': require('../../../datadog-plugin-paperplane/src'), + 'pg': require('../../../datadog-plugin-pg/src'), + 'pino': require('../../../datadog-plugin-pino/src'), + 'redis': require('../../../datadog-plugin-redis/src'), + 'restify': require('../../../datadog-plugin-restify/src'), + 'rhea': require('../../../datadog-plugin-rhea/src'), + 'router': require('../../../datadog-plugin-router/src'), + 'sharedb': require('../../../datadog-plugin-sharedb/src'), + 'tedious': require('../../../datadog-plugin-tedious/src'), + 'undici': require('../../../datadog-plugin-undici/src'), + 'winston': require('../../../datadog-plugin-winston/src') } From 3d57adfeec1221dad51a39aa129343658c0c659e Mon Sep 17 00:00:00 2001 From: Florent Vilmart Date: Mon, 31 Jan 2022 15:48:54 -0500 Subject: [PATCH 13/22] Use async storage context --- index.d.ts | 407 ++++++++------------ packages/datadog-plugin-undici/src/index.js | 56 ++- 2 files changed, 192 insertions(+), 271 deletions(-) diff --git a/index.d.ts b/index.d.ts index 00fd25dcb3b..efb50dcfc4f 100644 --- a/index.d.ts +++ b/index.d.ts @@ -1,5 +1,5 @@ import { ClientRequest, IncomingMessage, ServerResponse } from "http"; -import { LookupFunction } from "net"; +import { LookupFunction } from 'net'; import * as opentracing from "opentracing"; import { SpanOptions } from "opentracing/lib/tracer"; @@ -81,15 +81,8 @@ export declare interface Tracer extends opentracing.Tracer { * If the `orphanable` option is set to false, the function will not be traced * unless there is already an active span or `childOf` option. */ - trace( - name: string, - fn: (span?: Span, fn?: (error?: Error) => any) => T - ): T; - trace( - name: string, - options: TraceOptions & SpanOptions, - fn: (span?: Span, done?: (error?: Error) => string) => T - ): T; + trace(name: string, fn: (span?: Span, fn?: (error?: Error) => any) => T): T; + trace(name: string, options: TraceOptions & SpanOptions, fn: (span?: Span, done?: (error?: Error) => string) => T): T; /** * Wrap a function to automatically create a span activated on its @@ -105,21 +98,9 @@ export declare interface Tracer extends opentracing.Tracer { * * The function doesn't accept a callback and doesn't return a promise, in * which case the span will finish at the end of the function execution. */ - wrap any>( - name: string, - fn: T, - requiresParent?: boolean - ): T; - wrap any>( - name: string, - options: TraceOptions & SpanOptions, - fn: T - ): T; - wrap any>( - name: string, - options: (...args: any[]) => TraceOptions & SpanOptions, - fn: T - ): T; + wrap any>(name: string, fn: T, requiresParent?: boolean): T; + wrap any>(name: string, options: TraceOptions & SpanOptions, fn: T): T; + wrap any>(name: string, options: (...args: any[]) => TraceOptions & SpanOptions, fn: T): T; /** * Create and return a string that can be included in the of a @@ -134,18 +115,18 @@ export declare interface TraceOptions extends Analyzable { * The resource you are tracing. The resource name must not be longer than * 5000 characters. */ - resource?: string; + resource?: string, /** * The service you are tracing. The service name must not be longer than * 100 characters. */ - service?: string; + service?: string, /** * The type of request. */ - type?: string; + type?: string } /** @@ -188,17 +169,17 @@ export declare interface SamplingRule { /** * Sampling rate for this rule. */ - sampleRate: Number; + sampleRate: Number /** * Service on which to apply this rule. The rule will apply to all services if not provided. */ - service?: string | RegExp; + service?: string | RegExp /** * Operation name on which to apply this rule. The rule will apply to all operation names if not provided. */ - name?: string | RegExp; + name?: string | RegExp } /** @@ -222,13 +203,13 @@ export declare interface TracerOptions { * traces with logs. * @default false */ - logInjection?: boolean; + logInjection?: boolean, /** * Whether to enable startup logs. * @default true */ - startupLogs?: boolean; + startupLogs?: boolean, /** * The service name to be used for this program. If not set, the service name @@ -257,7 +238,7 @@ export declare interface TracerOptions { /** * Whether to enable profiling. */ - profiling?: boolean; + profiling?: boolean /** * Options specific for the Dogstatsd agent. @@ -266,13 +247,13 @@ export declare interface TracerOptions { /** * The hostname of the Dogstatsd agent that the metrics will submitted to. */ - hostname?: string; + hostname?: string /** * The port of the Dogstatsd agent that the metrics will submitted to. * @default 8125 */ - port?: number; + port?: number }; /** @@ -301,19 +282,19 @@ export declare interface TracerOptions { * Whether to enable runtime metrics. * @default false */ - runtimeMetrics?: boolean; + runtimeMetrics?: boolean /** * Custom function for DNS lookups when sending requests to the agent. * @default dns.lookup() */ - lookup?: LookupFunction; + lookup?: LookupFunction /** * Protocol version to use for requests to the agent. The version configured must be supported by the agent version installed or all traces will be dropped. * @default 0.4 */ - protocolVersion?: string; + protocolVersion?: string /** * Configuration of the ingestion between the agent and the backend. @@ -322,63 +303,61 @@ export declare interface TracerOptions { /** * Controls the ingestion sample rate (between 0 and 1) between the agent and the backend. */ - sampleRate?: number; + sampleRate?: number /** * Controls the ingestion rate limit between the agent and the backend. */ - rateLimit?: number; + rateLimit?: number }; /** * Experimental features can be enabled all at once by using true or individually using key / value pairs. * @default {} */ - experimental?: - | boolean - | { - b3?: boolean; - - /** - * Whether to add an auto-generated `runtime-id` tag to metrics. - * @default false - */ - runtimeId?: boolean; - - /** - * Whether to write traces to log output, rather than send to an agent - * @default false - */ - exporter?: "log" | "agent"; - - /** - * Configuration of the priority sampler. Supports a global config and rules by span name or service name. The first matching rule is applied, and if no rule matches it falls back to the global config or on the rates provided by the agent if there is no global config. - */ - sampler?: { - /** - * Sample rate to apply globally when no other rule is matched. Omit to fallback on the dynamic rates returned by the agent instead. - */ - sampleRate?: Number; - - /** - * Global rate limit that is applied on the global sample rate and all rules. - * @default 100 - */ - rateLimit?: Number; - - /** - * Sampling rules to apply to priority sampling. - * @default [] - */ - rules?: SamplingRule[]; - }; - - /** - * Whether to enable the experimental `getRumData` method. - * @default false - */ - enableGetRumData?: boolean; - }; + experimental?: boolean | { + b3?: boolean + + /** + * Whether to add an auto-generated `runtime-id` tag to metrics. + * @default false + */ + runtimeId?: boolean + + /** + * Whether to write traces to log output, rather than send to an agent + * @default false + */ + exporter?: 'log' | 'agent' + + /** + * Configuration of the priority sampler. Supports a global config and rules by span name or service name. The first matching rule is applied, and if no rule matches it falls back to the global config or on the rates provided by the agent if there is no global config. + */ + sampler?: { + /** + * Sample rate to apply globally when no other rule is matched. Omit to fallback on the dynamic rates returned by the agent instead. + */ + sampleRate?: Number, + + /** + * Global rate limit that is applied on the global sample rate and all rules. + * @default 100 + */ + rateLimit?: Number, + + /** + * Sampling rules to apply to priority sampling. + * @default [] + */ + rules?: SamplingRule[] + } + + /** + * Whether to enable the experimental `getRumData` method. + * @default false + */ + enableGetRumData?: boolean + }; /** * Whether to load all built-in plugins. @@ -408,30 +387,25 @@ export declare interface TracerOptions { * implementation for the runtime. Only change this if you know what you are * doing. */ - scope?: - | "async_hooks" - | "async_local_storage" - | "async_resource" - | "sync" - | "noop"; + scope?: 'async_hooks' | 'async_local_storage' | 'async_resource' | 'sync' | 'noop' /** * Whether to report the hostname of the service host. This is used when the agent is deployed on a different host and cannot determine the hostname automatically. * @default false */ - reportHostname?: boolean; + reportHostname?: boolean /** * A string representing the minimum tracer log level to use when debug logging is enabled * @default 'debug' */ - logLevel?: "error" | "debug"; + logLevel?: 'error' | 'debug' /** * If false, require a parent in order to trace. * @default true */ - orphanable?: boolean; + orphanable?: boolean } /** @hidden */ @@ -439,14 +413,8 @@ interface EventEmitter { emit(eventName: string | symbol, ...args: any[]): any; on?(eventName: string | symbol, listener: (...args: any[]) => any): any; off?(eventName: string | symbol, listener: (...args: any[]) => any): any; - addListener?( - eventName: string | symbol, - listener: (...args: any[]) => any - ): any; - removeListener?( - eventName: string | symbol, - listener: (...args: any[]) => any - ): any; + addListener?(eventName: string | symbol, listener: (...args: any[]) => any): any; + removeListener?(eventName: string | symbol, listener: (...args: any[]) => any): any; } /** @hidden */ @@ -481,7 +449,7 @@ export declare interface Scope { * @param {Function} fn Function that will have the span activated on its scope. * @returns The return value of the provided function. */ - activate(span: Span, fn: (...args: any[]) => T): T; + activate(span: Span, fn: ((...args: any[]) => T)): T; /** * Binds a target to the provided span, or the active span if omitted. @@ -498,56 +466,55 @@ export declare interface Scope { /** @hidden */ interface Plugins { - amqp10: plugins.amqp10; - amqplib: plugins.amqplib; + "amqp10": plugins.amqp10; + "amqplib": plugins.amqplib; "aws-sdk": plugins.aws_sdk; - bunyan: plugins.bunyan; + "bunyan": plugins.bunyan; "cassandra-driver": plugins.cassandra_driver; - connect: plugins.connect; - couchbase: plugins.couchbase; - cucumber: plugins.cucumber; - cypress: plugins.cypress; - dns: plugins.dns; - elasticsearch: plugins.elasticsearch; - express: plugins.express; - fastify: plugins.fastify; - fs: plugins.fs; + "connect": plugins.connect; + "couchbase": plugins.couchbase; + "cucumber": plugins.cucumber; + "cypress": plugins.cypress; + "dns": plugins.dns; + "elasticsearch": plugins.elasticsearch; + "express": plugins.express; + "fastify": plugins.fastify; + "fs": plugins.fs; "generic-pool": plugins.generic_pool; "google-cloud-pubsub": plugins.google_cloud_pubsub; - graphql: plugins.graphql; - grpc: plugins.grpc; - hapi: plugins.hapi; - http: plugins.http; - http2: plugins.http2; - ioredis: plugins.ioredis; - jest: plugins.jest; - kafkajs: plugins.kafkajs; - knex: plugins.knex; - koa: plugins.koa; + "graphql": plugins.graphql; + "grpc": plugins.grpc; + "hapi": plugins.hapi; + "http": plugins.http; + "http2": plugins.http2; + "ioredis": plugins.ioredis; + "jest": plugins.jest; + "kafkajs": plugins.kafkajs + "knex": plugins.knex; + "koa": plugins.koa; "limitd-client": plugins.limitd_client; - memcached: plugins.memcached; + "memcached": plugins.memcached; "microgateway-core": plugins.microgateway_core; - mocha: plugins.mocha; - moleculer: plugins.moleculer; + "mocha": plugins.mocha; + "moleculer": plugins.moleculer; "mongodb-core": plugins.mongodb_core; - mongoose: plugins.mongoose; - mysql: plugins.mysql; - mysql2: plugins.mysql2; - net: plugins.net; - next: plugins.next; - oracledb: plugins.oracledb; - paperplane: plugins.paperplane; - pg: plugins.pg; - pino: plugins.pino; - redis: plugins.redis; - restify: plugins.restify; - rhea: plugins.rhea; - router: plugins.router; - sharedb: plugins.sharedb; - tedious: plugins.tedious; - undici: plugins.undici; - when: plugins.when; - winston: plugins.winston; + "mongoose": plugins.mongoose; + "mysql": plugins.mysql; + "mysql2": plugins.mysql2; + "net": plugins.net; + "next": plugins.next; + "oracledb": plugins.oracledb; + "paperplane": plugins.paperplane; + "pg": plugins.pg; + "pino": plugins.pino; + "redis": plugins.redis; + "restify": plugins.restify; + "rhea": plugins.rhea; + "router": plugins.router; + "sharedb": plugins.sharedb; + "tedious": plugins.tedious; + "undici": plugins.undici; + "winston": plugins.winston; } /** @hidden */ @@ -583,11 +550,7 @@ declare namespace plugins { * * @default /^.*$/ */ - allowlist?: - | string - | RegExp - | ((url: string) => boolean) - | (string | RegExp | ((url: string) => boolean))[]; + allowlist?: string | RegExp | ((url: string) => boolean) | (string | RegExp | ((url: string) => boolean))[]; /** * Deprecated in favor of `allowlist`. @@ -595,11 +558,7 @@ declare namespace plugins { * @deprecated * @hidden */ - whitelist?: - | string - | RegExp - | ((url: string) => boolean) - | (string | RegExp | ((url: string) => boolean))[]; + whitelist?: string | RegExp | ((url: string) => boolean) | (string | RegExp | ((url: string) => boolean))[]; /** * List of URLs that should not be instrumented. Takes precedence over @@ -607,11 +566,7 @@ declare namespace plugins { * * @default [] */ - blocklist?: - | string - | RegExp - | ((url: string) => boolean) - | (string | RegExp | ((url: string) => boolean))[]; + blocklist?: string | RegExp | ((url: string) => boolean) | (string | RegExp | ((url: string) => boolean))[]; /** * Deprecated in favor of `blocklist`. @@ -619,11 +574,7 @@ declare namespace plugins { * @deprecated * @hidden */ - blacklist?: - | string - | RegExp - | ((url: string) => boolean) - | (string | RegExp | ((url: string) => boolean))[]; + blacklist?: string | RegExp | ((url: string) => boolean) | (string | RegExp | ((url: string) => boolean))[]; /** * An array of headers to include in the span metadata. @@ -660,11 +611,7 @@ declare namespace plugins { /** * Hook to execute just before the request span finishes. */ - request?: ( - span?: opentracing.Span, - req?: IncomingMessage, - res?: ServerResponse - ) => any; + request?: (span?: opentracing.Span, req?: IncomingMessage, res?: ServerResponse) => any; }; /** @@ -700,21 +647,13 @@ declare namespace plugins { /** * Hook to execute just before the request span finishes. */ - request?: ( - span?: opentracing.Span, - req?: ClientRequest, - res?: IncomingMessage - ) => any; + request?: (span?: opentracing.Span, req?: ClientRequest, res?: IncomingMessage) => any; }; /** * List of urls to which propagation headers should not be injected */ - propagationBlocklist?: - | string - | RegExp - | ((url: string) => boolean) - | (string | RegExp | ((url: string) => boolean))[]; + propagationBlocklist?: string | RegExp | ((url: string) => boolean) | (string | RegExp | ((url: string) => boolean))[]; } /** @hidden */ @@ -755,9 +694,7 @@ declare namespace plugins { * the key/value pairs to record. For example, using * `variables => variables` would record all variables. */ - metadata?: - | string[] - | ((variables: { [key: string]: any }) => { [key: string]: any }); + metadata?: string[] | ((variables: { [key: string]: any }) => { [key: string]: any }); } /** @hidden */ @@ -905,14 +842,14 @@ declare namespace plugins { /** @hidden */ interface ExecutionArgs { - schema: any; - document: any; - rootValue?: any; - contextValue?: any; - variableValues?: any; - operationName?: string; - fieldResolver?: any; - typeResolver?: any; + schema: any, + document: any, + rootValue?: any, + contextValue?: any, + variableValues?: any, + operationName?: string, + fieldResolver?: any, + typeResolver?: any, } /** @@ -963,9 +900,7 @@ declare namespace plugins { * the key/value pairs to record. For example, using * `variables => variables` would record all variables. */ - variables?: - | string[] - | ((variables: { [key: string]: any }) => { [key: string]: any }); + variables?: string[] | ((variables: { [key: string]: any }) => { [key: string]: any }); /** * Whether to collapse list items into a single element. (i.e. single @@ -994,7 +929,7 @@ declare namespace plugins { execute?: (span?: Span, args?: ExecutionArgs, res?: any) => void; validate?: (span?: Span, document?: any, errors?: any) => void; parse?: (span?: Span, source?: any, document?: any) => void; - }; + } } /** @@ -1005,12 +940,12 @@ declare namespace plugins { /** * Configuration for gRPC clients. */ - client?: Grpc; + client?: Grpc, /** * Configuration for gRPC servers. */ - server?: Grpc; + server?: Grpc } /** @@ -1031,12 +966,12 @@ declare namespace plugins { /** * Configuration for HTTP clients. */ - client?: HttpClient | boolean; + client?: HttpClient | boolean, /** * Configuration for HTTP servers. */ - server?: HttpServer | boolean; + server?: HttpServer | boolean /** * Hooks to run before spans are finished. @@ -1065,12 +1000,12 @@ declare namespace plugins { /** * Configuration for HTTP clients. */ - client?: Http2Client | boolean; + client?: Http2Client | boolean, /** * Configuration for HTTP servers. */ - server?: Http2Server | boolean; + server?: Http2Server | boolean } /** @@ -1083,11 +1018,7 @@ declare namespace plugins { * * @default /^.*$/ */ - allowlist?: - | string - | RegExp - | ((command: string) => boolean) - | (string | RegExp | ((command: string) => boolean))[]; + allowlist?: string | RegExp | ((command: string) => boolean) | (string | RegExp | ((command: string) => boolean))[]; /** * Deprecated in favor of `allowlist`. @@ -1095,11 +1026,7 @@ declare namespace plugins { * @deprecated * @hidden */ - whitelist?: - | string - | RegExp - | ((command: string) => boolean) - | (string | RegExp | ((command: string) => boolean))[]; + whitelist?: string | RegExp | ((command: string) => boolean) | (string | RegExp | ((command: string) => boolean))[]; /** * List of commands that should not be instrumented. Takes precedence over @@ -1107,11 +1034,7 @@ declare namespace plugins { * * @default [] */ - blocklist?: - | string - | RegExp - | ((command: string) => boolean) - | (string | RegExp | ((command: string) => boolean))[]; + blocklist?: string | RegExp | ((command: string) => boolean) | (string | RegExp | ((command: string) => boolean))[]; /** * Deprecated in favor of `blocklist`. @@ -1119,11 +1042,7 @@ declare namespace plugins { * @deprecated * @hidden */ - blacklist?: - | string - | RegExp - | ((command: string) => boolean) - | (string | RegExp | ((command: string) => boolean))[]; + blacklist?: string | RegExp | ((command: string) => boolean) | (string | RegExp | ((command: string) => boolean))[]; /** * Whether to use a different service name for each Redis instance based @@ -1186,7 +1105,7 @@ declare namespace plugins { * This plugin automatically instruments the * [moleculer](https://moleculer.services/) module. */ - interface moleculer extends Moleculer { + interface moleculer extends Moleculer { /** * Configuration for Moleculer clients. Set to false to disable client * instrumentation. @@ -1238,15 +1157,11 @@ declare namespace plugins { /** * Hooks to run before spans are finished. */ - hooks?: { + hooks?: { /** * Hook to execute just before the request span finishes. */ - request?: ( - span?: opentracing.Span, - req?: IncomingMessage, - res?: ServerResponse - ) => any; + request?: (span?: opentracing.Span, req?: IncomingMessage, res?: ServerResponse) => any; }; } @@ -1265,7 +1180,7 @@ declare namespace plugins { * This plugin automatically instruments the * [paperplane](https://github.com/articulate/paperplane) module. */ - interface paperplane extends HttpServer {} + interface paperplane extends HttpServer {} /** * This plugin automatically instruments the @@ -1296,11 +1211,7 @@ declare namespace plugins { * * @default /^.*$/ */ - allowlist?: - | string - | RegExp - | ((command: string) => boolean) - | (string | RegExp | ((command: string) => boolean))[]; + allowlist?: string | RegExp | ((command: string) => boolean) | (string | RegExp | ((command: string) => boolean))[]; /** * Deprecated in favor of `allowlist`. @@ -1308,11 +1219,7 @@ declare namespace plugins { * deprecated * @hidden */ - whitelist?: - | string - | RegExp - | ((command: string) => boolean) - | (string | RegExp | ((command: string) => boolean))[]; + whitelist?: string | RegExp | ((command: string) => boolean) | (string | RegExp | ((command: string) => boolean))[]; /** * List of commands that should not be instrumented. Takes precedence over @@ -1320,11 +1227,7 @@ declare namespace plugins { * * @default [] */ - blocklist?: - | string - | RegExp - | ((command: string) => boolean) - | (string | RegExp | ((command: string) => boolean))[]; + blocklist?: string | RegExp | ((command: string) => boolean) | (string | RegExp | ((command: string) => boolean))[]; /** * Deprecated in favor of `blocklist`. @@ -1332,11 +1235,7 @@ declare namespace plugins { * @deprecated * @hidden */ - blacklist?: - | string - | RegExp - | ((command: string) => boolean) - | (string | RegExp | ((command: string) => boolean))[]; + blacklist?: string | RegExp | ((command: string) => boolean) | (string | RegExp | ((command: string) => boolean))[]; } /** @@ -1390,12 +1289,6 @@ declare namespace plugins { */ interface undici extends HttpClient {} - /** - * This plugin patches the [when](https://github.com/cujojs/when) - * module to bind the promise callback the the caller context. - */ - interface when extends Integration {} - /** * This plugin patches the [winston](https://github.com/winstonjs/winston) * to automatically inject trace identifiers in log records when the diff --git a/packages/datadog-plugin-undici/src/index.js b/packages/datadog-plugin-undici/src/index.js index 0d73aca1c01..22cd1b27ecc 100644 --- a/packages/datadog-plugin-undici/src/index.js +++ b/packages/datadog-plugin-undici/src/index.js @@ -3,12 +3,12 @@ const url = require('url') const opentracing = require('opentracing') const log = require('../../dd-trace/src/log') -const constants = require('../../dd-trace/src/constants') const tags = require('../../../ext/tags') const kinds = require('../../../ext/kinds') const formats = require('../../../ext/formats') const urlFilter = require('../../dd-trace/src/plugins/util/urlfilter') const analyticsSampler = require('../../dd-trace/src/analytics_sampler') +const { AsyncResource, AsyncLocalStorage } = require('async_hooks') const Reference = opentracing.Reference @@ -19,7 +19,8 @@ const HTTP_RESPONSE_HEADERS = tags.HTTP_RESPONSE_HEADERS const SPAN_KIND = tags.SPAN_KIND const CLIENT = kinds.CLIENT const REFERENCE_CHILD_OF = opentracing.REFERENCE_CHILD_OF -const REFERENCE_NOOP = constants.REFERENCE_NOOP + +const asyncLocalStorage = new AsyncLocalStorage() function addError (span, error) { span.addTags({ @@ -81,29 +82,24 @@ function diagnostics (tracer, config) { channels.errorChannel.subscribe(handleRequestError) channels.headersChannel.subscribe(handleRequestHeaders) - // We use a weakmap here to store the request / spans const requestSpansMap = new WeakMap() function handleRequestCreate ({ request }) { const method = (request.method || 'GET').toUpperCase() - const scope = tracer.scope() - const childOf = scope.active() const path = request.path ? request.path.split(/[?#]/)[0] : '/' const uri = `${request.origin}${path}` - const type = config.filter(uri) ? REFERENCE_CHILD_OF : REFERENCE_NOOP - const span = tracer.startSpan('http.request', { - references: [new Reference(type, childOf)], - tags: { - [SPAN_KIND]: CLIENT, + const span = asyncLocalStorage.getStore() + if (span) { + span.addTags({ 'resource.name': method, 'span.type': 'http', 'http.method': method, 'http.url': uri, 'service.name': getServiceName(tracer, config, request) - } - }) + }) + } requestSpansMap.set(request, span) @@ -119,7 +115,7 @@ function diagnostics (tracer, config) { } function handleRequestError ({ request, error }) { - const span = requestSpansMap.get(request) + const span = asyncLocalStorage.getStore() addError(span, error) finish(request, null, span, config) } @@ -306,11 +302,43 @@ function getHooks (config) { return { request } } +function patch (undici, methodName, tracer, config) { + this.wrap(undici, methodName, fn => makeRequestTrace(fn)) + + function makeRequestTrace (request) { + return function requestTrace () { + // Bind the callback for async resources + if (arguments.length === 3) { + arguments[2] = AsyncResource.bind(arguments[2]) + } + const scope = tracer.scope() + const childOf = scope.active() + const span = tracer.startSpan('http.request', { + references: [new Reference(REFERENCE_CHILD_OF, childOf)], + tags: { + [SPAN_KIND]: CLIENT + } + }) + + return asyncLocalStorage.run(span, () => { + return request.apply(this, arguments) + }) + } + } +} + module.exports = [ { name: 'undici', versions: ['>=4.7.1'], - patch: function (_, tracer, config) { + patch: function (undici, tracer, config) { + patch.call(this, undici, 'request', tracer, config) + patch.call(this, undici, 'upgrade', tracer, config) + patch.call(this, undici, 'connect', tracer, config) + patch.call(this, undici, 'fetch', tracer, config) + + // Stream take 3 arguments + // patch.call(this, undici, 'stream', tracer, config) this.unpatch = diagnostics.call(this, tracer, config) } } From f63858cb1a5081b96c7c6d7a1ae189653b5aa65a Mon Sep 17 00:00:00 2001 From: Florent Vilmart Date: Mon, 31 Jan 2022 16:29:20 -0500 Subject: [PATCH 14/22] Patch Client.prototype.request --- packages/datadog-plugin-undici/src/index.js | 14 +++++--------- packages/datadog-plugin-undici/test/index.spec.js | 6 ++++-- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/packages/datadog-plugin-undici/src/index.js b/packages/datadog-plugin-undici/src/index.js index 22cd1b27ecc..7ef37dca929 100644 --- a/packages/datadog-plugin-undici/src/index.js +++ b/packages/datadog-plugin-undici/src/index.js @@ -1,7 +1,6 @@ 'use strict' const url = require('url') -const opentracing = require('opentracing') const log = require('../../dd-trace/src/log') const tags = require('../../../ext/tags') const kinds = require('../../../ext/kinds') @@ -10,15 +9,12 @@ const urlFilter = require('../../dd-trace/src/plugins/util/urlfilter') const analyticsSampler = require('../../dd-trace/src/analytics_sampler') const { AsyncResource, AsyncLocalStorage } = require('async_hooks') -const Reference = opentracing.Reference - const HTTP_HEADERS = formats.HTTP_HEADERS const HTTP_STATUS_CODE = tags.HTTP_STATUS_CODE const HTTP_REQUEST_HEADERS = tags.HTTP_REQUEST_HEADERS const HTTP_RESPONSE_HEADERS = tags.HTTP_RESPONSE_HEADERS const SPAN_KIND = tags.SPAN_KIND const CLIENT = kinds.CLIENT -const REFERENCE_CHILD_OF = opentracing.REFERENCE_CHILD_OF const asyncLocalStorage = new AsyncLocalStorage() @@ -99,10 +95,9 @@ function diagnostics (tracer, config) { 'http.url': uri, 'service.name': getServiceName(tracer, config, request) }) + requestSpansMap.set(request, span) } - requestSpansMap.set(request, span) - if (!(hasAmazonSignature(request) || !config.propagationFilter(uri))) { const injectedHeaders = {} tracer.inject(span, HTTP_HEADERS, injectedHeaders) @@ -115,7 +110,7 @@ function diagnostics (tracer, config) { } function handleRequestError ({ request, error }) { - const span = asyncLocalStorage.getStore() + const span = requestSpansMap.get(request) addError(span, error) finish(request, null, span, config) } @@ -314,7 +309,7 @@ function patch (undici, methodName, tracer, config) { const scope = tracer.scope() const childOf = scope.active() const span = tracer.startSpan('http.request', { - references: [new Reference(REFERENCE_CHILD_OF, childOf)], + childOf, tags: { [SPAN_KIND]: CLIENT } @@ -336,8 +331,9 @@ module.exports = [ patch.call(this, undici, 'upgrade', tracer, config) patch.call(this, undici, 'connect', tracer, config) patch.call(this, undici, 'fetch', tracer, config) + patch.call(this, undici.Client.prototype, 'request', tracer, config) - // Stream take 3 arguments + // Stream take different args arguments // patch.call(this, undici, 'stream', tracer, config) this.unpatch = diagnostics.call(this, tracer, config) } diff --git a/packages/datadog-plugin-undici/test/index.spec.js b/packages/datadog-plugin-undici/test/index.spec.js index 80e76fd7f5e..e7d36d26c4d 100644 --- a/packages/datadog-plugin-undici/test/index.spec.js +++ b/packages/datadog-plugin-undici/test/index.spec.js @@ -550,7 +550,9 @@ describe('undici', () => { }) }) - it('should skip requests to the agent', (done) => { + /// Undici is not the client making requests to the agent + // this seems irrelevant at that time + xit('should skip requests to the agent', (done) => { const app = express() app.get('/user', (req, res) => { @@ -560,7 +562,7 @@ describe('undici', () => { getPort().then((port) => { const timer = setTimeout(done, 100) - agent.use(() => { + agent.use((traces) => { done(new Error('Request to the agent was traced.')) clearTimeout(timer) }) From cbb32d63e7281394f98524bc1ce627043806d63c Mon Sep 17 00:00:00 2001 From: Florent Vilmart Date: Mon, 31 Jan 2022 16:36:56 -0500 Subject: [PATCH 15/22] use node 16 only --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index e63a32c1160..91293faea21 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -826,7 +826,7 @@ workflows: - node-undici: matrix: parameters: - node-version: ["14"] + node-version: ["16"] - node-when: *matrix-supported-node-versions - node-winston: *matrix-supported-node-versions - codecov: From 7b51211c6188e9b7097ed22a5d9ade08f20ba672 Mon Sep 17 00:00:00 2001 From: Florent Vilmart Date: Mon, 31 Jan 2022 17:15:27 -0500 Subject: [PATCH 16/22] name all spans with undici.methdoName - adds specs --- packages/datadog-plugin-undici/src/index.js | 2 +- .../datadog-plugin-undici/test/index.spec.js | 112 ++++++++++++++++++ 2 files changed, 113 insertions(+), 1 deletion(-) diff --git a/packages/datadog-plugin-undici/src/index.js b/packages/datadog-plugin-undici/src/index.js index 7ef37dca929..5a43d65eb3b 100644 --- a/packages/datadog-plugin-undici/src/index.js +++ b/packages/datadog-plugin-undici/src/index.js @@ -308,7 +308,7 @@ function patch (undici, methodName, tracer, config) { } const scope = tracer.scope() const childOf = scope.active() - const span = tracer.startSpan('http.request', { + const span = tracer.startSpan(`undici.${methodName}`, { childOf, tags: { [SPAN_KIND]: CLIENT diff --git a/packages/datadog-plugin-undici/test/index.spec.js b/packages/datadog-plugin-undici/test/index.spec.js index e7d36d26c4d..975dbbdbfdb 100644 --- a/packages/datadog-plugin-undici/test/index.spec.js +++ b/packages/datadog-plugin-undici/test/index.spec.js @@ -35,6 +35,118 @@ describe('undici', () => { return agent.close() }) + + describe('all methods', () => { + beforeEach(() => { + return agent.load('undici').then(() => { + undici = require(`../../../versions/undici@${version}`).get() + express = require('express') + }) + }) + + it('should do automatic instrumentation for request', (done) => { + const app = express() + + app.get('/user', (_, res) => { + res.status(200).send() + }) + + getPort().then((port) => { + agent + .use((traces) => { + expect(traces[0][0]).to.have.property( + 'service', + 'test-http-client' + ) + 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', 'client') + 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', + '200' + ) + }) + .then(done) + .catch(done) + + appListener = server(app, port, () => { + undici.request(`http://localhost:${port}/user`) + }) + }) + }) + it('should do automatic instrumentation for fetch', (done) => { + const app = express() + + app.get('/user', (_, res) => { + res.status(200).send() + }) + + getPort().then((port) => { + agent + .use((traces) => { + expect(traces[0][0]).to.have.property( + 'service', + 'test-http-client' + ) + 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', 'client') + 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', + '200' + ) + }) + .then(done) + .catch(done) + + appListener = server(app, port, () => { + undici.fetch(`http://localhost:${port}/user`) + }) + }) + }) + it('should do automatic instrumentation for connect', (done) => { + const app = express() + + app.connect('/user', (_, res) => { + res.status(200).send() + }) + + getPort().then((port) => { + agent + .use((traces) => { + expect(traces[0][0]).to.have.property( + 'service', + 'test-http-client' + ) + expect(traces[0][0]).to.have.property('type', 'http') + expect(traces[0][0]).to.have.property('resource', 'CONNECT') + expect(traces[0][0].meta).to.have.property('span.kind', 'client') + expect(traces[0][0].meta).to.have.property( + 'http.url', + `http://localhost:${port}/user` + ) + expect(traces[0][0].meta).to.have.property('http.method', 'CONNECT') + }) + .then(done) + .catch(done) + + appListener = server(app, port, () => { + undici.connect(`http://localhost:${port}/user`) + }) + }) + }) + }) + describe('without configuration', () => { beforeEach(() => { return agent.load('undici').then(() => { From 303b2f4d5ed00df0525d1e86c4b769bd90e527d0 Mon Sep 17 00:00:00 2001 From: Florent Vilmart Date: Mon, 31 Jan 2022 17:29:21 -0500 Subject: [PATCH 17/22] adds pending specs for pipeline and stream --- packages/datadog-plugin-undici/src/index.js | 4 +- .../datadog-plugin-undici/test/index.spec.js | 68 +++++++++++++++++++ 2 files changed, 70 insertions(+), 2 deletions(-) diff --git a/packages/datadog-plugin-undici/src/index.js b/packages/datadog-plugin-undici/src/index.js index 5a43d65eb3b..d7f9e7b1627 100644 --- a/packages/datadog-plugin-undici/src/index.js +++ b/packages/datadog-plugin-undici/src/index.js @@ -331,10 +331,10 @@ module.exports = [ patch.call(this, undici, 'upgrade', tracer, config) patch.call(this, undici, 'connect', tracer, config) patch.call(this, undici, 'fetch', tracer, config) + patch.call(this, undici, 'pipeline', tracer, config) + patch.call(this, undici, 'stream', tracer, config) patch.call(this, undici.Client.prototype, 'request', tracer, config) - // Stream take different args arguments - // patch.call(this, undici, 'stream', tracer, config) this.unpatch = diagnostics.call(this, tracer, config) } } diff --git a/packages/datadog-plugin-undici/test/index.spec.js b/packages/datadog-plugin-undici/test/index.spec.js index 975dbbdbfdb..acae7c50b6b 100644 --- a/packages/datadog-plugin-undici/test/index.spec.js +++ b/packages/datadog-plugin-undici/test/index.spec.js @@ -114,6 +114,7 @@ describe('undici', () => { }) }) }) + it('should do automatic instrumentation for connect', (done) => { const app = express() @@ -145,6 +146,73 @@ describe('undici', () => { }) }) }) + + xit('should do automatic instrumentation for pipeline', (done) => { + const app = express() + + app.get('/user', (_, res) => { + res.status(200).send() + }) + + getPort().then((port) => { + agent + .use((traces) => { + console.log(traces) + expect(traces[0][0]).to.have.property( + 'service', + 'test-http-client' + ) + 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', 'client') + 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') + }) + .then(done) + .catch(done) + + appListener = server(app, port, () => { + undici.pipeline(`http://localhost:${port}/user`, (_) => { + }) + }) + }) + }) + + xit('should do automatic instrumentation for stream', (done) => { + const app = express() + + app.connect('/user', (_, res) => { + res.status(200).send() + }) + + getPort().then((port) => { + agent + .use((traces) => { + console.log(traces) + expect(traces[0][0]).to.have.property( + 'service', + 'test-http-client' + ) + 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', 'client') + 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') + }) + .then(done) + .catch(done) + + appListener = server(app, port, () => { + undici.stream(`http://localhost:${port}/user`) + }) + }) + }) }) describe('without configuration', () => { From 7f85c87e911d117e9b0b4cb0cbc6854a3fa2a456 Mon Sep 17 00:00:00 2001 From: Florent Vilmart Date: Mon, 31 Jan 2022 18:13:34 -0500 Subject: [PATCH 18/22] refactor: move http client commons in util/web --- packages/datadog-plugin-http/src/client.js | 77 +----------- packages/datadog-plugin-undici/src/index.js | 133 ++------------------ packages/dd-trace/src/plugins/util/web.js | 100 +++++++++++++++ 3 files changed, 112 insertions(+), 198 deletions(-) diff --git a/packages/datadog-plugin-http/src/client.js b/packages/datadog-plugin-http/src/client.js index cc14d26bd68..f276fb9b202 100644 --- a/packages/datadog-plugin-http/src/client.js +++ b/packages/datadog-plugin-http/src/client.js @@ -8,6 +8,7 @@ const formats = require('../../../ext/formats') const urlFilter = require('../../dd-trace/src/plugins/util/urlfilter') const analyticsSampler = require('../../dd-trace/src/analytics_sampler') const { storage } = require('../../datadog-core') +const { addErrorToSpan, getServiceName, hasAmazonSignature, client } = require('../../dd-trace/src/plugins/util/web') const HTTP_HEADERS = formats.HTTP_HEADERS const HTTP_STATUS_CODE = tags.HTTP_STATUS_CODE @@ -17,7 +18,7 @@ const SPAN_KIND = tags.SPAN_KIND const CLIENT = kinds.CLIENT function patch (http, methodName, tracer, config) { - config = normalizeConfig(tracer, config) + config = normalizeConfig(config) this.wrap(http, methodName, fn => makeRequestTrace(fn)) function makeRequestTrace (request) { @@ -84,7 +85,7 @@ function patch (http, methodName, tracer, config) { break } case 'error': - addError(span, arg) + addErrorToSpan(span, arg) case 'abort': // eslint-disable-line no-fallthrough case 'timeout': // eslint-disable-line no-fallthrough finish(req, null, span, config) @@ -119,16 +120,6 @@ function patch (http, methodName, tracer, config) { span.finish() } - function addError (span, error) { - span.addTags({ - 'error.type': error.name, - 'error.msg': error.message, - 'error.stack': error.stack - }) - - return error - } - function addRequestHeaders (req, span, config) { config.headers.forEach(key => { const value = req.getHeader(key) @@ -220,54 +211,6 @@ function patch (http, methodName, tracer, config) { } } -function getHost (options) { - if (typeof options === 'string') { - return url.parse(options).host - } - - const hostname = options.hostname || options.host || 'localhost' - const port = options.port - - return [hostname, port].filter(val => val).join(':') -} - -function getServiceName (tracer, config, options) { - if (config.splitByDomain) { - return getHost(options) - } else if (config.service) { - return config.service - } - - return `${tracer._service}-http-client` -} - -function hasAmazonSignature (options) { - if (!options) { - return false - } - - if (options.headers) { - const headers = Object.keys(options.headers) - .reduce((prev, next) => Object.assign(prev, { - [next.toLowerCase()]: options.headers[next] - }), {}) - - if (headers['x-amz-signature']) { - return true - } - - if ([].concat(headers['authorization']).some(startsWith('AWS4-HMAC-SHA256'))) { - return true - } - } - - return options.path && options.path.toLowerCase().indexOf('x-amz-signature=') !== -1 -} - -function startsWith (searchString) { - return value => String(value).startsWith(searchString) -} - function unpatch (http) { this.unwrap(http, 'request') this.unwrap(http, 'get') @@ -290,20 +233,10 @@ function getFilter (config) { return urlFilter.getFilter(config) } -function normalizeConfig (tracer, config) { +function normalizeConfig (config) { config = config.client || config - const validateStatus = getStatusValidator(config) - const propagationFilter = getFilter({ blocklist: config.propagationBlocklist }) - const headers = getHeaders(config) - const hooks = getHooks(config) - - return Object.assign({}, config, { - validateStatus, - propagationFilter, - headers, - hooks - }) + return client.normalizeConfig(config) } function getHeaders (config) { diff --git a/packages/datadog-plugin-undici/src/index.js b/packages/datadog-plugin-undici/src/index.js index d7f9e7b1627..8ca91726be4 100644 --- a/packages/datadog-plugin-undici/src/index.js +++ b/packages/datadog-plugin-undici/src/index.js @@ -8,6 +8,7 @@ const formats = require('../../../ext/formats') const urlFilter = require('../../dd-trace/src/plugins/util/urlfilter') const analyticsSampler = require('../../dd-trace/src/analytics_sampler') const { AsyncResource, AsyncLocalStorage } = require('async_hooks') +const { addErrorToSpan, getServiceName, hasAmazonSignature, client: { normalizeConfig } } = require('../../dd-trace/src/plugins/util/web') const HTTP_HEADERS = formats.HTTP_HEADERS const HTTP_STATUS_CODE = tags.HTTP_STATUS_CODE @@ -18,15 +19,6 @@ const CLIENT = kinds.CLIENT const asyncLocalStorage = new AsyncLocalStorage() -function addError (span, error) { - span.addTags({ - 'error.type': error.name, - 'error.msg': error.message, - 'error.stack': error.stack - }) - return error -} - function parseHeaders (headers) { const pairs = headers .split('\r\n') @@ -66,7 +58,7 @@ function diagnostics (tracer, config) { ) return () => {} } - config = normalizeConfig(tracer, config) + config = normalizeConfig(config) channels.requestChannel = diagnosticsChannel.channel('undici:request:create') channels.headersChannel = diagnosticsChannel.channel( @@ -93,12 +85,14 @@ function diagnostics (tracer, config) { 'span.type': 'http', 'http.method': method, 'http.url': uri, - 'service.name': getServiceName(tracer, config, request) + 'service.name': getServiceName(tracer, config, request.origin) }) requestSpansMap.set(request, span) } - if (!(hasAmazonSignature(request) || !config.propagationFilter(uri))) { + const headers = typeof request.headers == 'string' ? parseHeaders(request.headers) : request.headers; + + if (!(hasAmazonSignature({ ...request, headers }) || !config.propagationFilter(uri))) { const injectedHeaders = {} tracer.inject(span, HTTP_HEADERS, injectedHeaders) Object.entries(injectedHeaders).forEach(([key, value]) => { @@ -111,7 +105,7 @@ function diagnostics (tracer, config) { function handleRequestError ({ request, error }) { const span = requestSpansMap.get(request) - addError(span, error) + addErrorToSpan(span, error) finish(request, null, span, config) } @@ -184,119 +178,6 @@ function finish (req, res, span, config) { span.finish() } -function getHost (options) { - return url.parse(options.origin).host -} - -function getServiceName (tracer, config, options) { - if (config.splitByDomain) { - return getHost(options) - } else if (config.service) { - return config.service - } - - return `${tracer._service}-http-client` -} - -function hasAmazonSignature (options) { - if (!options) { - return false - } - - if (options.headers) { - let headers = {} - if (typeof options.headers === 'string') { - headers = parseHeaders(options.headers) - } else { - headers = Object.keys(options.headers).reduce( - (prev, next) => - Object.assign(prev, { - [next.toLowerCase()]: options.headers[next] - }), - {} - ) - } - - if (headers['x-amz-signature']) { - return true - } - - if ( - [].concat(headers['authorization']).some(startsWith('AWS4-HMAC-SHA256')) - ) { - return true - } - } - - return ( - options.path && - options.path.toLowerCase().indexOf('x-amz-signature=') !== -1 - ) -} - -function startsWith (searchString) { - return (value) => String(value).startsWith(searchString) -} - -function getStatusValidator (config) { - if (typeof config.validateStatus === 'function') { - return config.validateStatus - } else if (config.hasOwnProperty('validateStatus')) { - log.error('Expected `validateStatus` to be a function.') - } - return (code) => code < 400 || code >= 500 -} - -function getFilter (tracer, config) { - const blocklist = tracer._url ? [getAgentFilter(tracer._url)] : [] - - config = Object.assign({}, config, { - blocklist: blocklist.concat(config.blocklist || []) - }) - - return urlFilter.getFilter(config) -} - -function getAgentFilter (url) { - // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#Escaping - const agentFilter = url.href.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') - - return RegExp(`^${agentFilter}.*$`, 'i') -} - -function normalizeConfig (tracer, config) { - const validateStatus = getStatusValidator(config) - const filter = getFilter(tracer, config) - const propagationFilter = getFilter(tracer, { - blocklist: config.propagationBlocklist - }) - const headers = getHeaders(config) - const hooks = getHooks(config) - - return Object.assign({}, config, { - validateStatus, - filter, - propagationFilter, - headers, - hooks - }) -} - -function getHeaders (config) { - if (!Array.isArray(config.headers)) return [] - - return config.headers - .filter((key) => typeof key === 'string') - .map((key) => key.toLowerCase()) -} - -function getHooks (config) { - const noop = () => {} - const request = (config.hooks && config.hooks.request) || noop - - return { request } -} - function patch (undici, methodName, tracer, config) { this.wrap(undici, methodName, fn => makeRequestTrace(fn)) diff --git a/packages/dd-trace/src/plugins/util/web.js b/packages/dd-trace/src/plugins/util/web.js index 1a76bf039f8..6c3645a0da5 100644 --- a/packages/dd-trace/src/plugins/util/web.js +++ b/packages/dd-trace/src/plugins/util/web.js @@ -1,5 +1,6 @@ 'use strict' +const url = require('url') const uniq = require('lodash.uniq') const analyticsSampler = require('../../analytics_sampler') const FORMAT_HTTP_HEADERS = require('opentracing').FORMAT_HTTP_HEADERS @@ -29,6 +30,21 @@ const HTTP2_HEADER_AUTHORITY = ':authority' const HTTP2_HEADER_SCHEME = ':scheme' const HTTP2_HEADER_PATH = ':path' +function getHost (options) { + if (typeof options === 'string') { + return url.parse(options).host + } + + const hostname = options.hostname || options.host || 'localhost' + const port = options.port + + return [hostname, port].filter(val => val).join(':') +} + +function startsWith (searchString) { + return (value) => String(value).startsWith(searchString) +} + const web = { // Ensure the configuration has the correct structure and defaults. normalizeConfig (config) { @@ -49,6 +65,22 @@ const web = { }) }, + client: { + normalizeConfig (config) { + const validateStatus = getClientStatusValidator(config) + const propagationFilter = getFilter({ blocklist: config.propagationBlocklist }) + const headers = getHeaders(config) + const hooks = getHooks(config) + + return Object.assign({}, config, { + validateStatus, + propagationFilter, + headers, + hooks + }) + } + }, + // Start a span and activate a scope for a request. instrument (tracer, config, req, res, name, callback) { this.patch(req) @@ -144,6 +176,48 @@ const web = { } }, + getServiceName (tracer, config, options) { + if (config.splitByDomain) { + return getHost(options) + } else if (config.service) { + return config.service + } + + return `${tracer._service}-http-client` + }, + + addErrorToSpan(span, error) { + span.addTags({ + 'error.type': error.name, + 'error.msg': error.message, + 'error.stack': error.stack + }) + return error + }, + + hasAmazonSignature (options) { + if (!options) { + return false + } + + if (options.headers) { + const headers = Object.keys(options.headers) + .reduce((prev, next) => Object.assign(prev, { + [next.toLowerCase()]: options.headers[next] + }), {}) + + if (headers['x-amz-signature']) { + return true + } + + if ([].concat(headers['authorization']).some(startsWith('AWS4-HMAC-SHA256'))) { + return true + } + } + + return options.path && options.path.toLowerCase().indexOf('x-amz-signature=') !== -1 + }, + // Register a callback to run before res.end() is called. beforeEnd (req, callback) { req._datadog.beforeEnd.push(callback) @@ -449,6 +523,15 @@ function getStatusValidator (config) { return code => code < 500 } +function getClientStatusValidator (config) { + if (typeof config.validateStatus === 'function') { + return config.validateStatus + } else if (config.hasOwnProperty('validateStatus')) { + log.error('Expected `validateStatus` to be a function.') + } + return code => code < 400 || code >= 500 +} + function getHooks (config) { const noop = () => {} const request = (config.hooks && config.hooks.request) || noop @@ -466,4 +549,21 @@ function getMiddlewareSetting (config) { return true } +function getFilter (config) { + config = Object.assign({}, config, { + blocklist: config.blocklist || [] + }) + + return urlFilter.getFilter(config) +} + +function getHeaders (config) { + if (!Array.isArray(config.headers)) return [] + + return config.headers + .filter(key => typeof key === 'string') + .map(key => key.toLowerCase()) +} + + module.exports = web From e66468a8a1bf874407ce1058bd84c90a551e0945 Mon Sep 17 00:00:00 2001 From: Florent Vilmart Date: Tue, 1 Feb 2022 09:19:22 -0500 Subject: [PATCH 19/22] refactor: adds support for all public apis --- packages/datadog-plugin-http/src/client.js | 33 --- packages/datadog-plugin-undici/src/index.js | 127 ++++++--- .../datadog-plugin-undici/test/index.spec.js | 260 +++++++++--------- packages/dd-trace/src/plugins/util/web.js | 9 +- 4 files changed, 225 insertions(+), 204 deletions(-) diff --git a/packages/datadog-plugin-http/src/client.js b/packages/datadog-plugin-http/src/client.js index f276fb9b202..71207fac4f1 100644 --- a/packages/datadog-plugin-http/src/client.js +++ b/packages/datadog-plugin-http/src/client.js @@ -5,7 +5,6 @@ const log = require('../../dd-trace/src/log') const tags = require('../../../ext/tags') const kinds = require('../../../ext/kinds') const formats = require('../../../ext/formats') -const urlFilter = require('../../dd-trace/src/plugins/util/urlfilter') const analyticsSampler = require('../../dd-trace/src/analytics_sampler') const { storage } = require('../../datadog-core') const { addErrorToSpan, getServiceName, hasAmazonSignature, client } = require('../../dd-trace/src/plugins/util/web') @@ -216,44 +215,12 @@ function unpatch (http) { this.unwrap(http, 'get') } -function getStatusValidator (config) { - if (typeof config.validateStatus === 'function') { - return config.validateStatus - } else if (config.hasOwnProperty('validateStatus')) { - log.error('Expected `validateStatus` to be a function.') - } - return code => code < 400 || code >= 500 -} - -function getFilter (config) { - config = Object.assign({}, config, { - blocklist: config.blocklist || [] - }) - - return urlFilter.getFilter(config) -} - function normalizeConfig (config) { config = config.client || config return client.normalizeConfig(config) } -function getHeaders (config) { - if (!Array.isArray(config.headers)) return [] - - return config.headers - .filter(key => typeof key === 'string') - .map(key => key.toLowerCase()) -} - -function getHooks (config) { - const noop = () => {} - const request = (config.hooks && config.hooks.request) || noop - - return { request } -} - module.exports = [ { name: 'http', diff --git a/packages/datadog-plugin-undici/src/index.js b/packages/datadog-plugin-undici/src/index.js index 8ca91726be4..cd1b3b08d42 100644 --- a/packages/datadog-plugin-undici/src/index.js +++ b/packages/datadog-plugin-undici/src/index.js @@ -5,10 +5,14 @@ const log = require('../../dd-trace/src/log') const tags = require('../../../ext/tags') const kinds = require('../../../ext/kinds') const formats = require('../../../ext/formats') -const urlFilter = require('../../dd-trace/src/plugins/util/urlfilter') const analyticsSampler = require('../../dd-trace/src/analytics_sampler') const { AsyncResource, AsyncLocalStorage } = require('async_hooks') -const { addErrorToSpan, getServiceName, hasAmazonSignature, client: { normalizeConfig } } = require('../../dd-trace/src/plugins/util/web') +const { + addErrorToSpan, + getServiceName, + hasAmazonSignature, + client: { normalizeConfig } +} = require('../../dd-trace/src/plugins/util/web') const HTTP_HEADERS = formats.HTTP_HEADERS const HTTP_STATUS_CODE = tags.HTTP_STATUS_CODE @@ -58,7 +62,6 @@ function diagnostics (tracer, config) { ) return () => {} } - config = normalizeConfig(config) channels.requestChannel = diagnosticsChannel.channel('undici:request:create') channels.headersChannel = diagnosticsChannel.channel( @@ -90,9 +93,17 @@ function diagnostics (tracer, config) { requestSpansMap.set(request, span) } - const headers = typeof request.headers == 'string' ? parseHeaders(request.headers) : request.headers; - - if (!(hasAmazonSignature({ ...request, headers }) || !config.propagationFilter(uri))) { + const headers = + typeof request.headers === 'string' + ? parseHeaders(request.headers) + : request.headers + + if ( + !( + hasAmazonSignature({ ...request, headers }) || + !config.propagationFilter(uri) + ) + ) { const injectedHeaders = {} tracer.inject(span, HTTP_HEADERS, injectedHeaders) Object.entries(injectedHeaders).forEach(([key, value]) => { @@ -106,12 +117,15 @@ function diagnostics (tracer, config) { function handleRequestError ({ request, error }) { const span = requestSpansMap.get(request) addErrorToSpan(span, error) - finish(request, null, span, config) + addRequestHeaders(request, span, config) + span.finish() } function handleRequestHeaders ({ request, response }) { const span = requestSpansMap.get(request) - finish(request, response, span, config) + addRequestHeaders(request, span, config) + setStatusCode(response, span, config) + config.hooks.request(span, request, response) } return function unsubscribe () { @@ -149,37 +163,38 @@ function addRequestHeaders (req, span, config) { } } -function addResponseHeaders (res, span, config) { - const resHeader = res.headers.map((x) => x.toString()) - while (resHeader.length) { - const key = resHeader.shift() - const value = resHeader.shift() - span.setTag(`${HTTP_RESPONSE_HEADERS}.${key}`, value) +function setStatusCode (res, span, config) { + // fetch has status set on `status` rather than statusCode + const statusCode = res.status || res.statusCode + span.setTag(HTTP_STATUS_CODE, statusCode) + + if (!config.validateStatus(statusCode)) { + span.setTag('error', 1) } } -function finish (req, res, span, config) { - if (res) { - span.setTag(HTTP_STATUS_CODE, res.statusCode) +function addResponseHeaders (res, span, config) { + config.headers.forEach((key) => { + const value = res.headers[key] - if (!config.validateStatus(res.statusCode)) { - span.setTag('error', 1) + if (value) { + span.setTag(`${HTTP_RESPONSE_HEADERS}.${key}`, value) } + }) +} +function finishSpan (res, span, error, config) { + if (res) { + setStatusCode(res, span, config) addResponseHeaders(res, span, config) + span.finish() } else { span.setTag('error', 1) } - - addRequestHeaders(req, span, config) - - config.hooks.request(span, req, res) - - span.finish() } function patch (undici, methodName, tracer, config) { - this.wrap(undici, methodName, fn => makeRequestTrace(fn)) + this.wrap(undici, methodName, (fn) => makeRequestTrace(fn)) function makeRequestTrace (request) { return function requestTrace () { @@ -195,28 +210,78 @@ function patch (undici, methodName, tracer, config) { [SPAN_KIND]: CLIENT } }) - - return asyncLocalStorage.run(span, () => { - return request.apply(this, arguments) + const result = asyncLocalStorage.run(span, () => { + return tracer.scope().activate(span, () => { + return request.apply(this, arguments) + }) }) + + if (methodName === 'pipeline') { + result.on('end', () => { + span.finish() + }).on('error', () => { + span.finish() + }) + return result + } + + return wrapPromise(result, span, config) } } } +function wrapPromise (promise, span, config) { + if (!promise) { + finishSpan(null, span, null, config) + return promise + } + + return promise + .then( + (res) => finishSpan(res, span, null, config), + (e) => finishSpan(null, span, e, config) + ) + .then(() => promise) +} + module.exports = [ { name: 'undici', versions: ['>=4.7.1'], patch: function (undici, tracer, config) { + config = normalizeConfig(config) + patch.call(this, undici, 'request', tracer, config) patch.call(this, undici, 'upgrade', tracer, config) patch.call(this, undici, 'connect', tracer, config) patch.call(this, undici, 'fetch', tracer, config) patch.call(this, undici, 'pipeline', tracer, config) patch.call(this, undici, 'stream', tracer, config) - patch.call(this, undici.Client.prototype, 'request', tracer, config) - this.unpatch = diagnostics.call(this, tracer, config) + patch.call(this, undici.Client.prototype, 'request', tracer, config) + patch.call(this, undici.Client.prototype, 'pipeline', tracer, config) + patch.call(this, undici.Client.prototype, 'upgrade', tracer, config) + patch.call(this, undici.Client.prototype, 'connect', tracer, config) + patch.call(this, undici.Client.prototype, 'stream', tracer, config) + + const unpatchDiagnostics = diagnostics.call(this, tracer, config) + + this.unpatch = () => { + this.unwrap(undici, 'request') + this.unwrap(undici, 'upgrade') + this.unwrap(undici, 'connect') + this.unwrap(undici, 'fetch') + this.unwrap(undici, 'pipeline') + this.unwrap(undici, 'stream') + + this.unwrap(undici.Client.prototype, 'request') + this.unwrap(undici.Client.prototype, 'pipeline') + this.unwrap(undici.Client.prototype, 'upgrade') + this.unwrap(undici.Client.prototype, 'connect') + this.unwrap(undici.Client.prototype, 'stream') + + unpatchDiagnostics() + } } } ] diff --git a/packages/datadog-plugin-undici/test/index.spec.js b/packages/datadog-plugin-undici/test/index.spec.js index acae7c50b6b..3b3911ba596 100644 --- a/packages/datadog-plugin-undici/test/index.spec.js +++ b/packages/datadog-plugin-undici/test/index.spec.js @@ -9,6 +9,7 @@ const { expect } = require('chai') const HTTP_REQUEST_HEADERS = tags.HTTP_REQUEST_HEADERS const HTTP_RESPONSE_HEADERS = tags.HTTP_RESPONSE_HEADERS const plugin = require('../src') +const { PassThrough, pipeline, Readable, Writable } = require('stream') describe('undici', () => { let express @@ -35,8 +36,7 @@ describe('undici', () => { return agent.close() }) - - describe('all methods', () => { + describe('automatic instrumentation', () => { beforeEach(() => { return agent.load('undici').then(() => { undici = require(`../../../versions/undici@${version}`).get() @@ -44,80 +44,104 @@ describe('undici', () => { }) }) - it('should do automatic instrumentation for request', (done) => { - const app = express() - - app.get('/user', (_, res) => { - res.status(200).send() - }) - - getPort().then((port) => { - agent - .use((traces) => { - expect(traces[0][0]).to.have.property( - 'service', - 'test-http-client' - ) - 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', 'client') - 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', - '200' - ) + function withUndici (port) { + const path = `http://localhost:${port}/user` + + return { + request: () => undici.request(path), + pipeline: () => + pipeline( + new Readable({ + read () { + this.push(Buffer.from('undici')) + this.push(null) + } + }), + undici.pipeline(path, ({ body }) => { + return pipeline(body, new PassThrough(), () => {}) + }), + new PassThrough(), + _ => { + } + ), + upgrade: () => undici.upgrade(path), + connect: () => undici.connect(path), + fetch: () => undici.fetch(path), + stream: () => undici.stream(path, ({ opaque: { bufs } }) => { + return new Writable({ + write (chunk, _, callback) { + bufs.push(chunk) + callback() + } }) - .then(done) - .catch(done) + }) + } + } - appListener = server(app, port, () => { - undici.request(`http://localhost:${port}/user`) + function withClient (port) { + const client = new undici.Client(`http://localhost:${port}`) + + return { + request: () => client.request({ path: '/user', method: 'GET' }), + pipeline: () => + pipeline( + new Readable({ + read () { + this.push(Buffer.from('undici')) + this.push(null) + } + }), + client.pipeline({ path: `/user`, method: 'GET' }, ({ body }) => { + return pipeline(body, new PassThrough(), () => {}) + }), + new PassThrough(), + _ => { + client.close() + } + ), + upgrade: () => client.upgrade({ path: '/user', method: 'GET' }), + connect: () => client.connect({ path: '/user', method: 'CONNECT' }), + stream: () => client.stream({ path: '/user', method: 'GET' }, ({ opaque: { bufs } }) => { + return new Writable({ + write (chunk, _, callback) { + bufs.push(chunk) + callback() + } + }) }) - }) - }) - it('should do automatic instrumentation for fetch', (done) => { + } + } + + function verifyTraces (port) { + return function (traces, options = { method: 'GET', expectStatusCode: '200' }) { + expect(traces[0][0]).to.have.property( + 'service', + 'test-http-client' + ) + expect(traces[0][0]).to.have.property('type', 'http') + expect(traces[0][0]).to.have.property('resource', options.method) + expect(traces[0][0].meta).to.have.property('span.kind', 'client') + expect(traces[0][0].meta).to.have.property( + 'http.url', + `http://localhost:${port}/user` + ) + expect(traces[0][0].meta).to.have.property('http.method', options.method) + if (options.expectStatusCode) { + expect(traces[0][0].meta).to.have.property( + 'http.status_code', + '200' + ) + } + } + } + + function setup (handler, done, options) { const app = express() app.get('/user', (_, res) => { res.status(200).send() }) - getPort().then((port) => { - agent - .use((traces) => { - expect(traces[0][0]).to.have.property( - 'service', - 'test-http-client' - ) - 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', 'client') - 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', - '200' - ) - }) - .then(done) - .catch(done) - - appListener = server(app, port, () => { - undici.fetch(`http://localhost:${port}/user`) - }) - }) - }) - - it('should do automatic instrumentation for connect', (done) => { - const app = express() - app.connect('/user', (_, res) => { res.status(200).send() }) @@ -125,92 +149,58 @@ describe('undici', () => { getPort().then((port) => { agent .use((traces) => { - expect(traces[0][0]).to.have.property( - 'service', - 'test-http-client' - ) - expect(traces[0][0]).to.have.property('type', 'http') - expect(traces[0][0]).to.have.property('resource', 'CONNECT') - expect(traces[0][0].meta).to.have.property('span.kind', 'client') - expect(traces[0][0].meta).to.have.property( - 'http.url', - `http://localhost:${port}/user` - ) - expect(traces[0][0].meta).to.have.property('http.method', 'CONNECT') + verifyTraces(port)(traces, options) }) .then(done) .catch(done) appListener = server(app, port, () => { - undici.connect(`http://localhost:${port}/user`) + handler(port)() }) }) - }) - - xit('should do automatic instrumentation for pipeline', (done) => { - const app = express() + } - app.get('/user', (_, res) => { - res.status(200).send() + describe('when using client', () => { + it('should do automatic instrumentation for request', (done) => { + setup((port) => withClient(port).request, done) }) - getPort().then((port) => { - agent - .use((traces) => { - console.log(traces) - expect(traces[0][0]).to.have.property( - 'service', - 'test-http-client' - ) - 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', 'client') - 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') - }) - .then(done) - .catch(done) + it('should do automatic instrumentation for upgrade', (done) => { + setup((port) => withClient(port).upgrade, done) + }) - appListener = server(app, port, () => { - undici.pipeline(`http://localhost:${port}/user`, (_) => { - }) - }) + it('should do automatic instrumentation for pipeline', (done) => { + setup((port) => withClient(port).pipeline, done, { method: 'GET', expectStatusCode: '200' }) + }) + it('should do automatic instrumentation for connect', (done) => { + setup((port) => withClient(port).connect, done, { method: 'CONNECT' }) + }) + it('should do automatic instrumentation for stream', (done) => { + setup((port) => withClient(port).stream, done) }) }) - xit('should do automatic instrumentation for stream', (done) => { - const app = express() + describe('when using undici', () => { + it('should do automatic instrumentation for request', (done) => { + setup((port) => withUndici(port).request, done) + }) - app.connect('/user', (_, res) => { - res.status(200).send() + it('should do automatic instrumentation for upgrade', (done) => { + setup((port) => withUndici(port).upgrade, done) }) - getPort().then((port) => { - agent - .use((traces) => { - console.log(traces) - expect(traces[0][0]).to.have.property( - 'service', - 'test-http-client' - ) - 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', 'client') - 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') - }) - .then(done) - .catch(done) + it('should do automatic instrumentation for pipeline', (done) => { + setup((port) => withUndici(port).pipeline, done, { method: 'GET', expectStatusCode: '200' }) + }) + it('should do automatic instrumentation for connect', (done) => { + setup((port) => withUndici(port).connect, done, { method: 'CONNECT' }) + }) + it('should do automatic instrumentation for stream', (done) => { + setup((port) => withUndici(port).stream, done) + }) - appListener = server(app, port, () => { - undici.stream(`http://localhost:${port}/user`) - }) + it('should do automatic instrumentation for fetch', (done) => { + setup((port) => withUndici(port).fetch, done) }) }) }) diff --git a/packages/dd-trace/src/plugins/util/web.js b/packages/dd-trace/src/plugins/util/web.js index 6c3645a0da5..ee32774ab92 100644 --- a/packages/dd-trace/src/plugins/util/web.js +++ b/packages/dd-trace/src/plugins/util/web.js @@ -186,7 +186,7 @@ const web = { return `${tracer._service}-http-client` }, - addErrorToSpan(span, error) { + addErrorToSpan (span, error) { span.addTags({ 'error.type': error.name, 'error.msg': error.message, @@ -202,9 +202,9 @@ const web = { if (options.headers) { const headers = Object.keys(options.headers) - .reduce((prev, next) => Object.assign(prev, { - [next.toLowerCase()]: options.headers[next] - }), {}) + .reduce((prev, next) => Object.assign(prev, { + [next.toLowerCase()]: options.headers[next] + }), {}) if (headers['x-amz-signature']) { return true @@ -565,5 +565,4 @@ function getHeaders (config) { .map(key => key.toLowerCase()) } - module.exports = web From c359bc4de1910a1b55ec2c2bd17c4ee6f81e77a6 Mon Sep 17 00:00:00 2001 From: Florent Vilmart Date: Tue, 1 Feb 2022 09:55:38 -0500 Subject: [PATCH 20/22] feat: adds support for node 14 (exclude fetch) --- .circleci/config.yml | 2 +- packages/datadog-plugin-undici/src/index.js | 8 ++++++-- packages/datadog-plugin-undici/test/index.spec.js | 7 ++++++- 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 91293faea21..fbe72b9195f 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -826,7 +826,7 @@ workflows: - node-undici: matrix: parameters: - node-version: ["16"] + node-version: ["14.9", "16"] - node-when: *matrix-supported-node-versions - node-winston: *matrix-supported-node-versions - codecov: diff --git a/packages/datadog-plugin-undici/src/index.js b/packages/datadog-plugin-undici/src/index.js index cd1b3b08d42..09ad4fc1c86 100644 --- a/packages/datadog-plugin-undici/src/index.js +++ b/packages/datadog-plugin-undici/src/index.js @@ -254,7 +254,9 @@ module.exports = [ patch.call(this, undici, 'request', tracer, config) patch.call(this, undici, 'upgrade', tracer, config) patch.call(this, undici, 'connect', tracer, config) - patch.call(this, undici, 'fetch', tracer, config) + if (undici.fetch) { + patch.call(this, undici, 'fetch', tracer, config) + } patch.call(this, undici, 'pipeline', tracer, config) patch.call(this, undici, 'stream', tracer, config) @@ -270,7 +272,9 @@ module.exports = [ this.unwrap(undici, 'request') this.unwrap(undici, 'upgrade') this.unwrap(undici, 'connect') - this.unwrap(undici, 'fetch') + if (undici.fetch) { + this.unwrap(undici, 'fetch') + } this.unwrap(undici, 'pipeline') this.unwrap(undici, 'stream') diff --git a/packages/datadog-plugin-undici/test/index.spec.js b/packages/datadog-plugin-undici/test/index.spec.js index 3b3911ba596..b1e4f1cfed2 100644 --- a/packages/datadog-plugin-undici/test/index.spec.js +++ b/packages/datadog-plugin-undici/test/index.spec.js @@ -200,7 +200,12 @@ describe('undici', () => { }) it('should do automatic instrumentation for fetch', (done) => { - setup((port) => withUndici(port).fetch, done) + // fetch is not available on node 14 + if (undici.fetch) { + setup((port) => withUndici(port).fetch, done) + } else { + done() + } }) }) }) From f2ea453acef2d7a25ab8be19ad058ff7c548e62c Mon Sep 17 00:00:00 2001 From: Florent Vilmart Date: Thu, 3 Feb 2022 12:54:25 -0500 Subject: [PATCH 21/22] fix merge --- packages/dd-trace/test/plugins/externals.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/dd-trace/test/plugins/externals.json b/packages/dd-trace/test/plugins/externals.json index 057736dd236..6a68f193bf8 100644 --- a/packages/dd-trace/test/plugins/externals.json +++ b/packages/dd-trace/test/plugins/externals.json @@ -139,8 +139,9 @@ { "name": "q", "versions": ["2"] - }, - "undici": [ + } + ], + "undici": [ { "name": "undici", "versions": [">=4.7.1"] From b6fdb97dedb802084f124941d7d0b14e44abf6de Mon Sep 17 00:00:00 2001 From: Florent Vilmart Date: Mon, 7 Feb 2022 09:32:40 -0500 Subject: [PATCH 22/22] use for..of instead of forEach --- packages/datadog-plugin-undici/src/index.js | 22 ++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/packages/datadog-plugin-undici/src/index.js b/packages/datadog-plugin-undici/src/index.js index 09ad4fc1c86..cab7709e11e 100644 --- a/packages/datadog-plugin-undici/src/index.js +++ b/packages/datadog-plugin-undici/src/index.js @@ -29,11 +29,12 @@ function parseHeaders (headers) { .map((r) => r.split(':').map((str) => str.trim())) const object = {} - pairs.forEach(([key, value]) => { - key = key.toLowerCase() + for (const pair of pairs) { + const value = pair[1] if (!value) { - return + continue } + const key = pair[0].toLowerCase() if (object[key]) { if (!Array.isArray(object[key])) { object[key] = [object[key], value] @@ -43,7 +44,7 @@ function parseHeaders (headers) { } else { object[key] = value } - }) + } return object } const channels = { @@ -106,9 +107,9 @@ function diagnostics (tracer, config) { ) { const injectedHeaders = {} tracer.inject(span, HTTP_HEADERS, injectedHeaders) - Object.entries(injectedHeaders).forEach(([key, value]) => { + for (const [key, value] of Object.entries(injectedHeaders)) { request.addHeader(key, value) - }) + } } analyticsSampler.sample(span, config.measured) @@ -143,9 +144,9 @@ function diagnostics (tracer, config) { function addRequestHeaders (req, span, config) { const headers = parseHeaders(req.headers) - Object.entries(headers).forEach(([key, value]) => { + for (const [key, value] of Object.entries(headers)) { span.setTag(`${HTTP_REQUEST_HEADERS}.${key}`, value) - }) + } if (!headers.host) { // req.servername holds the value of the host header @@ -174,13 +175,12 @@ function setStatusCode (res, span, config) { } function addResponseHeaders (res, span, config) { - config.headers.forEach((key) => { + for (const key of config.headers) { const value = res.headers[key] - if (value) { span.setTag(`${HTTP_RESPONSE_HEADERS}.${key}`, value) } - }) + } } function finishSpan (res, span, error, config) {