Skip to content

fix(tracing): Remove undefined tracestate data rather than setting it to null #3373

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
5 changes: 4 additions & 1 deletion packages/tracing/src/span.ts
Original file line number Diff line number Diff line change
Expand Up @@ -378,6 +378,9 @@ export class Span implements SpanInterface {

const { environment, release } = client.getOptions() || {};

// only define a `user` object if there's going to be something in it
const user = userId || userSegment ? { id: userId, segment: userSegment } : undefined;

// TODO - the only reason we need the non-null assertion on `dsn.publicKey` (below) is because `dsn.publicKey` has
// to be optional while we transition from `dsn.user` -> `dsn.publicKey`. Once `dsn.user` is removed, we can make
// `dsn.publicKey` required and remove the `!`.
Expand All @@ -388,7 +391,7 @@ export class Span implements SpanInterface {
release,
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
public_key: dsn.publicKey!,
user: { id: userId, segment: userSegment },
user,
})}`;
}

Expand Down
14 changes: 5 additions & 9 deletions packages/tracing/src/utils.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { getCurrentHub, Hub } from '@sentry/hub';
import { Options, TraceparentData, Transaction } from '@sentry/types';
import { SentryError, unicodeToBase64 } from '@sentry/utils';
import { dropUndefinedKeys, SentryError, unicodeToBase64 } from '@sentry/utils';

export const SENTRY_TRACE_REGEX = new RegExp(
'^[ \\t]*' + // whitespace
Expand Down Expand Up @@ -146,10 +146,10 @@ export { stripUrlQueryAndFragment } from '@sentry/utils';

type SentryTracestateData = {
trace_id: string;
environment: string | undefined | null;
release: string | undefined | null;
environment?: string;
release?: string;
public_key: string;
user: { id: string | undefined | null; segment: string | undefined | null };
user?: { id?: string; segment?: string };
};

/**
Expand All @@ -161,10 +161,6 @@ type SentryTracestateData = {
export function computeTracestateValue(data: SentryTracestateData): string {
// `JSON.stringify` will drop keys with undefined values, but not ones with null values, so this prevents
// these values from being dropped if they haven't been set by `Sentry.init`
data.environment = data.environment || null;
data.release = data.release || null;
data.user.id = data.user.id || null;
data.user.segment = data.user.segment || null;

// See https://www.w3.org/TR/trace-context/#tracestate-header-field-values
// The spec for tracestate header values calls for a string of the form
Expand All @@ -175,7 +171,7 @@ export function computeTracestateValue(data: SentryTracestateData): string {
// used to pad the end of base64 values though, so to avoid confusion, we strip them off. (Most languages' base64
// decoding functions (including those in JS) are able to function without the padding.)
try {
return unicodeToBase64(JSON.stringify(data)).replace(/={1,2}$/, '');
return unicodeToBase64(JSON.stringify(dropUndefinedKeys(data))).replace(/={1,2}$/, '');
} catch (err) {
throw new SentryError(`[Tracing] Error computing tracestate value from data: ${err}\nData: ${data}`);
}
Expand Down
11 changes: 4 additions & 7 deletions packages/tracing/test/httpheaders.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -166,14 +166,13 @@ describe('tracestate', () => {

describe('mutibility', () => {
it("won't include data set after transaction is created if there's an inherited value", () => {
expect.assertions(2);
expect.assertions(1);

const inheritedTracestate = `sentry=${computeTracestateValue({
trace_id: '12312012090820131231201209082013',
environment: 'dogpark',
release: 'off.leash.trail',
public_key: 'dogsarebadatkeepingsecrets',
user: { id: undefined, segment: undefined },
})}`;

const transaction = new Transaction(
Expand All @@ -194,8 +193,7 @@ describe('tracestate', () => {
const tracestateValue = (transaction as any)._toTracestate().replace('sentry=', '');
const reinflatedTracestate = JSON.parse(base64ToUnicode(tracestateValue));

expect(reinflatedTracestate.user.id).toBeNull();
expect(reinflatedTracestate.user.segment).toBeNull();
expect(reinflatedTracestate.user).toBeUndefined();
});
});

Expand All @@ -221,7 +219,7 @@ describe('tracestate', () => {
});

it("won't include data set after first call to `getTraceHeaders`", () => {
expect.assertions(2);
expect.assertions(1);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are no need to explicitly state number of assertions in sync tests, but it's fine to leave it. Personal preference.

Copy link
Member Author

@lobsterkatie lobsterkatie Apr 12, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

^^^ Not from this PR (I hadn't rebased yet) but I did the expect.assertions() because the expects are in a callback. Less of an issue in this test than some of the others, because the callback is explicitly called and included in the test itself, unlike, for instance, this one:

        it('uses existing sentry tracestate in envelope headers rather than creating a new one', () => {
          // one here, and two inside the `captureEvent` implementation above
          expect.assertions(3);


const transaction = new Transaction(
{
Expand All @@ -238,8 +236,7 @@ describe('tracestate', () => {
const tracestateValue = (transaction as any)._toTracestate().replace('sentry=', '');
const reinflatedTracestate = JSON.parse(base64ToUnicode(tracestateValue));

expect(reinflatedTracestate.user.id).toBeNull();
expect(reinflatedTracestate.user.segment).toBeNull();
expect(reinflatedTracestate.user).toBeUndefined();
});
});
});
Expand Down