Skip to content

Commit 5116935

Browse files
committed
feat: Refactor to use global extension hub method
1 parent dcf4c7f commit 5116935

File tree

17 files changed

+219
-166
lines changed

17 files changed

+219
-166
lines changed

packages/apm/src/hub.ts

-92
This file was deleted.

packages/apm/src/hubextensions.ts

+84
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
import { getMainCarrier, Hub } from '@sentry/hub';
2+
import { SpanContext } from '@sentry/types';
3+
4+
import { Span } from './span';
5+
6+
/**
7+
* Checks whether given value is instance of Span
8+
* @param span value to check
9+
*/
10+
function isSpanInstance(span: unknown): span is Span {
11+
return span instanceof Span;
12+
}
13+
14+
/** Returns all trace headers that are currently on the top scope. */
15+
function traceHeaders(): { [key: string]: string } {
16+
// @ts-ignore
17+
const that = this as Hub;
18+
const scope = that.getScope();
19+
if (scope) {
20+
const span = scope.getSpan();
21+
if (span) {
22+
return {
23+
'sentry-trace': span.toTraceparent(),
24+
};
25+
}
26+
}
27+
return {};
28+
}
29+
30+
/**
31+
* This functions starts a span. If argument passed is of type `Span`, it'll run sampling on it if configured
32+
* and attach a `SpanRecorder`. If it's of type `SpanContext` and there is already a `Span` on the Scope,
33+
* the created Span will have a reference to it and become it's child. Otherwise it'll crete a new `Span`.
34+
*
35+
* @param span Already constructed span which should be started or properties with which the span should be created
36+
*/
37+
function startSpan(spanOrSpanContext?: Span | SpanContext, forceNoChild: boolean = false): Span {
38+
// @ts-ignore
39+
const that = this as Hub;
40+
const scope = that.getScope();
41+
const client = that.getClient();
42+
let span;
43+
44+
if (!isSpanInstance(spanOrSpanContext) && !forceNoChild) {
45+
if (scope) {
46+
const parentSpan = scope.getSpan() as Span;
47+
if (parentSpan) {
48+
span = parentSpan.child(spanOrSpanContext);
49+
}
50+
}
51+
}
52+
53+
if (!isSpanInstance(span)) {
54+
span = new Span(spanOrSpanContext, that);
55+
}
56+
57+
if (span.sampled === undefined && span.transaction !== undefined) {
58+
const sampleRate = (client && client.getOptions().tracesSampleRate) || 0;
59+
span.sampled = Math.random() < sampleRate;
60+
}
61+
62+
if (span.sampled) {
63+
const experimentsOptions = (client && client.getOptions()._experiments) || {};
64+
span.initFinishedSpans(experimentsOptions.maxSpans);
65+
}
66+
67+
return span;
68+
}
69+
70+
/**
71+
* This patches the global object and injects the APM extensions methods
72+
*/
73+
export function addExtensionMethods(): void {
74+
const carrier = getMainCarrier();
75+
if (carrier.__SENTRY__) {
76+
carrier.__SENTRY__.extensions = carrier.__SENTRY__.extensions || {};
77+
if (!carrier.__SENTRY__.extensions.startSpan) {
78+
carrier.__SENTRY__.extensions.startSpan = startSpan;
79+
}
80+
if (!carrier.__SENTRY__.extensions.traceHeaders) {
81+
carrier.__SENTRY__.extensions.traceHeaders = traceHeaders;
82+
}
83+
}
84+
}

packages/apm/src/index.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
1+
import { addExtensionMethods } from './hubextensions';
12
import * as ApmIntegrations from './integrations';
23

3-
export { Hub, makeApmHubMain as makeMain } from './hub';
44
export { ApmIntegrations as Integrations };
55
export { Span, TRACEPARENT_REGEXP } from './span';
6+
7+
// We are patching the global object with our hub extension methods
8+
addExtensionMethods();

packages/apm/src/integrations/express.ts

