Skip to content

ref(tracing): Prework for initial tracestate implementation #3242

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions .jest/dom-environment.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
const JSDOMEnvironment = require('jest-environment-jsdom');

// TODO Node >= 8.3 includes the same TextEncoder and TextDecoder as exist in the browser, but they haven't yet been
// added to jsdom. Until they are, we can do it ourselves. Once they do, this file can go away.

// see https://github.com/jsdom/jsdom/issues/2524 and https://nodejs.org/api/util.html#util_class_util_textencoder

module.exports = class DOMEnvironment extends JSDOMEnvironment {
async setup() {
await super.setup();
if (typeof this.global.TextEncoder === 'undefined') {
const { TextEncoder, TextDecoder } = require('util');
this.global.TextEncoder = TextEncoder;
this.global.TextDecoder = TextDecoder;
}
}
};
4 changes: 2 additions & 2 deletions packages/core/src/baseclient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -402,8 +402,8 @@ export abstract class BaseClient<B extends Backend, O extends Options> implement
const options = this.getOptions();
const { environment, release, dist, maxValueLength = 250 } = options;

if (!('environment' in event)) {
event.environment = 'environment' in options ? environment : 'production';
if (event.environment === undefined && environment !== undefined) {
event.environment = environment;
}

if (event.release === undefined && release !== undefined) {
Expand Down
165 changes: 95 additions & 70 deletions packages/core/src/request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,29 +31,40 @@ function enhanceEventWithSdkInfo(event: Event, sdkInfo?: SdkInfo): Event {
return event;
}

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

if (event.type === 'transaction') {
return transactionToSentryRequest(eventWithSdkInfo, api);
}
return {
body: `${envelopeHeaders}\n${itemHeaders}\n${JSON.stringify(session)}`,
type: 'session',
url: api.getEnvelopeEndpointWithUrlEncodedAuth(),
body: JSON.stringify(eventWithSdkInfo),
type: event.type || 'event',
url: api.getStoreEndpointWithUrlEncodedAuth(),
};
}

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

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

const req: SentryRequest = {
body: JSON.stringify(sdkInfo ? enhanceEventWithSdkInfo(event, api.metadata.sdk) : event),
type: eventType,
url: useEnvelope ? api.getEnvelopeEndpointWithUrlEncodedAuth() : api.getStoreEndpointWithUrlEncodedAuth(),
};
const envelopeHeaders = JSON.stringify({
event_id: event.event_id,
sent_at: new Date().toISOString(),
...(sdkInfo && { sdk: sdkInfo }),

// https://develop.sentry.dev/sdk/envelopes/

// Since we don't need to manipulate envelopes nor store them, there is no
// exported concept of an Envelope with operations including serialization and
// deserialization. Instead, we only implement a minimal subset of the spec to
// serialize events inline here.
if (useEnvelope) {
const envelopeHeaders = JSON.stringify({
event_id: event.event_id,
sent_at: new Date().toISOString(),
...(sdkInfo && { sdk: sdkInfo }),

// trace context for dynamic sampling on relay
trace: {
trace_id: event.contexts?.trace?.trace_id,
public_key: api.getDsn().publicKey,
environment: event.environment || null,
release: event.release || null,
},
});

const itemHeaders = JSON.stringify({
type: event.type,

// TODO: Right now, sampleRate may or may not be defined (it won't be in the cases of inheritance and
// explicitly-set sampling decisions). Are we good with that?
sample_rates: [{ id: samplingMethod, rate: sampleRate }],

// The content-type is assumed to be 'application/json' and not part of
// the current spec for transaction items, so we don't bloat the request
// body with it.
//
// content_type: 'application/json',
//
// The length is optional. It must be the number of bytes in req.Body
// encoded as UTF-8. Since the server can figure this out and would
// otherwise refuse events that report the length incorrectly, we decided
// not to send the length to avoid problems related to reporting the wrong
// size and to reduce request body size.
//
// length: new TextEncoder().encode(req.body).length,
});

// The trailing newline is optional. We intentionally don't send it to avoid
// sending unnecessary bytes.
// trace context for dynamic sampling on relay
trace: {
trace_id: event.contexts?.trace?.trace_id,
public_key: api.getDsn().publicKey,
environment: event.environment || null,
release: event.release || null,
},
});

const itemHeaders = JSON.stringify({
type: event.type,

// TODO: Right now, sampleRate will be undefined in the cases of inheritance and explicitly-set sampling decisions.
sample_rates: [{ id: samplingMethod, rate: sampleRate }],

// Note: `content_type` and `length` were left out on purpose. Here's a quick explanation of why, along with the
// value to use if we ever decide to put them back in.
//
// const envelope = `${envelopeHeaders}\n${itemHeaders}\n${req.body}\n`;
const envelope = `${envelopeHeaders}\n${itemHeaders}\n${req.body}`;
req.body = envelope;
}
// `content_type`:
// Assumed to be 'application/json' and not part of the current spec for transaction items. No point in bloating the
// request body with it.
//
// would be:
// content_type: 'application/json',
//
// `length`:
// Optional and equal to the number of bytes in req.Body encoded as UTF-8. Since the server can figure this out and
// would otherwise refuse events that report the length incorrectly, we decided not to send the length to avoid
// problems related to reporting the wrong size and to reduce request body size.
//
// would be:
// length: new TextEncoder().encode(req.body).length,
});

const req: SentryRequest = {
// The trailing newline is optional; leave it off to avoid sending unnecessary bytes.
// body: `${envelopeHeaders}\n${itemHeaders}\n${JSON.stringify(event)\n}`,
body: `${envelopeHeaders}\n${itemHeaders}\n${JSON.stringify(event)}`,
type: 'transaction',
url: api.getEnvelopeEndpointWithUrlEncodedAuth(),
};

return req;
}

