Skip to content

Commit 97c2439

Browse files
committed
Merge branch 'develop' into nh/langchain-embeddings
# Conflicts: # CHANGELOG.md
2 parents 03872bb + d0bbc1a commit 97c2439

186 files changed

Lines changed: 6377 additions & 2038 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/validate-pr.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ jobs:
1010
permissions:
1111
pull-requests: write
1212
steps:
13-
- uses: getsentry/github-workflows/validate-pr@33c378e8d3aa1515164b62c16c210784cee35638
13+
- uses: getsentry/github-workflows/validate-pr@71588ddf95134f804e82c5970a8098588e2eaecd
1414
with:
1515
app-id: ${{ vars.SDK_MAINTAINER_BOT_APP_ID }}
1616
private-key: ${{ secrets.SDK_MAINTAINER_BOT_PRIVATE_KEY }}

.version.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
{
22
"_comment": "Auto-generated by scripts/bump-version.js. Used by the gitflow sync workflow to detect version bumps. Do not edit manually.",
3-
"version": "10.46.0"
3+
"version": "10.47.0"
44
}

CHANGELOG.md

Lines changed: 85 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,38 @@
22

33
## Unreleased
44

5+
- "You miss 100 percent of the chances you don't take. — Wayne Gretzky" — Michael Scott
6+
7+
- **ref(core): Remove provider-specific AI span attributes in favor of `gen_ai` attributes in sentry conventions ([#20011](https://github.com/getsentry/sentry-javascript/pull/20011))**
8+
9+
The following provider-specific span attributes have been removed from the OpenAI and Anthropic AI integrations. Use the standardized `gen_ai.*` equivalents instead:
10+
11+
| Removed attribute | Replacement |
12+
| -------------------------------- | ---------------------------- |
13+
| `openai.response.id` | `gen_ai.response.id` |
14+
| `openai.response.model` | `gen_ai.response.model` |
15+
| `openai.usage.prompt_tokens` | `gen_ai.usage.input_tokens` |
16+
| `openai.usage.completion_tokens` | `gen_ai.usage.output_tokens` |
17+
| `openai.response.timestamp` | _(removed, no replacement)_ |
18+
| `anthropic.response.timestamp` | _(removed, no replacement)_ |
19+
20+
If you reference these attributes in hooks (e.g. `beforeSendTransaction`), update them to the `gen_ai.*` equivalents.
21+
22+
- feat(langchain): Support embeddings APIs in LangChain ([#20017](https://github.com/getsentry/sentry-javascript/pull/20017))
23+
24+
Adds instrumentation for LangChain embeddings (`embedQuery`, `embedDocuments`), creating `gen_ai.embeddings` spans. In Node.js, embedding classes from `@langchain/openai`, `@langchain/google-genai`, `@langchain/mistralai`, and `@langchain/google-vertexai` are auto-instrumented. For other runtimes, use the new `instrumentLangChainEmbeddings` API:
25+
26+
```javascript
27+
import * as Sentry from '@sentry/cloudflare';
28+
import { OpenAIEmbeddings } from '@langchain/openai';
29+
30+
const embeddings = Sentry.instrumentLangChainEmbeddings(new OpenAIEmbeddings({ model: 'text-embedding-3-small' }));
31+
32+
await embeddings.embedQuery('Hello world');
33+
```
34+
35+
## 10.47.0
36+
537
### Important Changes
638

739
- **feat(node-core): Add OTLP integration for node-core/light ([#19729](https://github.com/getsentry/sentry-javascript/pull/19729))**
@@ -53,24 +85,68 @@
5385
});
5486
```
5587

56-
- "You miss 100 percent of the chances you don't take. — Wayne Gretzky" — Michael Scott
57-
- feat(core): Support embedding APIs in google-genai ([#19797](https://github.com/getsentry/sentry-javascript/pull/19797))
88+
- **feat(core): Support embedding APIs in google-genai ([#19797](https://github.com/getsentry/sentry-javascript/pull/19797))**
5889

5990
Adds instrumentation for the Google GenAI [`embedContent`](https://ai.google.dev/gemini-api/docs/embeddings) API, creating `gen_ai.embeddings` spans.
6091

61-
- feat(langchain): Support embeddings APIs in LangChain ([#20017](https://github.com/getsentry/sentry-javascript/pull/20017))
92+
- **feat(browser): Add `elementTimingIntegration` for tracking element render and load times ([#19869](https://github.com/getsentry/sentry-javascript/pull/19869))**
6293

63-
Adds instrumentation for LangChain embeddings (`embedQuery`, `embedDocuments`), creating `gen_ai.embeddings` spans. In Node.js, embedding classes from `@langchain/openai`, `@langchain/google-genai`, `@langchain/mistralai`, and `@langchain/google-vertexai` are auto-instrumented. For other runtimes, use the new `instrumentLangChainEmbeddings` API:
94+
The new `elementTimingIntegration` captures Element Timing API data as Sentry metrics. It emits `element_timing.render_time` and `element_timing.load_time` distribution metrics for elements annotated with the `elementtiming` HTML attribute.
6495

65-
```javascript
66-
import * as Sentry from '@sentry/cloudflare';
67-
import { OpenAIEmbeddings } from '@langchain/openai';
96+
```ts
97+
import * as Sentry from '@sentry/browser';
6898

69-
const embeddings = Sentry.instrumentLangChainEmbeddings(new OpenAIEmbeddings({ model: 'text-embedding-3-small' }));
99+
Sentry.init({
100+
dsn: '__DSN__',
101+
integrations: [Sentry.browserTracingIntegration(), Sentry.elementTimingIntegration()],
102+
});
103+
```
70104

71-
await embeddings.embedQuery('Hello world');
105+
```html
106+
<img src="hero.jpg" elementtiming="hero-image" />
72107
```
73108

109+
### Other Changes
110+
111+
- feat(nuxt): Add middleware instrumentation compatibility for Nuxt 5 ([#19968](https://github.com/getsentry/sentry-javascript/pull/19968))
112+
- feat(nuxt): Support parametrized SSR routes in Nuxt 5 ([#19977](https://github.com/getsentry/sentry-javascript/pull/19977))
113+
- feat(solid): Add route parametrization for Solid Router ([#20031](https://github.com/getsentry/sentry-javascript/pull/20031))
114+
- fix(core): Guard nullish response in supabase PostgREST handler ([#20033](https://github.com/getsentry/sentry-javascript/pull/20033))
115+
- fix(node): Deduplicate `sentry-trace` and `baggage` headers on outgoing requests ([#19960](https://github.com/getsentry/sentry-javascript/pull/19960))
116+
- fix(node): Ensure startNewTrace propagates traceId in OTel environments ([#19963](https://github.com/getsentry/sentry-javascript/pull/19963))
117+
- fix(nuxt): Use virtual module for Nuxt pages data (SSR route parametrization) ([#20020](https://github.com/getsentry/sentry-javascript/pull/20020))
118+
- fix(opentelemetry): Convert seconds timestamps in span.end() to milliseconds ([#19958](https://github.com/getsentry/sentry-javascript/pull/19958))
119+
- fix(profiling): Disable profiling in worker threads ([#20040](https://github.com/getsentry/sentry-javascript/pull/20040))
120+
- fix(react-router): Disable debug ID injection in Vite plugin to prevent double injection ([#19890](https://github.com/getsentry/sentry-javascript/pull/19890))
121+
- refactor(browser): Reduce browser package bundle size ([#19856](https://github.com/getsentry/sentry-javascript/pull/19856))
122+
- feat(deps): Bump OpenTelemetry dependencies ([#20046](https://github.com/getsentry/sentry-javascript/pull/20046))
123+
124+
<details>
125+
<summary> <strong>Internal Changes</strong> </summary>
126+
127+
- chore: Add shared validate-pr composite action ([#20025](https://github.com/getsentry/sentry-javascript/pull/20025))
128+
- chore: Update validate-pr action to latest version ([#20027](https://github.com/getsentry/sentry-javascript/pull/20027))
129+
- chore(deps): Bump @apollo/server from 5.4.0 to 5.5.0 ([#20007](https://github.com/getsentry/sentry-javascript/pull/20007))
130+
- chore(deps): Bump amqplib from 0.10.7 to 0.10.9 ([#20000](https://github.com/getsentry/sentry-javascript/pull/20000))
131+
- chore(deps): Bump srvx from 0.11.12 to 0.11.13 ([#20001](https://github.com/getsentry/sentry-javascript/pull/20001))
132+
- chore(deps-dev): Bump node-forge from 1.3.2 to 1.4.0 ([#20012](https://github.com/getsentry/sentry-javascript/pull/20012))
133+
- chore(deps-dev): Bump yaml from 2.8.2 to 2.8.3 ([#19985](https://github.com/getsentry/sentry-javascript/pull/19985))
134+
- ci(deps): Bump actions/upload-artifact from 6 to 7 ([#19569](https://github.com/getsentry/sentry-javascript/pull/19569))
135+
- docs(release): Update publishing-a-release.md ([#19982](https://github.com/getsentry/sentry-javascript/pull/19982))
136+
- feat(deps): Bump babel-loader from 10.0.0 to 10.1.1 ([#19997](https://github.com/getsentry/sentry-javascript/pull/19997))
137+
- feat(deps): Bump handlebars from 4.7.7 to 4.7.9 ([#20008](https://github.com/getsentry/sentry-javascript/pull/20008))
138+
- fix(browser-tests): Pin axios to 1.13.5 to avoid compromised 1.14.1 ([#20047](https://github.com/getsentry/sentry-javascript/pull/20047))
139+
- fix(ci): Update validate-pr action to remove draft enforcement ([#20035](https://github.com/getsentry/sentry-javascript/pull/20035))
140+
- fix(ci): Update validate-pr action to remove draft enforcement ([#20037](https://github.com/getsentry/sentry-javascript/pull/20037))
141+
- fix(e2e): Pin @opentelemetry/api to 1.9.0 in ts3.8 test app ([#19992](https://github.com/getsentry/sentry-javascript/pull/19992))
142+
- ref(browser-tests): Add waitForMetricRequest helper ([#20002](https://github.com/getsentry/sentry-javascript/pull/20002))
143+
- ref(core): Consolidate getOperationName into one shared utility ([#19971](https://github.com/getsentry/sentry-javascript/pull/19971))
144+
- ref(core): Introduce instrumented method registry for AI integrations ([#19981](https://github.com/getsentry/sentry-javascript/pull/19981))
145+
- test(deno): Expand Deno E2E test coverage ([#19957](https://github.com/getsentry/sentry-javascript/pull/19957))
146+
- test(e2e): Add e2e tests for `nodeRuntimeMetricsIntegration` ([#19989](https://github.com/getsentry/sentry-javascript/pull/19989))
147+
148+
</details>
149+
74150
## 10.46.0
75151

76152
### Important Changes

dev-packages/browser-integration-tests/package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@sentry-internal/browser-integration-tests",
3-
"version": "10.46.0",
3+
"version": "10.47.0",
44
"main": "index.js",
55
"license": "MIT",
66
"engines": {
@@ -60,9 +60,9 @@
6060
"@babel/preset-typescript": "^7.16.7",
6161
"@playwright/test": "~1.56.0",
6262
"@sentry-internal/rrweb": "2.34.0",
63-
"@sentry/browser": "10.46.0",
63+
"@sentry/browser": "10.47.0",
6464
"@supabase/supabase-js": "2.49.3",
65-
"axios": "^1.12.2",
65+
"axios": "1.13.5",
6666
"babel-loader": "^10.1.1",
6767
"fflate": "0.8.2",
6868
"html-webpack-plugin": "^5.5.0",

dev-packages/browser-integration-tests/suites/tracing/metrics/element-timing/test.ts

Lines changed: 37 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -1,84 +1,15 @@
1-
import type { Page, Request, Route } from '@playwright/test';
1+
import type { Page, Route } from '@playwright/test';
22
import { expect } from '@playwright/test';
3-
import type { Envelope } from '@sentry/core';
3+
import type { SerializedMetric } from '@sentry/core';
44
import { sentryTest } from '../../../../utils/fixtures';
5-
import {
6-
properFullEnvelopeRequestParser,
7-
shouldSkipMetricsTest,
8-
shouldSkipTracingTest,
9-
} from '../../../../utils/helpers';
10-
11-
type MetricItem = Record<string, unknown> & {
12-
name: string;
13-
type: string;
14-
value: number;
15-
unit?: string;
16-
attributes: Record<string, { value: string | number; type: string }>;
17-
};
18-
19-
function extractMetricsFromRequest(req: Request): MetricItem[] {
20-
try {
21-
const envelope = properFullEnvelopeRequestParser<Envelope>(req);
22-
const items = envelope[1];
23-
const metrics: MetricItem[] = [];
24-
for (const item of items) {
25-
const [header] = item;
26-
if (header.type === 'trace_metric') {
27-
const payload = item[1] as { items?: MetricItem[] };
28-
if (payload.items) {
29-
metrics.push(...payload.items);
30-
}
31-
}
32-
}
33-
return metrics;
34-
} catch {
35-
return [];
36-
}
37-
}
38-
39-
/**
40-
* Collects element timing metrics from envelope requests on the page.
41-
* Returns a function to get all collected metrics so far and a function
42-
* that waits until all expected identifiers have been seen in render_time metrics.
43-
*/
44-
function createMetricCollector(page: Page) {
45-
const collectedRequests: Request[] = [];
46-
47-
page.on('request', req => {
48-
if (!req.url().includes('/api/1337/envelope/')) return;
49-
const metrics = extractMetricsFromRequest(req);
50-
if (metrics.some(m => m.name.startsWith('ui.element.'))) {
51-
collectedRequests.push(req);
52-
}
53-
});
54-
55-
function getAll(): MetricItem[] {
56-
return collectedRequests.flatMap(req => extractMetricsFromRequest(req));
57-
}
5+
import { shouldSkipMetricsTest, shouldSkipTracingTest, waitForMetrics } from '../../../../utils/helpers';
586

59-
async function waitForIdentifiers(identifiers: string[], timeout = 30_000): Promise<void> {
60-
const deadline = Date.now() + timeout;
61-
while (Date.now() < deadline) {
62-
const all = getAll().filter(m => m.name === 'ui.element.render_time');
63-
const seen = new Set(all.map(m => m.attributes['ui.element.identifier']?.value));
64-
if (identifiers.every(id => seen.has(id))) {
65-
return;
66-
}
67-
await page.waitForTimeout(500);
68-
}
69-
// Final check with assertion for clear error message
70-
const all = getAll().filter(m => m.name === 'ui.element.render_time');
71-
const seen = all.map(m => m.attributes['ui.element.identifier']?.value);
72-
for (const id of identifiers) {
73-
expect(seen).toContain(id);
74-
}
75-
}
76-
77-
function reset(): void {
78-
collectedRequests.length = 0;
79-
}
7+
function getIdentifier(m: SerializedMetric): unknown {
8+
return m.attributes?.['ui.element.identifier']?.value;
9+
}
8010

81-
return { getAll, waitForIdentifiers, reset };
11+
function getPaintType(m: SerializedMetric): unknown {
12+
return m.attributes?.['ui.element.paint_type']?.value;
8213
}
8314

8415
sentryTest(
@@ -91,19 +22,23 @@ sentryTest(
9122
serveAssets(page);
9223

9324
const url = await getLocalTestUrl({ testDir: __dirname });
94-
const collector = createMetricCollector(page);
9525

96-
await page.goto(url);
26+
const expectedIdentifiers = ['image-fast', 'text1', 'button1', 'image-slow', 'lazy-image', 'lazy-text'];
9727

98-
// Wait until all expected element identifiers have been flushed as metrics
99-
await collector.waitForIdentifiers(['image-fast', 'text1', 'button1', 'image-slow', 'lazy-image', 'lazy-text']);
28+
// Wait for all expected element identifiers to arrive as metrics
29+
const [allMetrics] = await Promise.all([
30+
waitForMetrics(page, metrics => {
31+
const seen = new Set(metrics.filter(m => m.name === 'ui.element.render_time').map(getIdentifier));
32+
return expectedIdentifiers.every(id => seen.has(id));
33+
}),
34+
page.goto(url),
35+
]);
10036

101-
const allMetrics = collector.getAll().filter(m => m.name.startsWith('ui.element.'));
10237
const renderTimeMetrics = allMetrics.filter(m => m.name === 'ui.element.render_time');
10338
const loadTimeMetrics = allMetrics.filter(m => m.name === 'ui.element.load_time');
10439

105-
const renderIdentifiers = renderTimeMetrics.map(m => m.attributes['ui.element.identifier']?.value);
106-
const loadIdentifiers = loadTimeMetrics.map(m => m.attributes['ui.element.identifier']?.value);
40+
const renderIdentifiers = renderTimeMetrics.map(getIdentifier);
41+
const loadIdentifiers = loadTimeMetrics.map(getIdentifier);
10742

10843
// All text and image elements should have render_time
10944
expect(renderIdentifiers).toContain('image-fast');
@@ -124,18 +59,18 @@ sentryTest(
12459
expect(loadIdentifiers).not.toContain('lazy-text');
12560

12661
// Validate metric structure for image-fast
127-
const imageFastRender = renderTimeMetrics.find(m => m.attributes['ui.element.identifier']?.value === 'image-fast');
62+
const imageFastRender = renderTimeMetrics.find(m => getIdentifier(m) === 'image-fast');
12863
expect(imageFastRender).toMatchObject({
12964
name: 'ui.element.render_time',
13065
type: 'distribution',
13166
unit: 'millisecond',
13267
value: expect.any(Number),
13368
});
134-
expect(imageFastRender!.attributes['ui.element.paint_type']?.value).toBe('image-paint');
69+
expect(getPaintType(imageFastRender!)).toBe('image-paint');
13570

13671
// Validate text-paint metric
137-
const text1Render = renderTimeMetrics.find(m => m.attributes['ui.element.identifier']?.value === 'text1');
138-
expect(text1Render!.attributes['ui.element.paint_type']?.value).toBe('text-paint');
72+
const text1Render = renderTimeMetrics.find(m => getIdentifier(m) === 'text1');
73+
expect(getPaintType(text1Render!)).toBe('text-paint');
13974
},
14075
);
14176

@@ -147,25 +82,31 @@ sentryTest('emits element timing metrics after navigation', async ({ getLocalTes
14782
serveAssets(page);
14883

14984
const url = await getLocalTestUrl({ testDir: __dirname });
150-
const collector = createMetricCollector(page);
85+
86+
// Start listening before navigation to avoid missing metrics
87+
const pageloadMetricsPromise = waitForMetrics(page, metrics =>
88+
metrics.some(m => m.name === 'ui.element.render_time' && getIdentifier(m) === 'image-fast'),
89+
);
15190

15291
await page.goto(url);
15392

15493
// Wait for pageload element timing metrics to arrive before navigating
155-
await collector.waitForIdentifiers(['image-fast', 'text1']);
94+
await pageloadMetricsPromise;
15695

157-
// Reset so we only capture post-navigation metrics
158-
collector.reset();
96+
// Start listening before click to avoid missing metrics
97+
const navigationMetricsPromise = waitForMetrics(page, metrics => {
98+
const seen = new Set(metrics.filter(m => m.name === 'ui.element.render_time').map(getIdentifier));
99+
return seen.has('navigation-image') && seen.has('navigation-text');
100+
});
159101

160102
// Trigger navigation
161103
await page.locator('#button1').click();
162104

163105
// Wait for navigation element timing metrics
164-
await collector.waitForIdentifiers(['navigation-image', 'navigation-text']);
106+
const navigationMetrics = await navigationMetricsPromise;
165107

166-
const allMetrics = collector.getAll();
167-
const renderTimeMetrics = allMetrics.filter(m => m.name === 'ui.element.render_time');
168-
const renderIdentifiers = renderTimeMetrics.map(m => m.attributes['ui.element.identifier']?.value);
108+
const renderTimeMetrics = navigationMetrics.filter(m => m.name === 'ui.element.render_time');
109+
const renderIdentifiers = renderTimeMetrics.map(getIdentifier);
169110

170111
expect(renderIdentifiers).toContain('navigation-image');
171112
expect(renderIdentifiers).toContain('navigation-text');

dev-packages/browser-integration-tests/suites/tracing/request/fetch-trace-header-merging/test.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,10 @@ async function assertRequests({
3939

4040
// No merged sentry trace headers
4141
expect(headers['sentry-trace']).not.toContain(',');
42+
expect(headers['sentry-trace']).toBe('12312012123120121231201212312012-1121201211212012-1');
4243

4344
// No multiple baggage entries
44-
expect(headers['baggage'].match(/sentry-release/g) ?? []).toHaveLength(1);
45+
expect(headers['baggage']).toBe('sentry-release=4.2.0');
4546
});
4647
}
4748

0 commit comments

Comments
 (0)