Skip to content

Commit 02f1788

Browse files
committed
ref(tracing): Prework for initial tracestate implementation (#3242)
1 parent eb29b33 commit 02f1788

File tree

10 files changed

+284
-102
lines changed

10 files changed

+284
-102
lines changed

.jest/dom-environment.js

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
const JSDOMEnvironment = require('jest-environment-jsdom');
2+
3+
// TODO Node >= 8.3 includes the same TextEncoder and TextDecoder as exist in the browser, but they haven't yet been
4+
// added to jsdom. Until they are, we can do it ourselves. Once they do, this file can go away.
5+
6+
// see https://github.com/jsdom/jsdom/issues/2524 and https://nodejs.org/api/util.html#util_class_util_textencoder
7+
8+
module.exports = class DOMEnvironment extends JSDOMEnvironment {
9+
async setup() {
10+
await super.setup();
11+
if (typeof this.global.TextEncoder === 'undefined') {
12+
const { TextEncoder, TextDecoder } = require('util');
13+
this.global.TextEncoder = TextEncoder;
14+
this.global.TextDecoder = TextDecoder;
15+
}
16+
}
17+
};

packages/core/src/baseclient.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -402,8 +402,8 @@ export abstract class BaseClient<B extends Backend, O extends Options> implement
402402
const options = this.getOptions();
403403
const { environment, release, dist, maxValueLength = 250 } = options;
404404

405-
if (!('environment' in event)) {
406-
event.environment = 'environment' in options ? environment : 'production';
405+
if (event.environment === undefined && environment !== undefined) {
406+
event.environment = environment;
407407
}
408408

409409
if (event.release === undefined && release !== undefined) {

packages/core/src/request.ts

Lines changed: 95 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -31,29 +31,40 @@ function enhanceEventWithSdkInfo(event: Event, sdkInfo?: SdkInfo): Event {
3131
return event;
3232
}
3333

34-
/** Creates a SentryRequest from an event. */
35-
export function sessionToSentryRequest(session: Session, api: API): SentryRequest {
34+
/**
35+
* Create a SentryRequest from an error, message, or transaction event.
36+
*
37+
* @param event The event to send
38+
* @param api Helper to provide the correct url for the request
39+
* @returns SentryRequest representing the event
40+
*/
41+
export function eventToSentryRequest(event: Event, api: API): SentryRequest {
3642
const sdkInfo = getSdkMetadataForEnvelopeHeader(api);
37-
const envelopeHeaders = JSON.stringify({
38-
sent_at: new Date().toISOString(),
39-
...(sdkInfo && { sdk: sdkInfo }),
40-
});
41-
const itemHeaders = JSON.stringify({
42-
type: 'session',
43-
});
43+
const eventWithSdkInfo = sdkInfo ? enhanceEventWithSdkInfo(event, api.metadata.sdk) : event;
4444

45+
if (event.type === 'transaction') {
46+
return transactionToSentryRequest(eventWithSdkInfo, api);
47+
}
4548
return {
46-
body: `${envelopeHeaders}\n${itemHeaders}\n${JSON.stringify(session)}`,
47-
type: 'session',
48-
url: api.getEnvelopeEndpointWithUrlEncodedAuth(),
49+
body: JSON.stringify(eventWithSdkInfo),
50+
type: event.type || 'event',
51+
url: api.getStoreEndpointWithUrlEncodedAuth(),
4952
};
5053
}
5154

52-
/** Creates a SentryRequest from an event. */
53-
export function eventToSentryRequest(event: Event, api: API): SentryRequest {
55+
/**
56+
* Create a SentryRequest from a transaction event.
57+
*
58+
* Since we don't need to manipulate envelopes nor store them, there is no exported concept of an Envelope with
59+
* operations including serialization and deserialization. Instead, we only implement a minimal subset of the spec to
60+
* serialize events inline here. See https://develop.sentry.dev/sdk/envelopes/.
61+
*
62+
* @param event The transaction event to send
63+
* @param api Helper to provide the correct url for the request
64+
* @returns SentryRequest in envelope form
65+
*/
66+
export function transactionToSentryRequest(event: Event, api: API): SentryRequest {
5467
const sdkInfo = getSdkMetadataForEnvelopeHeader(api);
55-
const eventType = event.type || 'event';
56-
const useEnvelope = eventType === 'transaction';
5768

5869
const { transactionSampling, ...metadata } = event.debug_meta || {};
5970
const { method: samplingMethod, rate: sampleRate } = transactionSampling || {};
@@ -63,62 +74,76 @@ export function eventToSentryRequest(event: Event, api: API): SentryRequest {
6374
event.debug_meta = metadata;
6475
}
6576

66-
const req: SentryRequest = {
67-
body: JSON.stringify(sdkInfo ? enhanceEventWithSdkInfo(event, api.metadata.sdk) : event),
68-
type: eventType,
69-
url: useEnvelope ? api.getEnvelopeEndpointWithUrlEncodedAuth() : api.getStoreEndpointWithUrlEncodedAuth(),
70-
};
77+
const envelopeHeaders = JSON.stringify({
78+
event_id: event.event_id,
79+
sent_at: new Date().toISOString(),
80+
...(sdkInfo && { sdk: sdkInfo }),
7181

72-
// https://develop.sentry.dev/sdk/envelopes/
73-
74-
// Since we don't need to manipulate envelopes nor store them, there is no
75-
// exported concept of an Envelope with operations including serialization and
76-
// deserialization. Instead, we only implement a minimal subset of the spec to
77-
// serialize events inline here.
78-
if (useEnvelope) {
79-
const envelopeHeaders = JSON.stringify({
80-
event_id: event.event_id,
81-
sent_at: new Date().toISOString(),
82-
...(sdkInfo && { sdk: sdkInfo }),
83-
84-
// trace context for dynamic sampling on relay
85-
trace: {
86-
trace_id: event.contexts?.trace?.trace_id,
87-
public_key: api.getDsn().publicKey,
88-
environment: event.environment || null,
89-
release: event.release || null,
90-
},
91-
});
92-
93-
const itemHeaders = JSON.stringify({
94-
type: event.type,
95-
96-
// TODO: Right now, sampleRate may or may not be defined (it won't be in the cases of inheritance and
97-
// explicitly-set sampling decisions). Are we good with that?
98-
sample_rates: [{ id: samplingMethod, rate: sampleRate }],
99-
100-
// The content-type is assumed to be 'application/json' and not part of
101-
// the current spec for transaction items, so we don't bloat the request
102-
// body with it.
103-
//
104-
// content_type: 'application/json',
105-
//
106-
// The length is optional. It must be the number of bytes in req.Body
107-
// encoded as UTF-8. Since the server can figure this out and would
108-
// otherwise refuse events that report the length incorrectly, we decided
109-
// not to send the length to avoid problems related to reporting the wrong
110-
// size and to reduce request body size.
111-
//
112-
// length: new TextEncoder().encode(req.body).length,
113-
});
114-
115-
// The trailing newline is optional. We intentionally don't send it to avoid
116-
// sending unnecessary bytes.
82+
// trace context for dynamic sampling on relay
83+
trace: {
84+
trace_id: event.contexts?.trace?.trace_id,
85+
public_key: api.getDsn().publicKey,
86+
environment: event.environment || null,
87+
release: event.release || null,
88+
},
89+
});
90+
91+
const itemHeaders = JSON.stringify({
92+
type: event.type,
93+
94+
// TODO: Right now, sampleRate will be undefined in the cases of inheritance and explicitly-set sampling decisions.
95+
sample_rates: [{ id: samplingMethod, rate: sampleRate }],
96+
97+
// Note: `content_type` and `length` were left out on purpose. Here's a quick explanation of why, along with the
98+
// value to use if we ever decide to put them back in.
11799
//
118-
// const envelope = `${envelopeHeaders}\n${itemHeaders}\n${req.body}\n`;
119-
const envelope = `${envelopeHeaders}\n${itemHeaders}\n${req.body}`;
120-
req.body = envelope;
121-
}
100+
// `content_type`:
101+
// Assumed to be 'application/json' and not part of the current spec for transaction items. No point in bloating the
102+
// request body with it.
103+
//
104+
// would be:
105+
// content_type: 'application/json',
106+
//
107+
// `length`:
108+
// Optional and equal to the number of bytes in req.Body encoded as UTF-8. Since the server can figure this out and
109+
// would otherwise refuse events that report the length incorrectly, we decided not to send the length to avoid
110+
// problems related to reporting the wrong size and to reduce request body size.
111+
//
112+
// would be:
113+
// length: new TextEncoder().encode(req.body).length,
114+
});
115+
116+
const req: SentryRequest = {
117+
// The trailing newline is optional; leave it off to avoid sending unnecessary bytes.
118+
// body: `${envelopeHeaders}\n${itemHeaders}\n${JSON.stringify(event)\n}`,
119+
body: `${envelopeHeaders}\n${itemHeaders}\n${JSON.stringify(event)}`,
120+
type: 'transaction',
121+
url: api.getEnvelopeEndpointWithUrlEncodedAuth(),
122+
};
122123

123124
return req;
124125
}
126+
127+
/**
128+
* Create a SentryRequest from a session event.
129+
*
130+
* @param event The session event to send
131+
* @param api Helper to provide the correct url for the request
132+
* @returns SentryRequest in envelope form
133+
*/
134+
export function sessionToSentryRequest(session: Session, api: API): SentryRequest {
135+
const sdkInfo = getSdkMetadataForEnvelopeHeader(api);
136+
const envelopeHeaders = JSON.stringify({
137+
sent_at: new Date().toISOString(),
138+
...(sdkInfo && { sdk: sdkInfo }),
139+
});
140+
const itemHeaders = JSON.stringify({
141+
type: 'session',
142+
});
143+
144+
return {
145+
body: `${envelopeHeaders}\n${itemHeaders}\n${JSON.stringify(session)}`,
146+
type: 'session',
147+
url: api.getEnvelopeEndpointWithUrlEncodedAuth(),
148+
};
149+
}

packages/core/src/sdk.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ export function initAndBind<F extends Client, O extends Options>(clientClass: Cl
1616
if (options.debug === true) {
1717
logger.enable();
1818
}
19+
options.environment = options.environment || 'production';
1920
const hub = getCurrentHub();
2021
const client = new clientClass(options);
2122
hub.bindClient(client);

packages/core/test/lib/base.test.ts

Lines changed: 0 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,6 @@ describe('BaseClient', () => {
176176
const client = new TestClient({ dsn: PUBLIC_DSN });
177177
client.captureException(new Error('test exception'));
178178
expect(TestBackend.instance!.event).toEqual({
179-
environment: 'production',
180179
event_id: '42',
181180
exception: {
182181
values: [
@@ -245,7 +244,6 @@ describe('BaseClient', () => {
245244
const client = new TestClient({ dsn: PUBLIC_DSN });
246245
client.captureMessage('test message');
247246
expect(TestBackend.instance!.event).toEqual({
248-
environment: 'production',
249247
event_id: '42',
250248
level: 'info',
251249
message: 'test message',
@@ -321,7 +319,6 @@ describe('BaseClient', () => {
321319
client.captureEvent({ message: 'message' }, undefined, scope);
322320
expect(TestBackend.instance!.event!.message).toBe('message');
323321
expect(TestBackend.instance!.event).toEqual({
324-
environment: 'production',
325322
event_id: '42',
326323
message: 'message',
327324
timestamp: 2020,
@@ -335,7 +332,6 @@ describe('BaseClient', () => {
335332
client.captureEvent({ message: 'message', timestamp: 1234 }, undefined, scope);
336333
expect(TestBackend.instance!.event!.message).toBe('message');
337334
expect(TestBackend.instance!.event).toEqual({
338-
environment: 'production',
339335
event_id: '42',
340336
message: 'message',
341337
timestamp: 1234,
@@ -348,28 +344,12 @@ describe('BaseClient', () => {
348344
const scope = new Scope();
349345
client.captureEvent({ message: 'message' }, { event_id: 'wat' }, scope);
350346
expect(TestBackend.instance!.event!).toEqual({
351-
environment: 'production',
352347
event_id: 'wat',
353348
message: 'message',
354349
timestamp: 2020,
355350
});
356351
});
357352

358-
test('sets default environment to `production` it none provided', () => {
359-
expect.assertions(1);
360-
const client = new TestClient({
361-
dsn: PUBLIC_DSN,
362-
});
363-
const scope = new Scope();
364-
client.captureEvent({ message: 'message' }, undefined, scope);
365-
expect(TestBackend.instance!.event!).toEqual({
366-
environment: 'production',
367-
event_id: '42',
368-
message: 'message',
369-
timestamp: 2020,
370-
});
371-
});
372-
373353
test('adds the configured environment', () => {
374354
expect.assertions(1);
375355
const client = new TestClient({
@@ -411,7 +391,6 @@ describe('BaseClient', () => {
411391
const scope = new Scope();
412392
client.captureEvent({ message: 'message' }, undefined, scope);
413393
expect(TestBackend.instance!.event!).toEqual({
414-
environment: 'production',
415394
event_id: '42',
416395
message: 'message',
417396
release: 'v1.0.0',
@@ -452,7 +431,6 @@ describe('BaseClient', () => {
452431
scope.setUser({ id: 'user' });
453432
client.captureEvent({ message: 'message' }, undefined, scope);
454433
expect(TestBackend.instance!.event!).toEqual({
455-
environment: 'production',
456434
event_id: '42',
457435
extra: { b: 'b' },
458436
message: 'message',
@@ -469,7 +447,6 @@ describe('BaseClient', () => {
469447
scope.setFingerprint(['abcd']);
470448
client.captureEvent({ message: 'message' }, undefined, scope);
471449
expect(TestBackend.instance!.event!).toEqual({
472-
environment: 'production',
473450
event_id: '42',
474451
fingerprint: ['abcd'],
475452
message: 'message',
@@ -515,7 +492,6 @@ describe('BaseClient', () => {
515492
expect(TestBackend.instance!.event!).toEqual({
516493
breadcrumbs: [normalizedBreadcrumb, normalizedBreadcrumb, normalizedBreadcrumb],
517494
contexts: normalizedObject,
518-
environment: 'production',
519495
event_id: '42',
520496
extra: normalizedObject,
521497
timestamp: 2020,
@@ -561,7 +537,6 @@ describe('BaseClient', () => {
561537
expect(TestBackend.instance!.event!).toEqual({
562538
breadcrumbs: [normalizedBreadcrumb, normalizedBreadcrumb, normalizedBreadcrumb],
563539
contexts: normalizedObject,
564-
environment: 'production',
565540
event_id: '42',
566541
extra: normalizedObject,
567542
timestamp: 2020,
@@ -612,7 +587,6 @@ describe('BaseClient', () => {
612587
expect(TestBackend.instance!.event!).toEqual({
613588
breadcrumbs: [normalizedBreadcrumb, normalizedBreadcrumb, normalizedBreadcrumb],
614589
contexts: normalizedObject,
615-
environment: 'production',
616590
event_id: '42',
617591
extra: normalizedObject,
618592
timestamp: 2020,

packages/core/test/lib/sdk.test.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,19 @@ jest.mock('@sentry/hub', () => ({
1414
bindClient(client: Client): boolean;
1515
getClient(): boolean;
1616
} {
17-
return {
17+
const mockHub = {
18+
_stack: [],
1819
getClient(): boolean {
1920
return false;
2021
},
2122
bindClient(client: Client): boolean {
23+
(this._stack as any[]).push({ client });
2224
client.setupIntegrations();
2325
return true;
2426
},
2527
};
28+
global.__SENTRY__.hub = mockHub;
29+
return mockHub;
2630
},
2731
}));
2832

@@ -41,6 +45,15 @@ describe('SDK', () => {
4145
});
4246

4347
describe('initAndBind', () => {
48+
test("sets environment to 'production' if none is provided", () => {
49+
initAndBind(TestClient, { dsn: PUBLIC_DSN });
50+
expect(global.__SENTRY__.hub._stack[0].client.getOptions().environment).toEqual('production');
51+
});
52+
test("doesn't overwrite given environment", () => {
53+
initAndBind(TestClient, { dsn: PUBLIC_DSN, environment: 'dogpark' });
54+
expect(global.__SENTRY__.hub._stack[0].client.getOptions().environment).toEqual('dogpark');
55+
});
56+
4457
test('installs default integrations', () => {
4558
const DEFAULT_INTEGRATIONS: Integration[] = [
4659
new MockIntegration('MockIntegration 1'),

packages/gatsby/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@
7676
"ts",
7777
"tsx"
7878
],
79-
"testEnvironment": "jsdom",
79+
"testEnvironment": "../../.jest/dom-environment",
8080
"testMatch": [
8181
"**/*.test.ts",
8282
"**/*.test.tsx"

packages/react/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@
8989
"ts",
9090
"tsx"
9191
],
92-
"testEnvironment": "jsdom",
92+
"testEnvironment": "../../.jest/dom-environment",
9393
"testMatch": [
9494
"**/*.test.ts",
9595
"**/*.test.tsx"

0 commit comments

Comments
 (0)