Skip to content

Commit

Permalink
feat(core): Add getTraceData function (#13134)
Browse files Browse the repository at this point in the history
Add  a `getTraceData` function to the `@sentry/core` package and
re-exports it in none-browser SDKs inheriting from it.

---------

Co-authored-by: Andrei <[email protected]>
  • Loading branch information
Lms24 and andreiborza authored Aug 2, 2024
1 parent 2b7fa21 commit 7e1a641
Show file tree
Hide file tree
Showing 16 changed files with 167 additions and 129 deletions.
1 change: 1 addition & 0 deletions packages/astro/src/index.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ export {
getSentryRelease,
getSpanDescendants,
getSpanStatusFromHttpCode,
getTraceData,
graphqlIntegration,
hapiIntegration,
httpIntegration,
Expand Down
86 changes: 0 additions & 86 deletions packages/astro/src/server/meta.ts

This file was deleted.

14 changes: 11 additions & 3 deletions packages/astro/src/server/middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import type { Client, Scope, Span, SpanAttributes } from '@sentry/types';
import { addNonEnumerableProperty, objectify, stripUrlQueryAndFragment } from '@sentry/utils';
import type { APIContext, MiddlewareResponseHandler } from 'astro';

import { getTracingMetaTags } from './meta';
import { getTraceData } from '@sentry/node';

type MiddlewareOptions = {
/**
Expand Down Expand Up @@ -189,9 +189,17 @@ function addMetaTagToHead(htmlChunk: string, scope: Scope, client: Client, span?
if (typeof htmlChunk !== 'string') {
return htmlChunk;
}
const { 'sentry-trace': sentryTrace, baggage } = getTraceData(span, scope, client);

if (!sentryTrace) {
return htmlChunk;
}

const sentryTraceMeta = `<meta name="sentry-trace" content="${sentryTrace}"/>`;
const baggageMeta = baggage && `<meta name="baggage" content="${baggage}"/>`;

const content = `<head>\n${sentryTraceMeta}`.concat(baggageMeta ? `\n${baggageMeta}` : '', '\n');

const { sentryTrace, baggage } = getTracingMetaTags(span, scope, client);
const content = `<head>\n${sentryTrace}\n${baggage}\n`;
return htmlChunk.replace('<head>', content);
}

Expand Down
2 changes: 1 addition & 1 deletion packages/astro/test/integration/middleware/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { describe, expect, it, vi } from 'vitest';
import { onRequest } from '../../../src/integration/middleware';

vi.mock('../../../src/server/meta', () => ({
getTracingMetaTags: () => ({
getTracingMetaTagValues: () => ({
sentryTrace: '<meta name="sentry-trace" content="123">',
baggage: '<meta name="baggage" content="abc">',
}),
Expand Down
13 changes: 11 additions & 2 deletions packages/astro/test/server/middleware.test.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import { SEMANTIC_ATTRIBUTE_SENTRY_SOURCE } from '@sentry/core';
import * as SentryCore from '@sentry/core';
import * as SentryNode from '@sentry/node';
import type { Client, Span } from '@sentry/types';
import { vi } from 'vitest';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';

import { handleRequest, interpolateRouteFromUrlAndParams } from '../../src/server/middleware';

vi.mock('../../src/server/meta', () => ({
getTracingMetaTags: () => ({
getTracingMetaTagValues: () => ({
sentryTrace: '<meta name="sentry-trace" content="123">',
baggage: '<meta name="baggage" content="abc">',
}),
Expand All @@ -28,10 +29,18 @@ describe('sentryMiddleware', () => {
setPropagationContext: vi.fn(),
getSpan: getSpanMock,
setSDKProcessingMetadata: setSDKProcessingMetadataMock,
getPropagationContext: () => ({}),
} as any;
});
vi.spyOn(SentryNode, 'getActiveSpan').mockImplementation(getSpanMock);
vi.spyOn(SentryNode, 'getClient').mockImplementation(() => ({}) as Client);
vi.spyOn(SentryNode, 'getTraceData').mockImplementation(() => ({
'sentry-trace': '123',
baggage: 'abc',
}));
vi.spyOn(SentryCore, 'getDynamicSamplingContextFromSpan').mockImplementation(() => ({
transaction: 'test',
}));
});

const nextResult = Promise.resolve(new Response(null, { status: 200, headers: new Headers() }));
Expand Down
1 change: 1 addition & 0 deletions packages/aws-serverless/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export {
getCurrentScope,
getGlobalScope,
getIsolationScope,
getTraceData,
setCurrentClient,
Scope,
SDK_VERSION,
Expand Down
1 change: 1 addition & 0 deletions packages/bun/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ export {
getCurrentScope,
getGlobalScope,
getIsolationScope,
getTraceData,
setCurrentClient,
Scope,
SDK_VERSION,
Expand Down
1 change: 1 addition & 0 deletions packages/cloudflare/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ export {
setMeasurement,
getActiveSpan,
getRootSpan,
getTraceData,
startSpan,
startInactiveSpan,
startSpanManual,
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ export {
} from './utils/spanUtils';
export { parseSampleRate } from './utils/parseSampleRate';
export { applySdkMetadata } from './utils/sdkMetadata';
export { getTraceData } from './utils/traceData';
export { DEFAULT_ENVIRONMENT } from './constants';
export { addBreadcrumb } from './breadcrumbs';
export { functionToStringIntegration } from './integrations/functiontostring';
Expand Down
89 changes: 89 additions & 0 deletions packages/core/src/utils/traceData.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import type { Client, Scope, Span } from '@sentry/types';
import {
TRACEPARENT_REGEXP,
dynamicSamplingContextToSentryBaggageHeader,
generateSentryTraceHeader,
logger,
} from '@sentry/utils';
import { getClient, getCurrentScope } from '../currentScopes';
import { getDynamicSamplingContextFromClient, getDynamicSamplingContextFromSpan } from '../tracing';
import { getActiveSpan, getRootSpan, spanToTraceHeader } from './spanUtils';

type TraceData = {
'sentry-trace'?: string;
baggage?: string;
};

/**
* Extracts trace propagation data from the current span or from the client's scope (via transaction or propagation
* context) and serializes it to `sentry-trace` and `baggage` values to strings. These values can be used to propagate
* a trace via our tracing Http headers or Html `<meta>` tags.
*
* This function also applies some validation to the generated sentry-trace and baggage values to ensure that
* only valid strings are returned.
*
* @param span a span to take the trace data from. By default, the currently active span is used.
* @param scope the scope to take trace data from By default, the active current scope is used.
* @param client the SDK's client to take trace data from. By default, the current client is used.
*
* @returns an object with the tracing data values. The object keys are the name of the tracing key to be used as header
* or meta tag name.
*/
export function getTraceData(span?: Span, scope?: Scope, client?: Client): TraceData {
const clientToUse = client || getClient();
const scopeToUse = scope || getCurrentScope();
const spanToUse = span || getActiveSpan();

const { dsc, sampled, traceId } = scopeToUse.getPropagationContext();
const rootSpan = spanToUse && getRootSpan(spanToUse);

const sentryTrace = spanToUse ? spanToTraceHeader(spanToUse) : generateSentryTraceHeader(traceId, undefined, sampled);

const dynamicSamplingContext = rootSpan
? getDynamicSamplingContextFromSpan(rootSpan)
: dsc
? dsc
: clientToUse
? getDynamicSamplingContextFromClient(traceId, clientToUse)
: undefined;

const baggage = dynamicSamplingContextToSentryBaggageHeader(dynamicSamplingContext);

const isValidSentryTraceHeader = TRACEPARENT_REGEXP.test(sentryTrace);
if (!isValidSentryTraceHeader) {
logger.warn('Invalid sentry-trace data. Cannot generate trace data');
return {};
}

const validBaggage = isValidBaggageString(baggage);
if (!validBaggage) {
logger.warn('Invalid baggage data. Not returning "baggage" value');
}

return {
'sentry-trace': sentryTrace,
...(validBaggage && { baggage }),
};
}

/**
* Tests string against baggage spec as defined in:
*
* - W3C Baggage grammar: https://www.w3.org/TR/baggage/#definition
* - RFC7230 token definition: https://datatracker.ietf.org/doc/html/rfc7230#section-3.2.6
*
* exported for testing
*/
export function isValidBaggageString(baggage?: string): boolean {
if (!baggage || !baggage.length) {
return false;
}
const keyRegex = "[-!#$%&'*+.^_`|~A-Za-z0-9]+";
const valueRegex = '[!#-+-./0-9:<=>?@A-Z\\[\\]a-z{-}]+';
const spaces = '\\s*';
// eslint-disable-next-line @sentry-internal/sdk/no-regexp-constructor -- RegExp for readability, no user input
const baggageRegex = new RegExp(
`^${keyRegex}${spaces}=${spaces}${valueRegex}(${spaces},${spaces}${keyRegex}${spaces}=${spaces}${valueRegex})*$`,
);
return baggageRegex.test(baggage);
}
Loading

0 comments on commit 7e1a641

Please sign in to comment.