/**
* Create a SentryRequest from a session event.
*
* @param event The session event to send
* @param api Helper to provide the correct url for the request
* @returns SentryRequest in envelope form
*/
export function sessionToSentryRequest(session: Session, api: API): SentryRequest {
const sdkInfo = getSdkMetadataForEnvelopeHeader(api);
const envelopeHeaders = JSON.stringify({
sent_at: new Date().toISOString(),
...(sdkInfo && { sdk: sdkInfo }),
});
const itemHeaders = JSON.stringify({
type: 'session',
});

return {
body: `${envelopeHeaders}\n${itemHeaders}\n${JSON.stringify(session)}`,
type: 'session',
url: api.getEnvelopeEndpointWithUrlEncodedAuth(),
};
}
1 change: 1 addition & 0 deletions packages/core/src/sdk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export function initAndBind<F extends Client, O extends Options>(clientClass: Cl
if (options.debug === true) {
logger.enable();
}
options.environment = options.environment || 'production';
const hub = getCurrentHub();
const client = new clientClass(options);
hub.bindClient(client);
Expand Down
26 changes: 0 additions & 26 deletions packages/core/test/lib/base.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,6 @@ describe('BaseClient', () => {
const client = new TestClient({ dsn: PUBLIC_DSN });
client.captureException(new Error('test exception'));
expect(TestBackend.instance!.event).toEqual({
environment: 'production',
event_id: '42',
exception: {
values: [
Expand Down Expand Up @@ -245,7 +244,6 @@ describe('BaseClient', () => {
const client = new TestClient({ dsn: PUBLIC_DSN });
client.captureMessage('test message');
expect(TestBackend.instance!.event).toEqual({
environment: 'production',
event_id: '42',
level: 'info',
message: 'test message',
Expand Down Expand Up @@ -321,7 +319,6 @@ describe('BaseClient', () => {
client.captureEvent({ message: 'message' }, undefined, scope);
expect(TestBackend.instance!.event!.message).toBe('message');
expect(TestBackend.instance!.event).toEqual({
environment: 'production',
event_id: '42',
message: 'message',
timestamp: 2020,
Expand All @@ -335,7 +332,6 @@ describe('BaseClient', () => {
client.captureEvent({ message: 'message', timestamp: 1234 }, undefined, scope);
expect(TestBackend.instance!.event!.message).toBe('message');
expect(TestBackend.instance!.event).toEqual({
environment: 'production',
event_id: '42',
message: 'message',
timestamp: 1234,
Expand All @@ -348,28 +344,12 @@ describe('BaseClient', () => {
const scope = new Scope();
client.captureEvent({ message: 'message' }, { event_id: 'wat' }, scope);
expect(TestBackend.instance!.event!).toEqual({
environment: 'production',
event_id: 'wat',
message: 'message',
timestamp: 2020,
});
});

test('sets default environment to `production` it none provided', () => {
expect.assertions(1);
const client = new TestClient({
dsn: PUBLIC_DSN,
});
const scope = new Scope();
client.captureEvent({ message: 'message' }, undefined, scope);
expect(TestBackend.instance!.event!).toEqual({
environment: 'production',
event_id: '42',
message: 'message',
timestamp: 2020,
});
});

test('adds the configured environment', () => {
expect.assertions(1);
const client = new TestClient({
Expand Down Expand Up @@ -411,7 +391,6 @@ describe('BaseClient', () => {
const scope = new Scope();
client.captureEvent({ message: 'message' }, undefined, scope);
expect(TestBackend.instance!.event!).toEqual({
environment: 'production',
event_id: '42',
message: 'message',
release: 'v1.0.0',
Expand Down Expand Up @@ -452,7 +431,6 @@ describe('BaseClient', () => {
scope.setUser({ id: 'user' });
client.captureEvent({ message: 'message' }, undefined, scope);
expect(TestBackend.instance!.event!).toEqual({
environment: 'production',
event_id: '42',
extra: { b: 'b' },
message: 'message',
Expand All @@ -469,7 +447,6 @@ describe('BaseClient', () => {
scope.setFingerprint(['abcd']);
client.captureEvent({ message: 'message' }, undefined, scope);
expect(TestBackend.instance!.event!).toEqual({
environment: 'production',
event_id: '42',
fingerprint: ['abcd'],
message: 'message',
Expand Down Expand Up @@ -515,7 +492,6 @@ describe('BaseClient', () => {
expect(TestBackend.instance!.event!).toEqual({
breadcrumbs: [normalizedBreadcrumb, normalizedBreadcrumb, normalizedBreadcrumb],
contexts: normalizedObject,
environment: 'production',
event_id: '42',
extra: normalizedObject,
timestamp: 2020,
Expand Down Expand Up @@ -561,7 +537,6 @@ describe('BaseClient', () => {
expect(TestBackend.instance!.event!).toEqual({
breadcrumbs: [normalizedBreadcrumb, normalizedBreadcrumb, normalizedBreadcrumb],
contexts: normalizedObject,
environment: 'production',
event_id: '42',
extra: normalizedObject,
timestamp: 2020,
Expand Down Expand Up @@ -612,7 +587,6 @@ describe('BaseClient', () => {
expect(TestBackend.instance!.event!).toEqual({
breadcrumbs: [normalizedBreadcrumb, normalizedBreadcrumb, normalizedBreadcrumb],
contexts: normalizedObject,
environment: 'production',
event_id: '42',
extra: normalizedObject,
timestamp: 2020,
Expand Down
15 changes: 14 additions & 1 deletion packages/core/test/lib/sdk.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,19 @@ jest.mock('@sentry/hub', () => ({
bindClient(client: Client): boolean;
getClient(): boolean;
} {
return {
const mockHub = {
_stack: [],
getClient(): boolean {
return false;
},
bindClient(client: Client): boolean {
(this._stack as any[]).push({ client });
client.setupIntegrations();
return true;
},
};
global.__SENTRY__.hub = mockHub;
return mockHub;
},
}));

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

describe('initAndBind', () => {
test("sets environment to 'production' if none is provided", () => {
initAndBind(TestClient, { dsn: PUBLIC_DSN });
expect(global.__SENTRY__.hub._stack[0].client.getOptions().environment).toEqual('production');
});
test("doesn't overwrite given environment", () => {
initAndBind(TestClient, { dsn: PUBLIC_DSN, environment: 'dogpark' });
expect(global.__SENTRY__.hub._stack[0].client.getOptions().environment).toEqual('dogpark');
});

test('installs default integrations', () => {
const DEFAULT_INTEGRATIONS: Integration[] = [
new MockIntegration('MockIntegration 1'),
Expand Down
2 changes: 1 addition & 1 deletion packages/gatsby/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@
"ts",
"tsx"
],
"testEnvironment": "jsdom",
"testEnvironment": "../../.jest/dom-environment",
"testMatch": [
"**/*.test.ts",
"**/*.test.tsx"
Expand Down
2 changes: 1 addition & 1 deletion packages/react/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@
"ts",
"tsx"
],
"testEnvironment": "jsdom",
"testEnvironment": "../../.jest/dom-environment",
"testMatch": [
"**/*.test.ts",
"**/*.test.tsx"
Expand Down
Loading