Skip to content

Commit cda5b4c

Browse files
authored
Merge branch 'develop' into abhi-enable-console-logs-by-default
2 parents 19c7f9b + 34538c8 commit cda5b4c

File tree

16 files changed

+325
-136
lines changed

16 files changed

+325
-136
lines changed

CHANGELOG.md

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,21 @@
44

55
- "You miss 100 percent of the chances you don't take. — Wayne Gretzky" — Michael Scott
66

7+
- **feat(cloudflare): Add honoIntegration with error-filtering function ([#17743](https://github.com/getsentry/sentry-javascript/pull/17743))**
8+
9+
This release adds a `honoIntegration` to `@sentry/cloudflare`, which exposes a `shouldHandleError` function that lets you define which errors in `onError` should be captured.
10+
By default, Sentry captures exceptions with `error.status >= 500 || error.status <= 299`.
11+
12+
The integration is added by default, and it's possible to modify this behavior like this:
13+
14+
```js
15+
integrations: [
16+
honoIntegration({
17+
shouldHandleError: (err) => true; // always capture exceptions in onError
18+
})
19+
]
20+
```
21+
722
Work in this release was contributed by @Karibash. Thank you for your contribution!
823

924
## 10.14.0
@@ -12,7 +27,7 @@ Work in this release was contributed by @Karibash. Thank you for your contributi
1227

1328
- **feat(cloudflare,vercel-edge): Add support for Google Gen AI instrumentation ([#17723](https://github.com/getsentry/sentry-javascript/pull/17723))**
1429

15-
The SDK now automatically instruments Google's Generative AI operations in Cloudflare Workers and Vercel Edge Runtime environments, providing insights into your AI operations.
30+
The SDK now supports manually instrumenting Google's Generative AI operations in Cloudflare Workers and Vercel Edge Runtime environments, providing insights into your AI operations. You can use `const wrappedClient = Sentry.instrumentGoogleGenAIClient(genAiClient)` to get an instrumented client.
1631

1732
### Other Changes
1833

dev-packages/e2e-tests/test-applications/aws-serverless/src/lambda-functions-layer/ExperimentalExtension/index.mjs

Lines changed: 0 additions & 16 deletions
This file was deleted.

dev-packages/e2e-tests/test-applications/aws-serverless/tests/layer.test.ts

Lines changed: 0 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -242,52 +242,4 @@ test.describe('Lambda layer', () => {
242242
}),
243243
);
244244
});
245-
246-
test('experimental extension works', async ({ lambdaClient }) => {
247-
const transactionEventPromise = waitForTransaction('aws-serverless-lambda-sam', transactionEvent => {
248-
return transactionEvent?.transaction === 'LayerExperimentalExtension';
249-
});
250-
251-
await lambdaClient.send(
252-
new InvokeCommand({
253-
FunctionName: 'LayerExperimentalExtension',
254-
Payload: JSON.stringify({}),
255-
}),
256-
);
257-
258-
const transactionEvent = await transactionEventPromise;
259-
260-
expect(transactionEvent.transaction).toEqual('LayerExperimentalExtension');
261-
expect(transactionEvent.contexts?.trace).toEqual({
262-
data: {
263-
'sentry.sample_rate': 1,
264-
'sentry.source': 'custom',
265-
'sentry.origin': 'auto.otel.aws-lambda',
266-
'sentry.op': 'function.aws.lambda',
267-
'cloud.account.id': '012345678912',
268-
'faas.execution': expect.any(String),
269-
'faas.id': 'arn:aws:lambda:us-east-1:012345678912:function:LayerExperimentalExtension',
270-
'faas.coldstart': true,
271-
'otel.kind': 'SERVER',
272-
},
273-
op: 'function.aws.lambda',
274-
origin: 'auto.otel.aws-lambda',
275-
span_id: expect.stringMatching(/[a-f0-9]{16}/),
276-
status: 'ok',
277-
trace_id: expect.stringMatching(/[a-f0-9]{32}/),
278-
});
279-
280-
expect(transactionEvent.spans).toHaveLength(1);
281-
282-
expect(transactionEvent.spans).toContainEqual(
283-
expect.objectContaining({
284-
data: expect.objectContaining({
285-
'sentry.op': 'test',
286-
'sentry.origin': 'manual',
287-
}),
288-
description: 'manual-span',
289-
op: 'test',
290-
}),
291-
);
292-
});
293245
});

packages/aws-serverless/src/init.ts

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,10 @@ export function getDefaultIntegrations(options: Options): Integration[] {
1515
}
1616

1717
export interface AwsServerlessOptions extends NodeOptions {
18-
_experiments?: NodeOptions['_experiments'] & {
19-
/**
20-
* If proxying Sentry events through the Sentry Lambda extension should be enabled.
21-
*/
22-
enableLambdaExtension?: boolean;
23-
};
18+
/**
19+
* If Sentry events should be proxied through the Lambda extension when using the Lambda layer. Defaults to `true` when using the Lambda layer.
20+
*/
21+
useLayerExtension?: boolean;
2422
}
2523