+1-4
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
1-
import { EventProcessor, Integration } from '@sentry/types';
1+
import { EventProcessor, Hub, Integration } from '@sentry/types';
22
import { logger } from '@sentry/utils';
33
// tslint:disable-next-line:no-implicit-dependencies
44
import { Application, ErrorRequestHandler, NextFunction, Request, RequestHandler, Response } from 'express';
55

6-
import { Hub, makeApmHubMain } from '../hub';
7-
86
/**
97
* Express integration
108
*
@@ -32,7 +30,6 @@ export class Express implements Integration {
3230
*/
3331
public constructor(options: { app?: Application } = {}) {
3432
this._app = options.app;
35-
makeApmHubMain();
3633
}
3734

3835
/**

packages/apm/src/integrations/tracing.ts

+1-4
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
1-
import { EventProcessor, Integration } from '@sentry/types';
1+
import { EventProcessor, Hub, Integration } from '@sentry/types';
22
import { fill, getGlobalObject, isMatchingPattern, logger, supportsNativeFetch } from '@sentry/utils';
33

4-
import { Hub, makeApmHubMain } from '../hub';
5-
64
/** JSDoc */
75
interface TracingOptions {
86
tracingOrigins?: Array<string | RegExp>;
@@ -37,7 +35,6 @@ export class Tracing implements Integration {
3735
* @param _options TracingOptions
3836
*/
3937
public constructor(private readonly _options: TracingOptions = {}) {
40-
makeApmHubMain();
4138
if (!Array.isArray(_options.tracingOrigins) || _options.tracingOrigins.length === 0) {
4239
const defaultTracingOrigins = ['localhost', /^\//];
4340
logger.warn(

packages/apm/src/integrations/transactionactivity.ts

+1-4
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
1-
import { EventProcessor, Integration, Span, SpanContext } from '@sentry/types';
1+
import { EventProcessor, Hub, Integration, Span, SpanContext } from '@sentry/types';
22
import { getGlobalObject } from '@sentry/utils';
33

4-
import { Hub, makeApmHubMain } from '../hub';
5-
64
/** JSDoc */
75
interface TransactionActivityOptions {
86
idleTimeout: number;
@@ -55,7 +53,6 @@ export class TransactionActivity implements Integration {
5553
* @inheritDoc
5654
*/
5755
public constructor(public readonly _options?: Partial<TransactionActivityOptions>) {
58-
makeApmHubMain();
5956
const defaults = {
6057
idleTimeout: 500,
6158
startTransactionOnLocationChange: true,

packages/apm/src/span.ts

+1-3
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,9 @@
11
// tslint:disable:max-classes-per-file
22

3-
import { getCurrentHub } from '@sentry/hub';
3+
import { getCurrentHub, Hub } from '@sentry/hub';
44
import { Span as SpanInterface, SpanContext } from '@sentry/types';
55
import { logger, timestampWithMs, uuid4 } from '@sentry/utils';
66

7-
import { Hub } from './hub';
8-
97
export const TRACEPARENT_REGEXP = new RegExp(
108
'^[ \\t]*' + // whitespace
119
'([0-9a-f]{32})?' + // trace_id

packages/apm/test/hub.test.ts

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import { Hub, Scope } from '@sentry/hub';
2+
3+
import { addExtensionMethods } from '../src/hubextensions';
4+
5+
addExtensionMethods();
6+
const clientFn: any = jest.fn();
7+
8+
describe('Hub', () => {
9+
afterEach(() => {
10+
jest.resetAllMocks();
11+
jest.useRealTimers();
12+
});
13+
14+
describe('spans', () => {
15+
describe('start', () => {
16+
test('simple', () => {
17+
const hub = new Hub(clientFn);
18+
const span = hub.startSpan() as any;
19+
expect(span._spanId).toBeTruthy();
20+
});
21+
22+
test('inherits from parent span', () => {
23+
const myScope = new Scope();
24+
const hub = new Hub(clientFn, myScope);
25+
const parentSpan = hub.startSpan({}) as any;
26+
expect(parentSpan._parentId).toBeFalsy();
27+
hub.configureScope(scope => {
28+
scope.setSpan(parentSpan);
29+
});
30+
const span = hub.startSpan({}) as any;
31+
expect(span._parentSpanId).toBeTruthy();
32+
});
33+
});
34+
});
35+
});

packages/apm/test/span.test.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import { Scope } from '@sentry/hub';
1+
import { Hub, Scope } from '@sentry/hub';
22

3-
import { Hub, Span, TRACEPARENT_REGEXP } from '../src';
3+
import { Span, TRACEPARENT_REGEXP } from '../src';
44

55
describe('Span', () => {
66
let hub: Hub;

packages/hub/src/hub.ts

+31
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ import {
88
Integration,
99
IntegrationClass,
1010
Severity,
11+
Span,
12+
SpanContext,
1113
User,
1214
} from '@sentry/types';
1315
import {
@@ -375,12 +377,41 @@ export class Hub implements HubInterface {
375377
return null;
376378
}
377379
}
380+
381+
/**
382+
* @inheritdoc
383+
*/
384+
public startSpan(spanOrSpanContext?: Span | SpanContext, forceNoChild: boolean = false): Span {
385+
return this._callExtensionMethod<Span>('startSpan', spanOrSpanContext, forceNoChild);
386+
}
387+
388+
/**
389+
* @inheritdoc
390+
*/
391+
public traceHeaders(): { [key: string]: string } {
392+
return this._callExtensionMethod<{ [key: string]: string }>('traceHeaders');
393+
}
394+
395+
/**
396+
* Calls global extension method and binding current instance to the function call
397+
*/
398+
// @ts-ignore
399+
private _callExtensionMethod<T>(method: string, ...args: any[]): T {
400+
const carrier = getMainCarrier();
401+
const sentry = carrier.__SENTRY__;
402+
// tslint:disable-next-line: strict-type-predicates
403+
if (sentry && sentry.extensions && typeof sentry.extensions[method] === 'function') {
404+
return sentry.extensions[method].apply(this, args);
405+
}
406+
logger.warn(`Extension method ${method} couldn't be found, doing nothing.`);
407+
}
378408
}
379409

380410
/** Returns the global shim registry. */
381411
export function getMainCarrier(): Carrier {
382412
const carrier = getGlobalObject();
383413
carrier.__SENTRY__ = carrier.__SENTRY__ || {
414+
extensions: {},
384415
hub: undefined,
385416
};
386417
return carrier;

packages/hub/src/interfaces.ts

+4
Original file line numberDiff line numberDiff line change
@@ -19,5 +19,9 @@ export interface Layer {
1919
export interface Carrier {
2020
__SENTRY__?: {
2121
hub?: Hub;
22+
/**
23+
* These are extension methods for the hub, the current instance of the hub will be bound to it
24+
*/
25+
extensions?: { [key: string]: Function };
2226
};
2327
}

packages/hub/test/global.test.ts

+16-1
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,23 @@ describe('global', () => {
1515
});
1616

1717
test('getGlobalHub', () => {
18-
const newestHub = new Hub(undefined, [], 999999);
18+
const newestHub = new Hub(undefined, undefined, 999999);
1919
(global as any).__SENTRY__.hub = newestHub;
2020
expect(getCurrentHub()).toBe(newestHub);
2121
});
22+
23+
test('hub extension methods receive correct hub instance', () => {
24+
const newestHub = new Hub(undefined, undefined, 999999);
25+
(global as any).__SENTRY__.hub = newestHub;
26+
// tslint:disable-next-line: typedef
27+
const fn = jest.fn().mockImplementation(function(...args: []) {
28+
expect(this).toBe(newestHub);
29+
expect(args).toEqual([1, 2, 3]);
30+
});
31+
(global as any).__SENTRY__.extensions = {};
32+
(global as any).__SENTRY__.extensions.testy = fn;
33+
(getCurrentHub() as any)._callExtensionMethod('testy', 1, 2, 3);
34+
expect(fn).toBeCalled();
35+
});
36+
// (global as any).__SENTRY__
2237
});

0 commit comments

Comments
 (0)