| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent 06a123b commit 6b1501e
7 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -617,20 +617,23 @@ Affected SDKs: All SDKs running in the browser. | |||
| 617 | 617 | ||
| 618 | 618 | With [span streaming](#span-streaming-is-now-the-default) enabled(the default), span names are now **low cardinality**, following the [Sentry span name conventions](https://getsentry.github.io/sentry-conventions/names/). | |
| 619 | 619 | ||
| 620 | - In v11, this only affects `pageload` spans. Further ops will follow in future releases. | ||
| 620 | + In v11, this only affects `pageload` and `resource.*` spans. Further ops will follow in future releases. | ||
| 621 | 621 | If you [opt out of span streaming](#opting-out-of-span-streaming), span names remain unchanged. | |
| 622 | 622 | ||
| 623 | 623 | The following span names were adjusted: | |
| 624 | 624 | ||
| 625 | - | Span op | Before | After | | ||
| 626 | - | ---------- | ------------------------------------------------------------------------------------------- | ---------------------------------------------------------- | | ||
| 627 | - | `pageload` | The parameterized route, or the raw URL path if the SDK couldn't resolve one (`/users/123`) | The parameterized route, or `Pageload` if the SDK has none | | ||
| 625 | + | Span op | Before | After | | ||
| 626 | + | ------------ | ------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------- | | ||
| 627 | + | `pageload` | The parameterized route, or the raw URL path if the SDK couldn't resolve one (`/users/123`) | The parameterized route, or `Pageload` if the SDK has none | | ||
| 628 | + | `resource.*` | The resource URL, relative to the page origin for same-origin resources (`/assets/app.js`) | The resource domain (`cdn.example.com`), or `Resource` if the SDK has none | | ||
| 629 | + | ||
| 630 | + Resource spans now also carry a `url.domain` attribute holding that domain. The full URL remains available on `url.full`. | ||
| 628 | 631 | ||
| 629 | 632 | Some consequences to be aware of: | |
| 630 | 633 | ||
| 631 | 634 | Child spans of a pageload span carry its name in their `sentry.segment.name` attribute, so that changes with it. If you group or filter spans by segment name in dashboards or alerts, update those references. | |
| 632 | 635 | ||
| 633 | - `ignoreSpans` is evaluated when a span **starts**, at which point a pageload span without a resolved route is already named `'Pageload'`, so filters matching a URL path no longer apply to it. Match on attributes instead: | ||
| 636 | + `ignoreSpans` is evaluated when a span **starts**, at which point a pageload span without a resolved route is already named `'Pageload'` and a resource span is already named after its domain, so filters matching a URL path no longer apply to them. Match on attributes instead: | ||
| 634 | 637 | ||
| 635 | 638 | ```js | |
| 636 | 639 | Sentry.init({ | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,10 @@ | |||
| 1 | + import * as Sentry from '@sentry/browser'; | ||
| 2 | + | ||
| 3 | + window.Sentry = Sentry; | ||
| 4 | + | ||
| 5 | + Sentry.init({ | ||
| 6 | + dsn: 'https://public@dsn.ingest.sentry.io/1337', | ||
| 7 | + integrations: [Sentry.browserTracingIntegration(), Sentry.spanStreamingIntegration()], | ||
| 8 | + traceLifecycle: 'stream', | ||
| 9 | + tracesSampleRate: 1, | ||
| 10 | + }); | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,12 @@ | |||
| 1 | + <!doctype html> | ||
| 2 | + <html> | ||
| 3 | + <head> | ||
| 4 | + <meta charset="utf-8" /> | ||
| 5 | + </head> | ||
| 6 | + <body> | ||
| 7 | + <img src="https://sentry-test-site.example/path/to/image.svg" /> | ||
| 8 | + <script src="https://sentry-test-site.example/path/to/script.js"></script> | ||
| 9 | + <link href="https://sentry-test-site.example/path/to/style.css" type="text/css" rel="stylesheet" /> | ||
| 10 | + <span>Rendered</span> | ||
| 11 | + </body> | ||
| 12 | + </html> | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,68 @@ | |||
| 1 | + import type { Route } from '@playwright/test'; | ||
| 2 | + import { expect } from '@playwright/test'; | ||
| 3 | + import { sentryTest } from '../../../../utils/fixtures'; | ||
| 4 | + import { shouldSkipTracingTest } from '../../../../utils/helpers'; | ||
| 5 | + import { getSpanOp, getSpansFromEnvelope, waitForStreamedSpanEnvelope } from '../../../../utils/spanUtils'; | ||
| 6 | + | ||
| 7 | + const assetsDir = `${__dirname}/../pageload-resource-spans/assets`; | ||
| 8 | + | ||
| 9 | + sentryTest('names streamed resource spans after the resource domain', async ({ getLocalTestUrl, page }) => { | ||
| 10 | + sentryTest.skip(shouldSkipTracingTest()); | ||
| 11 | + | ||
| 12 | + // Intercepting asset requests to avoid network-related flakiness and random retries (on Firefox). | ||
| 13 | + await page.route('https://sentry-test-site.example/path/to/image.svg', (route: Route) => | ||
| 14 | + route.fulfill({ | ||
| 15 | + path: `${assetsDir}/image.svg`, | ||
| 16 | + headers: { | ||
| 17 | + 'Timing-Allow-Origin': '*', | ||
| 18 | + 'Content-Type': 'image/svg+xml', | ||
| 19 | + }, | ||
| 20 | + }), | ||
| 21 | + ); | ||
| 22 | + await page.route('https://sentry-test-site.example/path/to/script.js', (route: Route) => | ||
| 23 | + route.fulfill({ | ||
| 24 | + path: `${assetsDir}/script.js`, | ||
| 25 | + headers: { | ||
| 26 | + 'Timing-Allow-Origin': '*', | ||
| 27 | + 'Content-Type': 'application/javascript', | ||
| 28 | + }, | ||
| 29 | + }), | ||
| 30 | + ); | ||
| 31 | + await page.route('https://sentry-test-site.example/path/to/style.css', (route: Route) => | ||
| 32 | + route.fulfill({ | ||
| 33 | + path: `${assetsDir}/style.css`, | ||
| 34 | + headers: { | ||
| 35 | + 'Timing-Allow-Origin': '*', | ||
| 36 | + 'Content-Type': 'text/css', | ||
| 37 | + }, | ||
| 38 | + }), | ||
| 39 | + ); | ||
| 40 | + | ||
| 41 | + const spanEnvelopePromise = waitForStreamedSpanEnvelope( | ||
| 42 | + page, | ||
| 43 | + env => !!getSpansFromEnvelope(env).find(s => getSpanOp(s) === 'resource.img'), | ||
| 44 | + ); | ||
| 45 | + | ||
| 46 | + const url = await getLocalTestUrl({ testDir: __dirname }); | ||
| 47 | + await page.goto(url); | ||
| 48 | + | ||
| 49 | + const spans = getSpansFromEnvelope(await spanEnvelopePromise); | ||
| 50 | + | ||
| 51 | + const imgSpan = spans.find(s => getSpanOp(s) === 'resource.img'); | ||
| 52 | + const linkSpan = spans.find(s => getSpanOp(s) === 'resource.link'); | ||
| 53 | + | ||
| 54 | + expect(imgSpan?.name).toBe('sentry-test-site.example'); | ||
| 55 | + expect(imgSpan?.attributes['url.domain']).toEqual({ type: 'string', value: 'sentry-test-site.example' }); | ||
| 56 | + expect(imgSpan?.attributes['url.full']).toEqual({ | ||
| 57 | + type: 'string', | ||
| 58 | + value: 'https://sentry-test-site.example/path/to/image.svg', | ||
| 59 | + }); | ||
| 60 | + | ||
| 61 | + expect(linkSpan?.name).toBe('sentry-test-site.example'); | ||
| 62 | + | ||
| 63 | + // Same-origin resources used to be named by their origin-relative path, they now carry the test host. | ||
| 64 | + const sameOriginScriptSpan = spans.find( | ||
| 65 | + s => getSpanOp(s) === 'resource.script' && s.name !== 'sentry-test-site.example', | ||
| 66 | + ); | ||
| 67 | + expect(sameOriginScriptSpan?.name).toBe('sentry-test.io'); | ||
| 68 | + }); | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -98,6 +98,7 @@ sentryTest('adds resource spans to pageload transaction', async ({ getLocalTestU | |||
| 98 | 98 | [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'resource.img', | |
| 99 | 99 | [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.resource.browser.metrics', | |
| 100 | 100 | 'server.address': 'sentry-test-site.example', | |
| 101 | + 'url.domain': 'sentry-test-site.example', | ||
| 101 | 102 | 'url.same_origin': false, | |
| 102 | 103 | 'url.scheme': 'https', | |
| 103 | 104 | 'url.full': 'https://sentry-test-site.example/path/to/image.svg', | |
@@ -147,6 +148,7 @@ sentryTest('adds resource spans to pageload transaction', async ({ getLocalTestU | |||
| 147 | 148 | [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'resource.link', | |
| 148 | 149 | [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.resource.browser.metrics', | |
| 149 | 150 | 'server.address': 'sentry-test-site.example', | |
| 151 | + 'url.domain': 'sentry-test-site.example', | ||
| 150 | 152 | 'url.same_origin': false, | |
| 151 | 153 | 'url.scheme': 'https', | |
| 152 | 154 | 'url.full': 'https://sentry-test-site.example/path/to/style.css', | |
@@ -190,6 +192,7 @@ sentryTest('adds resource spans to pageload transaction', async ({ getLocalTestU | |||
| 190 | 192 | 'sentry.op': 'resource.script', | |
| 191 | 193 | 'sentry.origin': 'auto.resource.browser.metrics', | |
| 192 | 194 | 'server.address': 'sentry-test-site.example', | |
| 195 | + 'url.domain': 'sentry-test-site.example', | ||
| 193 | 196 | 'url.same_origin': false, | |
| 194 | 197 | 'url.scheme': 'https', | |
| 195 | 198 | 'url.full': 'https://sentry-test-site.example/path/to/script.js', | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -4,12 +4,13 @@ import { | |||
| 4 | 4 | browserPerformanceTimeOrigin, | |
| 5 | 5 | getActiveSpan, | |
| 6 | 6 | parseUrl, | |
| 7 | + RESOURCE_SPAN_NAME_FALLBACK, | ||
| 7 | 8 | SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, | |
| 8 | 9 | setMeasurement, | |
| 9 | 10 | spanToJSON, | |
| 10 | 11 | filterCollectedUrl, | |
| 11 | 12 | } from '@sentry/core'; | |
| 12 | - import { CODE_FILE_PATH, CODE_FUNCTION_NAME, SENTRY_OP, URL_FULL } from '@sentry/conventions/attributes'; | ||
| 13 | + import { CODE_FILE_PATH, CODE_FUNCTION_NAME, SENTRY_OP, URL_DOMAIN, URL_FULL } from '@sentry/conventions/attributes'; | ||
| 13 | 14 | import { BROWSER_BROWSER_PAINT_SPAN_OP } from '@sentry/conventions/op'; | |
| 14 | 15 | import { | |
| 15 | 16 | addPerformanceInstrumentationHandler, | |
@@ -225,6 +226,7 @@ export function addPerformanceEntries(span: Span, options: AddPerformanceEntries | |||
| 225 | 226 | duration, | |
| 226 | 227 | timeOrigin, | |
| 227 | 228 | ignoreResourceSpans, | |
| 229 | + spanStreamingEnabled, | ||
| 228 | 230 | ); | |
| 229 | 231 | break; | |
| 230 | 232 | } | |
@@ -366,6 +368,7 @@ export function _addResourceSpans( | |||
| 366 | 368 | duration: number, | |
| 367 | 369 | timeOrigin: number, | |
| 368 | 370 | ignoredResourceSpanOps?: Array<string>, | |
| 371 | + spanStreamingEnabled?: boolean, | ||
| 369 | 372 | ): void { | |
| 370 | 373 | // we already instrument based on fetch and xhr, so we don't need to | |
| 371 | 374 | // duplicate spans here. | |
@@ -392,6 +395,13 @@ export function _addResourceSpans( | |||
| 392 | 395 | attributes['server.address'] = parsedUrl.host; | |
| 393 | 396 | } | |
| 394 | 397 | ||
| 398 | + // `host` carries the port, which `url.domain` doesn't. | ||
| 399 | + const domain = parsedUrl.host?.replace(/:\d+$/, ''); | ||
| 400 | + | ||
| 401 | + if (domain) { | ||
| 402 | + attributes[URL_DOMAIN] = domain; | ||
| 403 | + } | ||
| 404 | + | ||
| 395 | 405 | attributes['url.same_origin'] = resourceUrl.includes(WINDOW.location.origin); | |
| 396 | 406 | ||
| 397 | 407 | attributes[URL_FULL] = filterCollectedUrl(resourceUrl); | |
@@ -417,7 +427,10 @@ export function _addResourceSpans( | |||
| 417 | 427 | const endTimestamp = startTimestamp + duration; | |
| 418 | 428 | ||
| 419 | 429 | startAndEndSpan(span, startTimestamp, endTimestamp, { | |
| 420 | - name: resourceUrl.replace(WINDOW.location.origin, ''), | ||
| 430 | + // With span streaming, span names have to be low cardinality, so we can't fall back to the URL. | ||
| 431 | + name: spanStreamingEnabled | ||
| 432 | + ? domain || RESOURCE_SPAN_NAME_FALLBACK | ||
| 433 | + : resourceUrl.replace(WINDOW.location.origin, ''), | ||
| 421 | 434 | op, | |
| 422 | 435 | attributes: attributesWithResourceTiming, | |
| 423 | 436 | }); | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -249,6 +249,7 @@ describe('_addResourceSpans', () => { | |||
| 249 | 249 | ['resource.render_blocking_status']: entry.renderBlockingStatus, | |
| 250 | 250 | ['url.scheme']: 'https', | |
| 251 | 251 | ['server.address']: 'example.com', | |
| 252 | + ['url.domain']: 'example.com', | ||
| 252 | 253 | ['url.same_origin']: true, | |
| 253 | 254 | ['url.full']: resourceEntryName, | |
| 254 | 255 | ['network.protocol.name']: 'http', | |
@@ -431,6 +432,7 @@ describe('_addResourceSpans', () => { | |||
| 431 | 432 | ['resource.render_blocking_status']: entry.renderBlockingStatus, | |
| 432 | 433 | ['url.scheme']: 'https', | |
| 433 | 434 | ['server.address']: 'example.com', | |
| 435 | + ['url.domain']: 'example.com', | ||
| 434 | 436 | ['url.same_origin']: true, | |
| 435 | 437 | ['url.full']: resourceEntryName, | |
| 436 | 438 | ['network.protocol.name']: 'http', | |
@@ -464,6 +466,7 @@ describe('_addResourceSpans', () => { | |||
| 464 | 466 | [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'resource.css', | |
| 465 | 467 | [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.resource.browser.metrics', | |
| 466 | 468 | 'server.address': 'example.com', | |
| 469 | + 'url.domain': 'example.com', | ||
| 467 | 470 | 'url.same_origin': true, | |
| 468 | 471 | 'url.scheme': 'https', | |
| 469 | 472 | 'url.full': resourceEntryName, | |
@@ -515,6 +518,7 @@ describe('_addResourceSpans', () => { | |||
| 515 | 518 | [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'resource.css', | |
| 516 | 519 | [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.resource.browser.metrics', | |
| 517 | 520 | 'server.address': 'example.com', | |
| 521 | + 'url.domain': 'example.com', | ||
| 518 | 522 | 'url.same_origin': true, | |
| 519 | 523 | 'url.scheme': 'https', | |
| 520 | 524 | 'url.full': resourceEntryName, | |
@@ -569,6 +573,48 @@ describe('_addResourceSpans', () => { | |||
| 569 | 573 | }); | |
| 570 | 574 | }, | |
| 571 | 575 | ); | |
| 576 | + | ||
| 577 | + describe('with span streaming enabled', () => { | ||
| 578 | + it.each([ | ||
| 579 | + ['https://example.com/assets/to/css', 'example.com'], | ||
| 580 | + ['https://cdn.example.org:8443/static/logo.png', 'cdn.example.org'], | ||
| 581 | + ])('names the span after the resource domain (%s)', (url, expectedName) => { | ||
| 582 | + const spans: Span[] = []; | ||
| 583 | + | ||
| 584 | + getClient()?.on('spanEnd', span => { | ||
| 585 | + spans.push(span); | ||
| 586 | + }); | ||
| 587 | + | ||
| 588 | + const entry = mockPerformanceResourceTiming({ initiatorType: 'css', nextHopProtocol: 'h2' }); | ||
| 589 | + | ||
| 590 | + _addResourceSpans(span, entry, url, 100, 23, 345, undefined, true); | ||
| 591 | + | ||
| 592 | + expect(spans).toHaveLength(1); | ||
| 593 | + expect(spanToJSON(spans[0]!)).toEqual( | ||
| 594 | + expect.objectContaining({ | ||
| 595 | + name: expectedName, | ||
| 596 | + attributes: expect.objectContaining({ 'url.domain': expectedName }), | ||
| 597 | + }), | ||
| 598 | + ); | ||
| 599 | + }); | ||
| 600 | + | ||
| 601 | + it('falls back to a static name when the resource URL has no domain', () => { | ||
| 602 | + const spans: Span[] = []; | ||
| 603 | + | ||
| 604 | + getClient()?.on('spanEnd', span => { | ||
| 605 | + spans.push(span); | ||
| 606 | + }); | ||
| 607 | + | ||
| 608 | + const entry = mockPerformanceResourceTiming({ initiatorType: 'script', nextHopProtocol: 'h2' }); | ||
| 609 | + | ||
| 610 | + _addResourceSpans(span, entry, 'blob:0f6b3f0a-1e2d-4d1a-9c3f-2a5c1d7b8e90', 100, 23, 345, undefined, true); | ||
| 611 | + | ||
| 612 | + expect(spans).toHaveLength(1); | ||
| 613 | + const spanJson = spanToJSON(spans[0]!); | ||
| 614 | + expect(spanJson.name).toBe('Resource'); | ||
| 615 | + expect(spanJson.attributes['url.domain']).toBeUndefined(); | ||
| 616 | + }); | ||
| 617 | + }); | ||
| 572 | 618 | }); | |
| 573 | 619 | ||
| 574 | 620 | describe('_addNavigationSpans', () => { | |
| Back | FazBrowse Home | New Git URL |
0 commit comments