diff --git a/experimental/CHANGELOG.md b/experimental/CHANGELOG.md index fb1383cb577..5803b1fb728 100644 --- a/experimental/CHANGELOG.md +++ b/experimental/CHANGELOG.md @@ -9,6 +9,8 @@ All notable changes to experimental packages in this project will be documented ### :rocket: (Enhancement) +* feat(instrumentation): Add allowUrls config option to web instrumentation [#4938](https://github.com/open-telemetry/opentelemetry-js/pull/4938) @jairo-mendoza + ### :bug: (Bug Fix) ### :books: (Refine Doc) diff --git a/experimental/packages/opentelemetry-instrumentation-fetch/src/fetch.ts b/experimental/packages/opentelemetry-instrumentation-fetch/src/fetch.ts index e5d9a84bdeb..8f85d255410 100644 --- a/experimental/packages/opentelemetry-instrumentation-fetch/src/fetch.ts +++ b/experimental/packages/opentelemetry-instrumentation-fetch/src/fetch.ts @@ -66,6 +66,11 @@ export interface FetchInstrumentationConfig extends InstrumentationConfig { clearTimingResources?: boolean; // urls which should include trace headers when origin doesn't match propagateTraceHeaderCorsUrls?: web.PropagateTraceHeaderCorsUrls; + /** + * URLs that partially match any regex or exactly match strings in allowUrls + * will be traced. + */ + allowUrls?: Array; /** * URLs that partially match any regex in ignoreUrls will not be traced. * In addition, URLs that are _exact matches_ of strings in ignoreUrls will @@ -196,6 +201,27 @@ export class FetchInstrumentation extends InstrumentationBase | undefined + ): boolean { + if (!allowedUrls) { + return true; + } + + for (const allowedUrl of allowedUrls) { + if (core.urlMatches(url, allowedUrl)) { + return true; + } + } + return false; + } + /** * Creates a new span * @param url @@ -205,6 +231,10 @@ export class FetchInstrumentation extends InstrumentationBase = {} ): api.Span | undefined { + if (!this._isUrlAllowed(url, this.getConfig().allowUrls)) { + this._diag.debug('ignoring span as url does not match an allowed url'); + return; + } if (core.isUrlIgnored(url, this.getConfig().ignoreUrls)) { this._diag.debug('ignoring span as url matches ignored url'); return; diff --git a/experimental/packages/opentelemetry-instrumentation-fetch/test/fetch.test.ts b/experimental/packages/opentelemetry-instrumentation-fetch/test/fetch.test.ts index e76d9843248..794f173c42d 100644 --- a/experimental/packages/opentelemetry-instrumentation-fetch/test/fetch.test.ts +++ b/experimental/packages/opentelemetry-instrumentation-fetch/test/fetch.test.ts @@ -964,6 +964,40 @@ describe('fetch', () => { }); }); + describe('when url is allowed', () => { + beforeEach(async () => { + const propagateTraceHeaderCorsUrls = url; + await prepareData(url, () => getData(url), { + propagateTraceHeaderCorsUrls, + allowUrls: [propagateTraceHeaderCorsUrls], + }); + }); + afterEach(() => { + clearData(); + }); + it('should create a span', () => { + assert.ok(exportSpy.args.length > 0, 'span should be exported'); + }); + }); + + describe('when url is NOT allowed', () => { + const otherUrl = 'http://localhost:8099/get'; + + beforeEach(async () => { + const propagateTraceHeaderCorsUrls = url; + await prepareData(url, () => getData(url), { + propagateTraceHeaderCorsUrls, + allowUrls: [otherUrl], + }); + }); + afterEach(() => { + clearData(); + }); + it('should NOT create any span', () => { + assert.ok(exportSpy.args.length === 0, "span shouldn't be exported"); + }); + }); + describe('when url is ignored', () => { beforeEach(async () => { const propagateTraceHeaderCorsUrls = url; @@ -976,7 +1010,11 @@ describe('fetch', () => { clearData(); }); it('should NOT create any span', () => { - assert.strictEqual(exportSpy.args.length, 0, "span shouldn't b exported"); + assert.strictEqual( + exportSpy.args.length, + 0, + "span shouldn't be exported" + ); }); it('should pass request object as the first parameter to the original function (#2411)', () => { const r = new Request(url); diff --git a/experimental/packages/opentelemetry-instrumentation-xml-http-request/src/xhr.ts b/experimental/packages/opentelemetry-instrumentation-xml-http-request/src/xhr.ts index 6d2eafa054c..a73cc26aff9 100644 --- a/experimental/packages/opentelemetry-instrumentation-xml-http-request/src/xhr.ts +++ b/experimental/packages/opentelemetry-instrumentation-xml-http-request/src/xhr.ts @@ -21,7 +21,12 @@ import { InstrumentationConfig, safeExecuteInTheMiddle, } from '@opentelemetry/instrumentation'; -import { hrTime, isUrlIgnored, otperformance } from '@opentelemetry/core'; +import { + hrTime, + isUrlIgnored, + otperformance, + urlMatches, +} from '@opentelemetry/core'; import { SEMATTRS_HTTP_HOST, SEMATTRS_HTTP_METHOD, @@ -75,6 +80,11 @@ export interface XMLHttpRequestInstrumentationConfig clearTimingResources?: boolean; /** URLs which should include trace headers when origin doesn't match */ propagateTraceHeaderCorsUrls?: PropagateTraceHeaderCorsUrls; + /** + * URLs that partially match any regex or exactly match strings in allowUrls + * will be traced. + */ + allowUrls?: Array; /** * URLs that partially match any regex in ignoreUrls will not be traced. * In addition, URLs that are _exact matches_ of strings in ignoreUrls will @@ -323,6 +333,27 @@ export class XMLHttpRequestInstrumentation extends InstrumentationBase | undefined + ): boolean { + if (!allowedUrls) { + return true; + } + + for (const allowedUrl of allowedUrls) { + if (urlMatches(url, allowedUrl)) { + return true; + } + } + return false; + } + /** * Creates a new span when method "open" is called * @param xhr @@ -335,6 +366,10 @@ export class XMLHttpRequestInstrumentation extends InstrumentationBase { } ); + describe('when url is allowed', () => { + beforeEach(done => { + clearData(); + const propagateTraceHeaderCorsUrls = url; + prepareData(done, url, { + propagateTraceHeaderCorsUrls, + allowUrls: [propagateTraceHeaderCorsUrls], + }); + }); + + it('should create a span', () => { + assert.ok(exportSpy.called, 'span should be exported'); + }); + }); + + describe('when another url is allowed', () => { + const otherUrl = 'http://localhost:8099/get'; + beforeEach(done => { + clearData(); + const propagateTraceHeaderCorsUrls = url; + prepareData(done, url, { + propagateTraceHeaderCorsUrls, + allowUrls: [otherUrl], + }); + }); + + it('should NOT create a span', () => { + assert.ok(exportSpy.notCalled, "span shouldn't be exported"); + }); + }); + describe('when url is ignored', () => { beforeEach(done => { clearData();