Skip to content

Commit

Permalink
feat: Deprecate tracingOrigins for tracePropagationTargets (#703)
Browse files Browse the repository at this point in the history
  • Loading branch information
timfish committed Jul 29, 2023
1 parent 7582ded commit ecd978a
Show file tree
Hide file tree
Showing 3 changed files with 314 additions and 44 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@
"@sentry/types": "7.60.0",
"@sentry/utils": "7.60.0",
"deepmerge": "4.3.0",
"lru_map": "^0.3.3",
"tslib": "^2.5.0"
},
"devDependencies": {
Expand Down
137 changes: 93 additions & 44 deletions src/main/integrations/net-breadcrumbs.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
/* eslint-disable deprecation/deprecation */
import { getCurrentHub } from '@sentry/core';
import { Integration, Span } from '@sentry/types';
import { fill } from '@sentry/utils';
import { getCurrentHub } from '@sentry/node';
import { EventProcessor, Hub, Integration, Span, TracePropagationTargets } from '@sentry/types';
import { fill, stringMatchesSomePattern } from '@sentry/utils';
import { ClientRequest, ClientRequestConstructorOptions, IncomingMessage, net } from 'electron';
import { LRUMap } from 'lru_map';
import * as urlModule from 'url';

import { OrBool, OrFalse } from '../../common/types';

type ShouldTraceFn = (method: string, url: string) => boolean;

interface NetOptions {
Expand All @@ -15,37 +14,23 @@ interface NetOptions {
*
* Defaults to: true
*/
breadcrumbs: boolean;
breadcrumbs?: boolean;
/**
* Whether to capture transaction spans for net requests
*
* true | false | (method: string, url: string) => boolean
* Defaults to: true
*/
tracing: ShouldTraceFn;
tracing?: ShouldTraceFn | boolean;

/**
* Whether to add 'sentry-trace' headers to outgoing requests
* @deprecated Use `tracePropagationTargets` client option instead.
*
* true | false | (method: string, url: string) => boolean
* Defaults to: true
* Sentry.init({
* tracePropagationTargets: ['api.site.com'],
* })
*/
tracingOrigins: ShouldTraceFn;
}

const DEFAULT_OPTIONS: NetOptions = {
breadcrumbs: true,
tracing: (_method, _url) => true,
tracingOrigins: (_method, _url) => true,
};

/** Converts all user supplied options to T | false */
export function normalizeOptions(options: Partial<OrBool<NetOptions>>): Partial<OrFalse<NetOptions>> {
return (Object.keys(options) as (keyof NetOptions)[]).reduce((obj, k) => {
if (typeof options[k] === 'function' || options[k] === false) {
obj[k] = options[k] as boolean & (false | ShouldTraceFn);
}
return obj;
}, {} as Partial<OrFalse<NetOptions>>);
tracingOrigins?: ShouldTraceFn | boolean;
}

/** http module integration */
Expand All @@ -56,22 +41,19 @@ export class Net implements Integration {
/** @inheritDoc */
public name: string = Net.id;

private readonly _options: OrFalse<NetOptions>;

/** @inheritDoc */
public constructor(options: Partial<OrBool<NetOptions>> = {}) {
this._options = {
...DEFAULT_OPTIONS,
...normalizeOptions(options),
};
}
public constructor(private readonly _options: NetOptions = {}) {}

/** @inheritDoc */
public setupOnce(): void {
public setupOnce(_addGlobalEventProcessor: (callback: EventProcessor) => void, getCurrentHub: () => Hub): void {
const clientOptions = getCurrentHub().getClient()?.getOptions();

// No need to instrument if we don't want to track anything
if (this._options.breadcrumbs || this._options.tracing) {
fill(net, 'request', createWrappedRequestFactory(this._options));
if (this._options.breadcrumbs === false && this._options.tracing === false) {
return;
}

fill(net, 'request', createWrappedRequestFactory(this._options, clientOptions?.tracePropagationTargets));
}
}

Expand Down Expand Up @@ -125,7 +107,73 @@ type RequestMethod = (opt: RequestOptions) => ClientRequest;
type WrappedRequestMethodFactory = (original: RequestMethod) => RequestMethod;

/** */
function createWrappedRequestFactory(options: OrFalse<NetOptions>): WrappedRequestMethodFactory {
function createWrappedRequestFactory(
options: NetOptions,
tracePropagationTargets: TracePropagationTargets | undefined,
): WrappedRequestMethodFactory {
// We're caching results so we don't have to recompute regexp every time we create a request.
const createSpanUrlMap = new LRUMap<string, boolean>(100);
const headersUrlMap = new LRUMap<string, boolean>(100);

const shouldCreateSpan = (method: string, url: string): boolean => {
if (options.tracing === undefined) {
return true;
}

if (options.tracing === false) {
return false;
}

const key = `${method}:${url}`;

const cachedDecision = createSpanUrlMap.get(key);
if (cachedDecision !== undefined) {
return cachedDecision;
}

const decision = options.tracing === true || options.tracing(method, url);
createSpanUrlMap.set(key, decision);
return decision;
};

// This will be considerably simpler once `tracingOrigins` is removed in the next major release
const shouldAttachTraceData = (method: string, url: string): boolean => {
if (options.tracingOrigins === false) {
return false;
}

// Neither integration nor client options are set or integration option is set to true
if (
(options.tracingOrigins === undefined && tracePropagationTargets === undefined) ||
options.tracingOrigins === true
) {
return true;
}

const key = `${method}:${url}`;

const cachedDecision = headersUrlMap.get(key);
if (cachedDecision !== undefined) {
return cachedDecision;
}

if (tracePropagationTargets) {
const decision = stringMatchesSomePattern(url, tracePropagationTargets);
headersUrlMap.set(key, decision);
return decision;
}

if (options.tracingOrigins) {
const decision = options.tracingOrigins(method, url);
headersUrlMap.set(key, decision);
return decision;
}

// We cannot reach here since either `tracePropagationTargets` or `tracingOrigins` will be defined but TypeScript
// cannot infer that
return true;
};

return function wrappedRequestMethodFactory(originalRequestMethod: RequestMethod): RequestMethod {
return function requestMethod(this: typeof net, reqOptions: RequestOptions): ClientRequest {
// eslint-disable-next-line @typescript-eslint/no-this-alias
Expand All @@ -140,8 +188,9 @@ function createWrappedRequestFactory(options: OrFalse<NetOptions>): WrappedReque

let span: Span | undefined;

const scope = getCurrentHub().getScope();
if (scope && options.tracing && options.tracing(method, url)) {
const hub = getCurrentHub();
const scope = hub.getScope();
if (scope && shouldCreateSpan(method, url)) {
const parentSpan = scope.getSpan();

if (parentSpan) {
Expand All @@ -150,7 +199,7 @@ function createWrappedRequestFactory(options: OrFalse<NetOptions>): WrappedReque
op: 'http.client',
});

if (options.tracingOrigins && options.tracingOrigins(method, url)) {
if (shouldAttachTraceData(method, url)) {
request.setHeader('sentry-trace', span.toTraceparent());
}
}
Expand All @@ -160,7 +209,7 @@ function createWrappedRequestFactory(options: OrFalse<NetOptions>): WrappedReque
.once('response', function (this: ClientRequest, res: IncomingMessage): void {
// eslint-disable-next-line @typescript-eslint/no-this-alias
const req = this;
if (options.breadcrumbs) {
if (options.breadcrumbs !== false) {
addRequestBreadcrumb('response', method, url, req, res);
}
if (span) {
Expand All @@ -174,7 +223,7 @@ function createWrappedRequestFactory(options: OrFalse<NetOptions>): WrappedReque
// eslint-disable-next-line @typescript-eslint/no-this-alias
const req = this;

if (options.breadcrumbs) {
if (options.breadcrumbs !== false) {
addRequestBreadcrumb('error', method, url, req, undefined);
}
if (span) {
Expand Down
Loading

0 comments on commit ecd978a

Please sign in to comment.