diff --git a/packages/collector/test/tracing/messaging/nats/subscriber.js b/packages/collector/test/tracing/messaging/nats/subscriber.js index 3aded6c5cb..46df255506 100644 --- a/packages/collector/test/tracing/messaging/nats/subscriber.js +++ b/packages/collector/test/tracing/messaging/nats/subscriber.js @@ -20,7 +20,7 @@ const express = require('express'); const fetch = require('node-fetch-v2'); const NATS = require('nats'); -const log = require('@instana/core/test/test_util/log').getLogger('NATS Subscriber'); +const log = require('@instana/core/test/test_util/log').getLogger('NATS Subscriber: '); const app = express(); const port = require('../../../test_util/app-port')(); diff --git a/packages/collector/test/tracing/messaging/nats/test.js b/packages/collector/test/tracing/messaging/nats/test.js index a6aabaeab3..0a73961780 100644 --- a/packages/collector/test/tracing/messaging/nats/test.js +++ b/packages/collector/test/tracing/messaging/nats/test.js @@ -328,19 +328,41 @@ mochaSuiteFn('tracing/nats', function () { } return agentControls.getSpans().then(spans => { - const httpSpan = expectExactlyOneMatching(spans, [ + // 1 x http entry span + // 1 x publish nats from publisher process + // 1 x http client span from publisher process + // 1 x subscribe nats + // 1 x http client span from subscriber process + expect(spans.length).to.equal(5); + + const httpEntrySpan = expectExactlyOneMatching(spans, [ span => expect(span.n).to.equal('node.http.server'), span => expect(span.f.e).to.equal(String(publisherControls.getPid())), span => expect(span.p).to.not.exist ]); + expectExactlyOneMatching(spans, [ + span => expect(span.n).to.equal('node.http.client'), + span => expect(span.f.e).to.equal(String(publisherControls.getPid())), + span => expect(span.t).to.equal(httpEntrySpan.t), + span => expect(span.p).to.equal(httpEntrySpan.s) + ]); + + expectExactlyOneMatching(spans, [ + span => expect(span.n).to.equal('nats'), + span => expect(span.k).to.equal(constants.EXIT), + span => expect(span.f.e).to.equal(String(publisherControls.getPid())), + span => expect(span.t).to.equal(httpEntrySpan.t), + span => expect(span.p).to.equal(httpEntrySpan.s) + ]); + // NATS 1.x does not support headers or metadata, so we do not have trace continuity. // NATS 2.x does support headers. const expectations = [ span => version === 'latest' - ? expect(span.t).to.equal(httpSpan.t) - : expect(span.t).to.not.equal(httpSpan.t), + ? expect(span.t).to.equal(httpEntrySpan.t) + : expect(span.t).to.not.equal(httpEntrySpan.t), span => (version === 'latest' ? expect(span.p).to.exist : expect(span.p).to.not.exist), span => expect(span.k).to.equal(constants.ENTRY), span => expect(span.n).to.equal('nats'), @@ -349,7 +371,6 @@ mochaSuiteFn('tracing/nats', function () { span => expect(span.async).to.not.exist, span => expect(span.ts).to.be.a('number'), span => expect(span.d).to.be.a('number'), - span => expect(span.data.nats).to.be.an('object'), span => expect(span.data.nats.sort).to.equal('consume'), span => expect(span.data.nats.subject).to.equal('subscribe-test-subject'), @@ -368,19 +389,14 @@ mochaSuiteFn('tracing/nats', function () { const natsEntry = expectExactlyOneMatching(spans, expectations); - // verify that subsequent calls are correctly traced expectExactlyOneMatching(spans, [ span => expect(span.n).to.equal('node.http.client'), + span => expect(span.f.e).to.equal(String(subscriberControls.getPid())), span => - // NOTE: the error is catched manually outside of the http request to the agent - version === 'latest' && withError - ? expect(span.t).to.not.equal(natsEntry.t) - : expect(span.t).to.equal(natsEntry.t), - span => - version === 'latest' && withError - ? expect(span.p).to.not.equal(natsEntry.s) - : expect(span.p).to.equal(natsEntry.s), - span => expect(span.k).to.equal(constants.EXIT) + version === 'latest' + ? expect(span.t).to.equal(httpEntrySpan.t) + : expect(span.t).to.not.equal(httpEntrySpan.t), + span => expect(span.p).to.equal(natsEntry.s) ]); }); }) diff --git a/packages/collector/test/tracing/protocols/http/client/clientApp.js b/packages/collector/test/tracing/protocols/http/client/clientApp.js index 47ed047caa..6cbbbefaff 100644 --- a/packages/collector/test/tracing/protocols/http/client/clientApp.js +++ b/packages/collector/test/tracing/protocols/http/client/clientApp.js @@ -53,6 +53,14 @@ app.get('/request-url-only', (req, res) => { httpModule.request(createUrl(req, '/request-only-url'), () => res.sendStatus(200)).end(); }); +app.get('/request-deferred', (req, res) => { + setTimeout(() => { + httpModule.get('http://example.com?k=2', () => {}).end(); + }, 500); + + httpModule.get('http://example.com?k=1', () => res.sendStatus(200)).end(); +}); + app.get('/request-options-only', (req, res) => { const downStreamQuery = {}; if (req.query.withQuery) { diff --git a/packages/collector/test/tracing/protocols/http/client/test.js b/packages/collector/test/tracing/protocols/http/client/test.js index a15898d3a6..b4f09b351d 100644 --- a/packages/collector/test/tracing/protocols/http/client/test.js +++ b/packages/collector/test/tracing/protocols/http/client/test.js @@ -134,6 +134,43 @@ function registerTests(useHttps) { await clientControls.clearIpcMessages(); }); + if (!useHttps) { + it('must trace request in background', () => { + return clientControls + .sendRequest({ + method: 'GET', + path: '/request-deferred' + }) + .then(() => { + return retry(() => { + return globalAgent.instance.getSpans().then(spans => { + expect(spans.length).to.equal(3); + + const entryInClient = verifyRootHttpEntry({ + spans, + host: `localhost:${clientControls.getPort()}`, + url: '/request-deferred' + }); + + verifyHttpExit({ + spans, + parent: entryInClient, + url: 'http://example.com/', + params: 'k=1' + }); + + verifyHttpExit({ + spans, + parent: entryInClient, + url: 'http://example.com/', + params: 'k=2' + }); + }); + }); + }); + }); + } + // HTTP requests can be triggered via http.request(...) + request.end(...) or http.get(...). // Both http.request and http.get accept // - an URL, an options object and a callback @@ -142,9 +179,8 @@ function registerTests(useHttps) { // The URL can be a string or an URL object. // // This following tests cover all variants. - - [false, true].forEach(urlObject => { - [false, true].forEach(withQuery => { + [true, false].forEach(urlObject => { + [true, false].forEach(withQuery => { const urlParam = urlObject ? 'urlObject' : 'urlString'; it(`must trace request(${urlParam}, options, cb) with query: ${withQuery}`, () => @@ -846,7 +882,7 @@ function verifyHttpEntry({ spans, parent, host, url = '/', method = 'GET', statu return expectExactlyOneMatching(spans, expectations); } -function verifyHttpExit({ spans, parent, url = '/', method = 'GET', status = 200, synthetic = false }) { +function verifyHttpExit({ spans, parent, url = '/', method = 'GET', status = 200, synthetic = false, params = null }) { return expectExactlyOneMatching(spans, [ span => expect(span.n).to.equal('node.http.client'), span => expect(span.k).to.equal(constants.EXIT), @@ -856,6 +892,7 @@ function verifyHttpExit({ spans, parent, url = '/', method = 'GET', status = 200 span => expect(span.data.http.url).to.equal(url), span => expect(span.data.http.method).to.equal(method), span => expect(span.data.http.status).to.equal(status), + span => (params ? expect(span.data.http.params).to.equal(params) : true), span => (!synthetic ? expect(span.sy).to.not.exist : expect(span.sy).to.be.true) ]); } diff --git a/packages/collector/test/tracing/protocols/http/native_fetch/clientApp.js b/packages/collector/test/tracing/protocols/http/native_fetch/clientApp.js index ed65a9911a..b08e9c8763 100644 --- a/packages/collector/test/tracing/protocols/http/native_fetch/clientApp.js +++ b/packages/collector/test/tracing/protocols/http/native_fetch/clientApp.js @@ -43,6 +43,15 @@ app.use(bodyParser.json()); app.get('/', (req, res) => res.sendStatus(200)); +app.get('/fetch-deferred', async (req, res) => { + setTimeout(async () => { + await fetch('http://example.com?k=2'); + }, 500); + + await fetch('http://example.com?k=1'); + res.sendStatus(200); +}); + app.get('/fetch', async (req, res) => { const resourceArgument = createResourceArgument(req, '/fetch'); let response; diff --git a/packages/collector/test/tracing/protocols/http/native_fetch/test.js b/packages/collector/test/tracing/protocols/http/native_fetch/test.js index 5c5d06d67e..609f831821 100644 --- a/packages/collector/test/tracing/protocols/http/native_fetch/test.js +++ b/packages/collector/test/tracing/protocols/http/native_fetch/test.js @@ -64,6 +64,41 @@ mochaSuiteFn('tracing/native fetch', function () { await clientControls.clearIpcMessages(); }); + it('must trace request in background', () => { + return clientControls + .sendRequest({ + method: 'GET', + path: '/fetch-deferred' + }) + .then(() => { + return retry(() => { + return globalAgent.instance.getSpans().then(spans => { + expect(spans.length).to.equal(3); + + const entryInClient = verifyRootHttpEntry({ + spans, + host: `localhost:${clientControls.getPort()}`, + url: '/fetch-deferred' + }); + + verifyHttpExit({ + spans, + parent: entryInClient, + url: 'http://example.com/', + params: 'k=1' + }); + + verifyHttpExit({ + spans, + parent: entryInClient, + url: 'http://example.com/', + params: 'k=2' + }); + }); + }); + }); + }); + // See https://developer.mozilla.org/en-US/docs/Web/API/fetch#parameters. describe('capture attributes from different resource types', () => { @@ -689,7 +724,8 @@ function verifyHttpExit({ withClientError, withServerError, withTimeout, - serverControls + serverControls, + params = null }) { const expectations = [ span => expect(span.n).to.equal('node.http.client'), @@ -700,6 +736,7 @@ function verifyHttpExit({ span => expect(span.ec).to.equal(withClientError || withServerError || withTimeout ? 1 : 0), span => expect(span.data.http.url).to.equal(url), span => expect(span.data.http.method).to.equal(method), + span => (params ? expect(span.data.http.params).to.equal(params) : true), span => expect(span.sy).to.not.exist ]; if (withClientError) { diff --git a/packages/core/src/tracing/instrumentation/protocols/httpClient.js b/packages/core/src/tracing/instrumentation/protocols/httpClient.js index 81c7a5b485..f1374a14e1 100644 --- a/packages/core/src/tracing/instrumentation/protocols/httpClient.js +++ b/packages/core/src/tracing/instrumentation/protocols/httpClient.js @@ -207,7 +207,7 @@ function instrument(coreModule, forceHttps) { } cls.ns.run(() => { - const span = cls.startSpan('node.http.client', constants.EXIT); + const span = cls.startSpan('node.http.client', constants.EXIT, parentSpan.t, parentSpan.s); // startSpan updates the W3C trace context and writes it back to CLS, so we have to refetch the updated context // object from CLS. diff --git a/packages/core/src/tracing/instrumentation/protocols/nativeFetch.js b/packages/core/src/tracing/instrumentation/protocols/nativeFetch.js index b5e61d2e59..dd1c5af0b9 100644 --- a/packages/core/src/tracing/instrumentation/protocols/nativeFetch.js +++ b/packages/core/src/tracing/instrumentation/protocols/nativeFetch.js @@ -80,7 +80,12 @@ function instrument() { // eslint-disable-next-line no-unused-vars let w3cTraceContext = cls.getW3cTraceContext(); - const skipTracingResult = cls.skipExitTracing({ isActive, extendedResponse: true, skipParentSpanCheck: true }); + const skipTracingResult = cls.skipExitTracing({ + isActive, + extendedResponse: true, + skipParentSpanCheck: true, + skipIsTracing: true + }); // If there is no active entry span, we fall back to the reduced span of the most recent entry span. See comment in // packages/core/src/tracing/clsHooked/unset.js#storeReducedSpan. @@ -100,7 +105,7 @@ function instrument() { } return cls.ns.runAndReturn(() => { - const span = cls.startSpan('node.http.client', constants.EXIT); + const span = cls.startSpan('node.http.client', constants.EXIT, parentSpan.t, parentSpan.s); // startSpan updates the W3C trace context and writes it back to CLS, so we have to refetch the updated context w3cTraceContext = cls.getW3cTraceContext();