Skip to content

Commit

Permalink
feat(core): Add links to span options (#15453)
Browse files Browse the repository at this point in the history
part of #14991
  • Loading branch information
s1gr1d authored Feb 24, 2025
1 parent 63ea300 commit 14667ee
Show file tree
Hide file tree
Showing 5 changed files with 116 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import * as Sentry from '@sentry/browser';

window.Sentry = Sentry;

Sentry.init({
dsn: 'https://public@dsn.ingest.sentry.io/1337',
integrations: [],
tracesSampleRate: 1,
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
const rootSpan1 = Sentry.startInactiveSpan({ name: 'rootSpan1' });
rootSpan1.end();

const rootSpan2 = Sentry.startInactiveSpan({ name: 'rootSpan2' });
rootSpan2.end();

Sentry.startSpan(
{
name: 'rootSpan3',
links: [
{ context: rootSpan1.spanContext() },
{ context: rootSpan2.spanContext(), attributes: { 'sentry.link.type': 'previous_trace' } },
],
},
async () => {
Sentry.startSpan({ name: 'childSpan3.1' }, async childSpan1 => {
childSpan1.end();
});
},
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { expect } from '@playwright/test';
import type { TransactionEvent } from '@sentry/core';
import { sentryTest } from '../../../utils/fixtures';
import { envelopeRequestParser, shouldSkipTracingTest, waitForTransactionRequest } from '../../../utils/helpers';

sentryTest('should link spans by adding "links" to span options', async ({ getLocalTestUrl, page }) => {
if (shouldSkipTracingTest()) {
sentryTest.skip();
}

const rootSpan1Promise = waitForTransactionRequest(page, event => event.transaction === 'rootSpan1');
const rootSpan2Promise = waitForTransactionRequest(page, event => event.transaction === 'rootSpan2');
const rootSpan3Promise = waitForTransactionRequest(page, event => event.transaction === 'rootSpan3');

const url = await getLocalTestUrl({ testDir: __dirname });
await page.goto(url);

const rootSpan1 = envelopeRequestParser<TransactionEvent>(await rootSpan1Promise);
const rootSpan2 = envelopeRequestParser<TransactionEvent>(await rootSpan2Promise);
const rootSpan3 = envelopeRequestParser<TransactionEvent>(await rootSpan3Promise);

const rootSpan1_traceId = rootSpan1.contexts?.trace?.trace_id as string;
const rootSpan1_spanId = rootSpan1.contexts?.trace?.span_id as string;
const rootSpan2_traceId = rootSpan2.contexts?.trace?.trace_id as string;
const rootSpan2_spanId = rootSpan2.contexts?.trace?.span_id as string;

expect(rootSpan1.transaction).toBe('rootSpan1');
expect(rootSpan1.spans).toEqual([]);

expect(rootSpan3.transaction).toBe('rootSpan3');
expect(rootSpan3.spans?.length).toBe(1);
expect(rootSpan3.spans?.[0].description).toBe('childSpan3.1');

expect(rootSpan3.contexts?.trace?.links?.length).toBe(2);
expect(rootSpan3.contexts?.trace?.links).toEqual([
{
sampled: true,
span_id: rootSpan1_spanId,
trace_id: rootSpan1_traceId,
},
{
attributes: { 'sentry.link.type': 'previous_trace' },
sampled: true,
span_id: rootSpan2_spanId,
trace_id: rootSpan2_traceId,
},
]);
});
1 change: 1 addition & 0 deletions packages/core/src/tracing/sentrySpan.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ export class SentrySpan implements Span {
this._traceId = spanContext.traceId || generateTraceId();
this._spanId = spanContext.spanId || generateSpanId();
this._startTime = spanContext.startTimestamp || timestampInSeconds();
this._links = spanContext.links;

this._attributes = {};
this.setAttributes({
Expand Down
38 changes: 38 additions & 0 deletions packages/core/test/lib/tracing/trace.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1305,6 +1305,44 @@ describe('startInactiveSpan', () => {
});
});

it('allows to pass span links in span options', () => {
const rawSpan1 = startInactiveSpan({ name: 'pageload_span' });

// @ts-expect-error _links exists on span
expect(rawSpan1?._links).toEqual(undefined);

const rawSpan2 = startInactiveSpan({
name: 'GET users/[id]',
links: [
{
context: rawSpan1.spanContext(),
attributes: { 'sentry.link.type': 'previous_trace' },
},
],
});

const span1JSON = spanToJSON(rawSpan1);
const span2JSON = spanToJSON(rawSpan2);
const span2LinkJSON = span2JSON.links?.[0];

expect(span2LinkJSON?.attributes?.['sentry.link.type']).toBe('previous_trace');

// @ts-expect-error _links and _traceId exist on SentrySpan
expect(rawSpan2?._links?.[0].context.traceId).toEqual(rawSpan1._traceId);
// @ts-expect-error _links and _traceId exist on SentrySpan
expect(rawSpan2?._links?.[0].context.traceId).toEqual(span1JSON.trace_id);
expect(span2LinkJSON?.trace_id).toBe(span1JSON.trace_id);

// @ts-expect-error _links and _traceId exist on SentrySpan
expect(rawSpan2?._links?.[0].context.spanId).toEqual(rawSpan1?._spanId);
// @ts-expect-error _links and _traceId exist on SentrySpan
expect(rawSpan2?._links?.[0].context.spanId).toEqual(span1JSON.span_id);
expect(span2LinkJSON?.span_id).toBe(span1JSON.span_id);

// sampling decision is inherited
expect(span2LinkJSON?.sampled).toBe(Boolean(spanToJSON(rawSpan1).data['sentry.sample_rate']));
});

it('allows to force a transaction with forceTransaction=true', async () => {
const options = getDefaultTestClientOptions({ tracesSampleRate: 1.0 });
client = new TestClient(options);
Expand Down

0 comments on commit 14667ee

Please sign in to comment.