2624
/**
@@ -29,14 +27,14 @@ export interface AwsServerlessOptions extends NodeOptions {
2927
* @param options Configuration options for the SDK, @see {@link AWSLambdaOptions}.
3028
*/
3129
export function init(options: AwsServerlessOptions = {}): NodeClient | undefined {
30+
const sdkSource = getSDKSource();
3231
const opts = {
3332
defaultIntegrations: getDefaultIntegrations(options),
33+
useLayerExtension: sdkSource === 'aws-lambda-layer' && !options.tunnel,
3434
...options,
3535
};
3636

37-
const sdkSource = getSDKSource();
38-
39-
if (opts._experiments?.enableLambdaExtension) {
37+
if (opts.useLayerExtension) {
4038
if (sdkSource === 'aws-lambda-layer') {
4139
if (!opts.tunnel) {
4240
DEBUG_BUILD && debug.log('Proxying Sentry events through the Sentry Lambda extension');

packages/aws-serverless/test/init.test.ts

Lines changed: 41 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,12 @@ const mockGetSDKSource = vi.mocked(getSDKSource);
1818
const mockInitWithoutDefaultIntegrations = vi.mocked(initWithoutDefaultIntegrations);
1919

2020
describe('init', () => {
21-
describe('experimental Lambda extension support', () => {
21+
describe('Lambda extension setup', () => {
2222
test('should preserve user-provided tunnel option when Lambda extension is enabled', () => {
2323
mockGetSDKSource.mockReturnValue('aws-lambda-layer');
2424
const options: AwsServerlessOptions = {
2525
tunnel: 'https://custom-tunnel.example.com',
26-
_experiments: {
27-
enableLambdaExtension: true,
28-
},
26+
useLayerExtension: true,
2927
};
3028

3129
init(options);
@@ -40,9 +38,7 @@ describe('init', () => {
4038
test('should set default tunnel when Lambda extension is enabled and SDK source is aws-lambda-layer', () => {
4139
mockGetSDKSource.mockReturnValue('aws-lambda-layer');
4240
const options: AwsServerlessOptions = {
43-
_experiments: {
44-
enableLambdaExtension: true,
45-
},
41+
useLayerExtension: true,
4642
};
4743

4844
init(options);
@@ -57,9 +53,7 @@ describe('init', () => {
5753
test('should not set tunnel when Lambda extension is disabled', () => {
5854
mockGetSDKSource.mockReturnValue('aws-lambda-layer');
5955
const options: AwsServerlessOptions = {
60-
_experiments: {
61-
enableLambdaExtension: false,
62-
},
56+
useLayerExtension: false,
6357
};
6458

6559
init(options);
@@ -74,9 +68,7 @@ describe('init', () => {
7468
test('should not set tunnel when SDK source is not aws-lambda-layer even with Lambda extension enabled', () => {
7569
mockGetSDKSource.mockReturnValue('npm');
7670
const options: AwsServerlessOptions = {
77-
_experiments: {
78-
enableLambdaExtension: true,
79-
},
71+
useLayerExtension: true,
8072
};
8173

8274
init(options);
@@ -88,17 +80,52 @@ describe('init', () => {
8880
);
8981
});
9082

91-
test('should not set tunnel when no experiments are provided', () => {
83+
test('should default useLayerExtension to true when SDK source is aws-lambda-layer', () => {
9284
mockGetSDKSource.mockReturnValue('aws-lambda-layer');
9385
const options: AwsServerlessOptions = {};
9486

9587
init(options);
9688

89+
expect(mockInitWithoutDefaultIntegrations).toHaveBeenCalledWith(
90+
expect.objectContaining({
91+
useLayerExtension: true,
92+
tunnel: 'http://localhost:9000/envelope',
93+
}),
94+
);
95+
});
96+
97+
test('should default useLayerExtension to false when SDK source is not aws-lambda-layer', () => {
98+
mockGetSDKSource.mockReturnValue('npm');
99+
const options: AwsServerlessOptions = {};
100+
101+
init(options);
102+
103+
expect(mockInitWithoutDefaultIntegrations).toHaveBeenCalledWith(
104+
expect.objectContaining({
105+
useLayerExtension: false,
106+
}),
107+
);
97108
expect(mockInitWithoutDefaultIntegrations).toHaveBeenCalledWith(
98109
expect.not.objectContaining({
99110
tunnel: expect.any(String),
100111
}),
101112
);
102113
});
114+
115+
test('should default useLayerExtension to false when tunnel is provided even when SDK source is aws-lambda-layer', () => {
116+
mockGetSDKSource.mockReturnValue('aws-lambda-layer');
117+
const options: AwsServerlessOptions = {
118+
tunnel: 'https://custom-tunnel.example.com',
119+
};
120+
121+
init(options);
122+
123+
expect(mockInitWithoutDefaultIntegrations).toHaveBeenCalledWith(
124+
expect.objectContaining({
125+
useLayerExtension: false,
126+
tunnel: 'https://custom-tunnel.example.com',
127+
}),
128+
);
129+
});
103130
});
104131
});

packages/browser-utils/src/metrics/resourceTiming.ts

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@ import type { SpanAttributes } from '@sentry/core';
22
import { browserPerformanceTimeOrigin } from '@sentry/core';
33
import { extractNetworkProtocol, getBrowserPerformanceAPI } from './utils';
44

5-
function getAbsoluteTime(time = 0): number {
6-
return ((browserPerformanceTimeOrigin() || performance.timeOrigin) + time) / 1000;
5+
function getAbsoluteTime(time: number | undefined): number | undefined {
6+
// falsy values should be preserved so that we can later on drop undefined values and
7+
// preserve 0 vals for cross-origin resources without proper `Timing-Allow-Origin` header.
8+
return time ? ((browserPerformanceTimeOrigin() || performance.timeOrigin) + time) / 1000 : time;
79
}
810

911
/**
@@ -30,7 +32,7 @@ export function resourceTimingToSpanAttributes(resourceTiming: PerformanceResour
3032
return timingSpanData;
3133
}
3234

33-
return {
35+
return dropUndefinedKeysFromObject({
3436
...timingSpanData,
3537

3638
'http.request.redirect_start': getAbsoluteTime(resourceTiming.redirectStart),
@@ -55,6 +57,16 @@ export function resourceTimingToSpanAttributes(resourceTiming: PerformanceResour
5557
// For TTFB we actually want the relative time from timeOrigin to responseStart
5658
// This way, TTFB always measures the "first page load" experience.
5759
// see: https://web.dev/articles/ttfb#measure-resource-requests
58-
'http.request.time_to_first_byte': (resourceTiming.responseStart ?? 0) / 1000,
59-
};
60+
'http.request.time_to_first_byte':
61+
resourceTiming.responseStart != null ? resourceTiming.responseStart / 1000 : undefined,
62+
});
63+
}
64+
65+
/**
66+
* Remove properties with `undefined` as value from an object.
67+
* In contrast to `dropUndefinedKeys` in core this funciton only works on first-level
68+
* key-value objects and does not recursively go into object properties or arrays.
69+
*/
70+
function dropUndefinedKeysFromObject<T extends object>(attrs: T): Partial<T> {
71+
return Object.fromEntries(Object.entries(attrs).filter(([, value]) => value != null)) as Partial<T>;
6072
}

packages/browser-utils/test/browser/browserMetrics.test.ts

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,18 @@ describe('_addResourceSpans', () => {
266266
decodedBodySize: 593,
267267
renderBlockingStatus: 'non-blocking',
268268
nextHopProtocol: 'http/1.1',
269+
connectStart: 1000,
270+
connectEnd: 1001,
271+
redirectStart: 1002,
272+
redirectEnd: 1003,
273+
fetchStart: 1004,
274+
domainLookupStart: 1005,
275+
domainLookupEnd: 1006,
276+
requestStart: 1007,
277+
responseStart: 1008,
278+
responseEnd: 1009,
279+
secureConnectionStart: 1005,
280+
workerStart: 1006,
269281
});
270282

271283
const timeOrigin = 100;
@@ -305,7 +317,7 @@ describe('_addResourceSpans', () => {
305317
'http.request.response_end': expect.any(Number),
306318
'http.request.response_start': expect.any(Number),
307319
'http.request.secure_connection_start': expect.any(Number),
308-
'http.request.time_to_first_byte': 0,
320+
'http.request.time_to_first_byte': 1.008,
309321
'http.request.worker_start': expect.any(Number),
310322
},
311323
}),
@@ -492,6 +504,18 @@ describe('_addResourceSpans', () => {
492504
encodedBodySize: null,
493505
decodedBodySize: null,
494506
nextHopProtocol: 'h3',
507+
connectStart: 1000,
508+
connectEnd: 1001,
509+
redirectStart: 1002,
510+
redirectEnd: 1003,
511+
fetchStart: 1004,
512+
domainLookupStart: 1005,
513+
domainLookupEnd: 1006,
514+
requestStart: 1007,
515+
responseStart: 1008,
516+
responseEnd: 1009,
517+
secureConnectionStart: 1005,
518+
workerStart: 1006,
495519
} as unknown as PerformanceResourceTiming;
496520

497521
_addResourceSpans(span, entry, resourceEntryName, 100, 23, 345);
@@ -518,7 +542,7 @@ describe('_addResourceSpans', () => {
518542
'http.request.response_end': expect.any(Number),
519543
'http.request.response_start': expect.any(Number),
520544
'http.request.secure_connection_start': expect.any(Number),
521-
'http.request.time_to_first_byte': 0,
545+
'http.request.time_to_first_byte': 1.008,
522546
'http.request.worker_start': expect.any(Number),
523547
},
524548
description: '/assets/to/css',

0 commit comments

Comments
 (0)