From 05684d45222782c41a0ffb6da2af16708ff65d32 Mon Sep 17 00:00:00 2001 From: Lukas Stracke Date: Tue, 6 Aug 2024 14:37:58 +0200 Subject: [PATCH] feat(core): Add `getTraceMetaTags` function (#13201) Export function `getTraceMetaTags` that gives users an easy way to get stringified Html meta tags for server->client trace propagation. --------- Co-authored-by: Andrei <168741329+andreiborza@users.noreply.github.com> --- .../suites/tracing/meta-tags/server.js | 33 +++++++++++++++++++ .../suites/tracing/meta-tags/test.ts | 26 +++++++++++++++ packages/astro/src/index.server.ts | 1 + packages/astro/src/server/middleware.ts | 12 +++---- packages/astro/test/server/middleware.test.ts | 10 +++--- packages/aws-serverless/src/index.ts | 1 + packages/bun/src/index.ts | 1 + packages/cloudflare/src/index.ts | 1 + packages/core/src/index.ts | 1 + packages/core/src/utils/meta.ts | 29 ++++++++++++++++ packages/core/test/lib/utils/meta.test.ts | 30 +++++++++++++++++ packages/deno/src/index.ts | 1 + packages/google-cloud-serverless/src/index.ts | 1 + packages/node/src/index.ts | 1 + packages/sveltekit/src/server/index.ts | 1 + packages/vercel-edge/src/index.ts | 1 + 16 files changed, 138 insertions(+), 12 deletions(-) create mode 100644 dev-packages/node-integration-tests/suites/tracing/meta-tags/server.js create mode 100644 dev-packages/node-integration-tests/suites/tracing/meta-tags/test.ts create mode 100644 packages/core/src/utils/meta.ts create mode 100644 packages/core/test/lib/utils/meta.test.ts diff --git a/dev-packages/node-integration-tests/suites/tracing/meta-tags/server.js b/dev-packages/node-integration-tests/suites/tracing/meta-tags/server.js new file mode 100644 index 000000000000..35e204a05d6b --- /dev/null +++ b/dev-packages/node-integration-tests/suites/tracing/meta-tags/server.js @@ -0,0 +1,33 @@ +const { loggingTransport } = require('@sentry-internal/node-integration-tests'); +const Sentry = require('@sentry/node'); + +Sentry.init({ + dsn: 'https://public@dsn.ingest.sentry.io/1337', + tracesSampleRate: 1.0, + transport: loggingTransport, +}); + +// express must be required after Sentry is initialized +const express = require('express'); +const { startExpressServerAndSendPortToRunner } = require('@sentry-internal/node-integration-tests'); + +const app = express(); + +app.get('/test', (_req, res) => { + res.send({ + response: ` + + + ${Sentry.getTraceMetaTags()} + + + Hi :) + + + `, + }); +}); + +Sentry.setupExpressErrorHandler(app); + +startExpressServerAndSendPortToRunner(app); diff --git a/dev-packages/node-integration-tests/suites/tracing/meta-tags/test.ts b/dev-packages/node-integration-tests/suites/tracing/meta-tags/test.ts new file mode 100644 index 000000000000..22bef9d2b1b9 --- /dev/null +++ b/dev-packages/node-integration-tests/suites/tracing/meta-tags/test.ts @@ -0,0 +1,26 @@ +import { cleanupChildProcesses, createRunner } from '../../../utils/runner'; + +describe('getTraceMetaTags', () => { + afterAll(() => { + cleanupChildProcesses(); + }); + + test('injects sentry tracing tags', async () => { + const traceId = 'cd7ee7a6fe3ebe7ab9c3271559bc203c'; + const parentSpanId = '100ff0980e7a4ead'; + + const runner = createRunner(__dirname, 'server.js').start(); + + const response = await runner.makeRequest('get', '/test', { + 'sentry-trace': `${traceId}-${parentSpanId}-1`, + baggage: 'sentry-environment=production', + }); + + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + const html = response?.response as unknown as string; + + expect(html).toMatch(//); + expect(html).toContain(''); + }); +}); diff --git a/packages/astro/src/index.server.ts b/packages/astro/src/index.server.ts index 1084643584d6..7f52ad0dc0bb 100644 --- a/packages/astro/src/index.server.ts +++ b/packages/astro/src/index.server.ts @@ -56,6 +56,7 @@ export { getSpanDescendants, getSpanStatusFromHttpCode, getTraceData, + getTraceMetaTags, graphqlIntegration, hapiIntegration, httpIntegration, diff --git a/packages/astro/src/server/middleware.ts b/packages/astro/src/server/middleware.ts index 6b668f462489..4b2f15eb3be4 100644 --- a/packages/astro/src/server/middleware.ts +++ b/packages/astro/src/server/middleware.ts @@ -6,6 +6,7 @@ import { getActiveSpan, getClient, getCurrentScope, + getTraceMetaTags, setHttpStatus, startSpan, withIsolationScope, @@ -14,8 +15,6 @@ import type { Client, Scope, Span, SpanAttributes } from '@sentry/types'; import { addNonEnumerableProperty, objectify, stripUrlQueryAndFragment } from '@sentry/utils'; import type { APIContext, MiddlewareResponseHandler } from 'astro'; -import { getTraceData } from '@sentry/node'; - type MiddlewareOptions = { /** * If true, the client IP will be attached to the event by calling `setUser`. @@ -189,16 +188,13 @@ function addMetaTagToHead(htmlChunk: string, scope: Scope, client: Client, span? if (typeof htmlChunk !== 'string') { return htmlChunk; } - const { 'sentry-trace': sentryTrace, baggage } = getTraceData(span, scope, client); + const metaTags = getTraceMetaTags(span, scope, client); - if (!sentryTrace) { + if (!metaTags) { return htmlChunk; } - const sentryTraceMeta = ``; - const baggageMeta = baggage && ``; - - const content = `\n${sentryTraceMeta}`.concat(baggageMeta ? `\n${baggageMeta}` : '', '\n'); + const content = `${metaTags}`; return htmlChunk.replace('', content); } diff --git a/packages/astro/test/server/middleware.test.ts b/packages/astro/test/server/middleware.test.ts index 58405c8d1c12..bf96f6ef9046 100644 --- a/packages/astro/test/server/middleware.test.ts +++ b/packages/astro/test/server/middleware.test.ts @@ -34,10 +34,12 @@ describe('sentryMiddleware', () => { }); 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(SentryNode, 'getTraceMetaTags').mockImplementation( + () => ` + + + `, + ); vi.spyOn(SentryCore, 'getDynamicSamplingContextFromSpan').mockImplementation(() => ({ transaction: 'test', })); diff --git a/packages/aws-serverless/src/index.ts b/packages/aws-serverless/src/index.ts index 95b2d553f2d4..20ef9eeaf09f 100644 --- a/packages/aws-serverless/src/index.ts +++ b/packages/aws-serverless/src/index.ts @@ -21,6 +21,7 @@ export { getGlobalScope, getIsolationScope, getTraceData, + getTraceMetaTags, setCurrentClient, Scope, SDK_VERSION, diff --git a/packages/bun/src/index.ts b/packages/bun/src/index.ts index 287dbc26eeee..4de55fd1c5f7 100644 --- a/packages/bun/src/index.ts +++ b/packages/bun/src/index.ts @@ -41,6 +41,7 @@ export { getGlobalScope, getIsolationScope, getTraceData, + getTraceMetaTags, setCurrentClient, Scope, SDK_VERSION, diff --git a/packages/cloudflare/src/index.ts b/packages/cloudflare/src/index.ts index 2f77f96f4e33..0c02fb8ca810 100644 --- a/packages/cloudflare/src/index.ts +++ b/packages/cloudflare/src/index.ts @@ -56,6 +56,7 @@ export { getActiveSpan, getRootSpan, getTraceData, + getTraceMetaTags, startSpan, startInactiveSpan, startSpanManual, diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 5c21c8e484ed..73295f7df64c 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -83,6 +83,7 @@ export { export { parseSampleRate } from './utils/parseSampleRate'; export { applySdkMetadata } from './utils/sdkMetadata'; export { getTraceData } from './utils/traceData'; +export { getTraceMetaTags } from './utils/meta'; export { DEFAULT_ENVIRONMENT } from './constants'; export { addBreadcrumb } from './breadcrumbs'; export { functionToStringIntegration } from './integrations/functiontostring'; diff --git a/packages/core/src/utils/meta.ts b/packages/core/src/utils/meta.ts new file mode 100644 index 000000000000..339dfcee2f28 --- /dev/null +++ b/packages/core/src/utils/meta.ts @@ -0,0 +1,29 @@ +import type { Client, Scope, Span } from '@sentry/types'; +import { getTraceData } from './traceData'; + +/** + * Returns a string of meta tags that represent the current trace data. + * + * You can use this to propagate a trace from your server-side rendered Html to the browser. + * This function returns up to two meta tags, `sentry-trace` and `baggage`, depending on the + * current trace data state. + * + * @example + * Usage example: + * + * ```js + * function renderHtml() { + * return ` + * + * ${getTraceMetaTags()} + * + * `; + * } + * ``` + * + */ +export function getTraceMetaTags(span?: Span, scope?: Scope, client?: Client): string { + return Object.entries(getTraceData(span, scope, client)) + .map(([key, value]) => ``) + .join('\n'); +} diff --git a/packages/core/test/lib/utils/meta.test.ts b/packages/core/test/lib/utils/meta.test.ts new file mode 100644 index 000000000000..3d78247b8951 --- /dev/null +++ b/packages/core/test/lib/utils/meta.test.ts @@ -0,0 +1,30 @@ +import { getTraceMetaTags } from '../../../src/utils/meta'; +import * as TraceDataModule from '../../../src/utils/traceData'; + +describe('getTraceMetaTags', () => { + it('renders baggage and sentry-trace values to stringified Html meta tags', () => { + jest.spyOn(TraceDataModule, 'getTraceData').mockReturnValueOnce({ + 'sentry-trace': '12345678901234567890123456789012-1234567890123456-1', + baggage: 'sentry-environment=production', + }); + + expect(getTraceMetaTags()).toBe(` +`); + }); + + it('renders just sentry-trace values to stringified Html meta tags', () => { + jest.spyOn(TraceDataModule, 'getTraceData').mockReturnValueOnce({ + 'sentry-trace': '12345678901234567890123456789012-1234567890123456-1', + }); + + expect(getTraceMetaTags()).toBe( + '', + ); + }); + + it('returns an empty string if neither sentry-trace nor baggage values are available', () => { + jest.spyOn(TraceDataModule, 'getTraceData').mockReturnValueOnce({}); + + expect(getTraceMetaTags()).toBe(''); + }); +}); diff --git a/packages/deno/src/index.ts b/packages/deno/src/index.ts index 69b26bb1729a..1f983b476b74 100644 --- a/packages/deno/src/index.ts +++ b/packages/deno/src/index.ts @@ -56,6 +56,7 @@ export { getActiveSpan, getRootSpan, getTraceData, + getTraceMetaTags, startSpan, startInactiveSpan, startSpanManual, diff --git a/packages/google-cloud-serverless/src/index.ts b/packages/google-cloud-serverless/src/index.ts index 351f843d2c2d..73f24f9cf39e 100644 --- a/packages/google-cloud-serverless/src/index.ts +++ b/packages/google-cloud-serverless/src/index.ts @@ -21,6 +21,7 @@ export { getGlobalScope, getIsolationScope, getTraceData, + getTraceMetaTags, setCurrentClient, Scope, SDK_VERSION, diff --git a/packages/node/src/index.ts b/packages/node/src/index.ts index badd1f1a27bf..1fdc32d3d77a 100644 --- a/packages/node/src/index.ts +++ b/packages/node/src/index.ts @@ -96,6 +96,7 @@ export { getCurrentScope, getIsolationScope, getTraceData, + getTraceMetaTags, withScope, withIsolationScope, captureException, diff --git a/packages/sveltekit/src/server/index.ts b/packages/sveltekit/src/server/index.ts index a74e5bb89dc0..99813d01ceba 100644 --- a/packages/sveltekit/src/server/index.ts +++ b/packages/sveltekit/src/server/index.ts @@ -52,6 +52,7 @@ export { getSpanDescendants, getSpanStatusFromHttpCode, getTraceData, + getTraceMetaTags, graphqlIntegration, hapiIntegration, httpIntegration, diff --git a/packages/vercel-edge/src/index.ts b/packages/vercel-edge/src/index.ts index a96fc15e35d2..8be93345fa3d 100644 --- a/packages/vercel-edge/src/index.ts +++ b/packages/vercel-edge/src/index.ts @@ -56,6 +56,7 @@ export { getActiveSpan, getRootSpan, getTraceData, + getTraceMetaTags, startSpan, startInactiveSpan, startSpanManual,