Skip to content
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
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@

Work in this release was contributed by @nabi-noor, @LuccaRebelloToledo, @andasan, @breken-ai, @EmileBrunelle, and @diobriggs. Thank you for your contributions!

### Important Changes

- **feat(nextjs): Add `cache_origin` span links to `use cache` hit spans ([#24821](https://github.com/getsentry/sentry-javascript/pull/24821))**

A `cache.get` span for a `"use cache"` hit now carries a span link (`sentry.link.type: 'cache_origin'`) to the `cache.put` span of the request that filled the cache entry, connecting the trace that reads a cached value to the trace that produced it.

## 11.1.0

### Important Changes
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,12 @@ import { waitForTransaction } from '@sentry-internal/test-utils';

// Origin links (`sentry.link.type: 'cache_origin'` on `cache.get` hit spans, pointing at the
// filling `cache.put`) for `use cache` in nested layout trees under `app/(cached-nesting)/`.
// Not implemented yet — every test is `test.fail()` with the final expected assertions.

// A `use cache` layout between dynamic segments. The layout entry is keyed by the awaited [id]
// param. If Next serves the entry from the prerendered shell (Resume Data Cache) instead of the
// cache handlers, there is no `cache.get` span at all — then this stays failing until Next
// exposes RDC reads.
// param. These runtime-filled entries are read through the cache handlers here; entries served
// from the prerendered shell (Resume Data Cache) would produce no `cache.get` span at all until
// Next exposes RDC reads.
test('links a cached layout hit to the trace that filled the layout entry', async ({ request }) => {
test.fail();

const id = crypto.randomUUID();

const missTxPromise = waitForTransaction('nextjs-16-cacheComponents', transactionEvent => {
Expand Down Expand Up @@ -56,8 +53,6 @@ test('links a cached layout hit to the trace that filled the layout entry', asyn
test('links a cached leaf component under dynamic layouts to the trace that filled the leaf entry', async ({
request,
}) => {
test.fail();

const id = crypto.randomUUID();

const missTxPromise = waitForTransaction('nextjs-16-cacheComponents', transactionEvent => {
Expand Down Expand Up @@ -101,7 +96,6 @@ test('links a cached leaf component under dynamic layouts to the trace that fill
// spans point at two different origin traces.
test('links two cached levels to different origin traces after the layout expires', async ({ request }) => {
test.skip(process.env.TEST_ENV !== 'production', 'Entries are only discarded at `expire` in production');
test.fail();

const id = crypto.randomUUID();

Expand Down Expand Up @@ -180,8 +174,6 @@ test('links two cached levels to different origin traces after the layout expire
test('links a shared layout hit on a sibling route to the trace of the route that filled the entry', async ({
request,
}) => {
test.fail();

const id = crypto.randomUUID();

const fillTxPromise = waitForTransaction('nextjs-16-cacheComponents', transactionEvent => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,13 @@ import { waitForTransaction } from '@sentry-internal/test-utils';
// Origin links for `use cache` inside rendered pages (cached components and nested cached
// functions). Target behavior: a cache hit records a `cache.get` span carrying a
// `sentry.link.type: 'cache_origin'` span link to the `cache.put` span of the trace that filled
// the entry. Not implemented yet — every test is `test.fail()`.
// the entry.

// Two cached sibling components are two cache entries (props are part of the key), so one request
// carries one `cache.get` hit span per section, each linking to its own fill. The components sit
// in a dynamic hole: entries served from the prerendered shell (Resume Data Cache) never reach
// the cache handlers and produce no spans until Next.js exposes RDC reads.
// carries one `cache.get` hit span per section, each linking to its own fill. These runtime-filled entries are read
// through the cache handlers; entries served from the prerendered shell (Resume Data Cache) would
// produce no spans at all until Next.js exposes RDC reads.
test('links each sibling component hit to the fill of its own entry', async ({ request }) => {
test.fail();

const id = crypto.randomUUID();

const missTxPromise = waitForTransaction('nextjs-16-cacheComponents', transactionEvent => {
Expand Down Expand Up @@ -65,7 +63,6 @@ test('links each sibling component hit to the fill of its own entry', async ({ r

test("links a nested cache hit inside another entry's refill to the original fill trace", async ({ request }) => {
test.skip(process.env.TEST_ENV !== 'production', 'Entries are only discarded at `expire` in production');
test.fail();

const id = crypto.randomUUID();

Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
import { expect, test } from '@playwright/test';
import { waitForTransaction } from '@sentry-internal/test-utils';

// Origin links for `use cache` in route handlers. Target behavior: a cache hit records a
// `cache.get` span carrying a `sentry.link.type: 'cache_origin'` span link to the `cache.put`
// span of the trace that filled the entry; unknown origin means no link. Not implemented yet —
// every test is `test.fail()`; shipping the feature should only require deleting those lines.
// Origin links for `use cache` in route handlers: a cache hit records a `cache.get` span
// carrying a `sentry.link.type: 'cache_origin'` span link to the `cache.put` span of the trace
// that filled the entry; unknown origin means no link.

test('links a route handler cache hit to the trace that filled the entry', async ({ request }) => {
test.fail();

// A fresh id makes the first request a guaranteed cache miss (the id is part of the cache key)
// even when the test is retried against the same server.
const id = crypto.randomUUID();
Expand Down Expand Up @@ -55,7 +52,6 @@ test('links a route handler cache hit to the trace that filled the entry', async

test('moves the origin link to the refill trace after the entry expires', async ({ request }) => {
test.skip(process.env.TEST_ENV !== 'production', 'Entries are only discarded at `expire` in production');
test.fail();

const id = crypto.randomUUID();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,12 @@ import { CACHE_ORIGIN_LINK_ATTRIBUTES, findCacheSpan } from './cacheOriginLinks-

// Origin links (`sentry.link.type: 'cache_origin'` on `cache.get` hit spans, pointing at the
// filling `cache.put`) for `use cache` in nested layout trees under `app/(cached-nesting)/`.
// Not implemented yet — every test is `test.fail()` with the final expected assertions.

// A `use cache` layout between dynamic segments. The layout entry is keyed by the awaited [id]
// param. If Next serves the entry from the prerendered shell (Resume Data Cache) instead of the
// cache handlers, there is no `cache.get` span at all — then this stays failing until Next
// exposes RDC reads.
// param. These runtime-filled entries are read through the cache handlers here; entries served
// from the prerendered shell (Resume Data Cache) would produce no `cache.get` span at all until
// Next exposes RDC reads.
test('links a cached layout hit to the trace that filled the layout entry', async ({ request }) => {
test.fail();

const id = crypto.randomUUID();

const missSpansPromise = collectStreamedSpans('nextjs-16-streaming-cacheComponents', spansOfTrace => {
Expand Down Expand Up @@ -57,8 +54,6 @@ test('links a cached layout hit to the trace that filled the layout entry', asyn
test('links a cached leaf component under dynamic layouts to the trace that filled the leaf entry', async ({
request,
}) => {
test.fail();

const id = crypto.randomUUID();

const missSpansPromise = collectStreamedSpans('nextjs-16-streaming-cacheComponents', spansOfTrace => {
Expand Down Expand Up @@ -102,7 +97,6 @@ test('links a cached leaf component under dynamic layouts to the trace that fill
// spans point at two different origin traces.
test('links two cached levels to different origin traces after the layout expires', async ({ request }) => {
test.skip(process.env.TEST_ENV !== 'production', 'Entries are only discarded at `expire` in production');
test.fail();

const id = crypto.randomUUID();

Expand Down Expand Up @@ -185,8 +179,6 @@ test('links two cached levels to different origin traces after the layout expire
test('links a shared layout hit on a sibling route to the trace of the route that filled the entry', async ({
request,
}) => {
test.fail();

const id = crypto.randomUUID();

const fillSpansPromise = collectStreamedSpans('nextjs-16-streaming-cacheComponents', spansOfTrace => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,13 @@ import { CACHE_ORIGIN_LINK_ATTRIBUTES } from './cacheOriginLinks-utils';
// Origin links for `use cache` inside rendered pages (cached components and nested cached
// functions). Target behavior: a cache hit records a `cache.get` span carrying a
// `sentry.link.type: 'cache_origin'` span link to the `cache.put` span of the trace that filled
// the entry. Not implemented yet — every test is `test.fail()`.
// the entry.

// Two cached sibling components are two cache entries (props are part of the key), so one request
// carries one `cache.get` hit span per section, each linking to its own fill. The components sit
// in a dynamic hole: entries served from the prerendered shell (Resume Data Cache) never reach
// the cache handlers and produce no spans until Next.js exposes RDC reads.
// carries one `cache.get` hit span per section, each linking to its own fill. These runtime-filled entries are read
// through the cache handlers; entries served from the prerendered shell (Resume Data Cache) would
// produce no spans at all until Next.js exposes RDC reads.
test('links each sibling component hit to the fill of its own entry', async ({ request }) => {
test.fail();

const id = crypto.randomUUID();

const missSpansPromise = collectStreamedSpans('nextjs-16-streaming-cacheComponents', spansOfTrace => {
Expand Down Expand Up @@ -70,7 +68,6 @@ test('links each sibling component hit to the fill of its own entry', async ({ r

test("links a nested cache hit inside another entry's refill to the original fill trace", async ({ request }) => {
test.skip(process.env.TEST_ENV !== 'production', 'Entries are only discarded at `expire` in production');
test.fail();

const id = crypto.randomUUID();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,11 @@ import { expect, test } from '@playwright/test';
import { collectStreamedSpans, getSpanOp } from '@sentry-internal/test-utils';
import { CACHE_ORIGIN_LINK_ATTRIBUTES, findCacheSpan } from './cacheOriginLinks-utils';

// Origin links for `use cache` in route handlers. Target behavior: a cache hit records a
// `cache.get` span carrying a `sentry.link.type: 'cache_origin'` span link to the `cache.put`
// span of the trace that filled the entry; unknown origin means no link. Not implemented yet —
// every test is `test.fail()`; shipping the feature should only require deleting those lines.
// Origin links for `use cache` in route handlers: a cache hit records a `cache.get` span
// carrying a `sentry.link.type: 'cache_origin'` span link to the `cache.put` span of the trace
// that filled the entry; unknown origin means no link.

test('links a route handler cache hit to the trace that filled the entry', async ({ request }) => {
test.fail();

// A fresh id makes the first request a guaranteed cache miss (the id is part of the cache key)
// even when the test is retried against the same server.
const id = crypto.randomUUID();
Expand Down Expand Up @@ -56,7 +53,6 @@ test('links a route handler cache hit to the trace that filled the entry', async

test('moves the origin link to the refill trace after the entry expires', async ({ request }) => {
test.skip(process.env.TEST_ENV !== 'production', 'Entries are only discarded at `expire` in production');
test.fail();

const id = crypto.randomUUID();

Expand Down
Loading
Loading