Skip to content

Commit 3b0b82d

Browse files
authored
Merge branch 'develop' into feat/growthbook-integration
2 parents 8341c65 + b29c880 commit 3b0b82d

File tree

11 files changed

+400
-125
lines changed

11 files changed

+400
-125
lines changed

dev-packages/node-integration-tests/suites/tracing/anthropic/scenario.mjs

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,55 @@ function startMockAnthropicServer() {
2929
return;
3030
}
3131

32+
// Check if streaming is requested
33+
if (req.body.stream === true) {
34+
res.writeHead(200, {
35+
'Content-Type': 'text/event-stream',
36+
'Cache-Control': 'no-cache',
37+
Connection: 'keep-alive',
38+
});
39+
40+
// Send streaming events
41+
const events = [
42+
{
43+
type: 'message_start',
44+
message: {
45+
id: 'msg_stream123',
46+
type: 'message',
47+
role: 'assistant',
48+
model,
49+
content: [],
50+
usage: { input_tokens: 10 },
51+
},
52+
},
53+
{ type: 'content_block_start', index: 0, content_block: { type: 'text', text: '' } },
54+
{ type: 'content_block_delta', index: 0, delta: { type: 'text_delta', text: 'Hello ' } },
55+
{ type: 'content_block_delta', index: 0, delta: { type: 'text_delta', text: 'from ' } },
56+
{ type: 'content_block_delta', index: 0, delta: { type: 'text_delta', text: 'stream!' } },
57+
{ type: 'content_block_stop', index: 0 },
58+
{
59+
type: 'message_delta',
60+
delta: { stop_reason: 'end_turn', stop_sequence: null },
61+
usage: { output_tokens: 15 },
62+
},
63+
{ type: 'message_stop' },
64+
];
65+
66+
events.forEach((event, index) => {
67+
setTimeout(() => {
68+
res.write(`event: ${event.type}\n`);
69+
res.write(`data: ${JSON.stringify(event)}\n\n`);
70+
71+
if (index === events.length - 1) {
72+
res.end();
73+
}
74+
}, index * 10); // Small delay between events
75+
});
76+
77+
return;
78+
}
79+
80+
// Non-streaming response
3281
res.send({
3382
id: 'msg_mock123',
3483
type: 'message',
@@ -92,8 +141,32 @@ async function run() {
92141

93142
// Fourth test: models.retrieve
94143
await client.models.retrieve('claude-3-haiku-20240307');
144+
145+
// Fifth test: streaming via messages.create
146+
const stream = await client.messages.create({
147+
model: 'claude-3-haiku-20240307',
148+
messages: [{ role: 'user', content: 'What is the capital of France?' }],
149+
stream: true,
150+
});
151+
152+
for await (const _ of stream) {
153+
void _;
154+
}
155+
156+
// Sixth test: streaming via messages.stream
157+
await client.messages
158+
.stream({
159+
model: 'claude-3-haiku-20240307',
160+
messages: [{ role: 'user', content: 'What is the capital of France?' }],
161+
})
162+
.on('streamEvent', () => {
163+
Sentry.captureMessage('stream event from user-added event listener captured');
164+
});
95165
});
96166

167+
// Wait for the stream event handler to finish
168+
await Sentry.flush(2000);
169+
97170
server.close();
98171
}
99172

dev-packages/node-integration-tests/suites/tracing/anthropic/test.ts

Lines changed: 45 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,30 @@ describe('Anthropic integration', () => {
152152
origin: 'auto.ai.anthropic',
153153
status: 'ok',
154154
}),
155+
// Fifth span - messages.create with stream: true
156+
expect.objectContaining({
157+
data: expect.objectContaining({
158+
'gen_ai.operation.name': 'messages',
159+
'gen_ai.request.model': 'claude-3-haiku-20240307',
160+
'gen_ai.request.stream': true,
161+
}),
162+
description: 'messages claude-3-haiku-20240307 stream-response',
163+
op: 'gen_ai.messages',
164+
origin: 'auto.ai.anthropic',
165+
status: 'ok',
166+
}),
167+
// Sixth span - messages.stream
168+
expect.objectContaining({
169+
data: expect.objectContaining({
170+
'gen_ai.operation.name': 'messages',
171+
'gen_ai.request.model': 'claude-3-haiku-20240307',
172+
'gen_ai.request.stream': true,
173+
}),
174+
description: 'messages claude-3-haiku-20240307 stream-response',
175+
op: 'gen_ai.messages',
176+
origin: 'auto.ai.anthropic',
177+
status: 'ok',
178+
}),
155179
]),
156180
};
157181

@@ -189,6 +213,21 @@ describe('Anthropic integration', () => {
189213
]),
190214
};
191215

216+
const EXPECTED_MODEL_ERROR = {
217+
exception: {
218+
values: [
219+
{
220+
type: 'Error',
221+
value: '404 Model not found',
222+
},
223+
],
224+
},
225+
};
226+
227+
const EXPECTED_STREAM_EVENT_HANDLER_MESSAGE = {
228+
message: 'stream event from user-added event listener captured',
229+
};
230+
192231
createEsmAndCjsTests(__dirname, 'scenario-manual-client.mjs', 'instrument.mjs', (createRunner, test) => {
193232
test('creates anthropic related spans when manually insturmenting client', async () => {
194233
await createRunner()
@@ -202,8 +241,9 @@ describe('Anthropic integration', () => {
202241
createEsmAndCjsTests(__dirname, 'scenario.mjs', 'instrument.mjs', (createRunner, test) => {
203242
test('creates anthropic related spans with sendDefaultPii: false', async () => {
204243
await createRunner()
205-
.ignore('event')
244+
.expect({ event: EXPECTED_MODEL_ERROR })
206245
.expect({ transaction: EXPECTED_TRANSACTION_DEFAULT_PII_FALSE })
246+
.expect({ event: EXPECTED_STREAM_EVENT_HANDLER_MESSAGE })
207247
.start()
208248
.completed();
209249
});
@@ -212,8 +252,9 @@ describe('Anthropic integration', () => {
212252
createEsmAndCjsTests(__dirname, 'scenario.mjs', 'instrument-with-pii.mjs', (createRunner, test) => {
213253
test('creates anthropic related spans with sendDefaultPii: true', async () => {
214254
await createRunner()
215-
.ignore('event')
255+
.expect({ event: EXPECTED_MODEL_ERROR })
216256
.expect({ transaction: EXPECTED_TRANSACTION_DEFAULT_PII_TRUE })
257+
.expect({ event: EXPECTED_STREAM_EVENT_HANDLER_MESSAGE })
217258
.start()
218259
.completed();
219260
});
@@ -222,8 +263,9 @@ describe('Anthropic integration', () => {
222263
createEsmAndCjsTests(__dirname, 'scenario.mjs', 'instrument-with-options.mjs', (createRunner, test) => {
223264
test('creates anthropic related spans with custom options', async () => {
224265
await createRunner()
225-
.ignore('event')
266+
.expect({ event: EXPECTED_MODEL_ERROR })
226267
.expect({ transaction: EXPECTED_TRANSACTION_WITH_OPTIONS })
268+
.expect({ event: EXPECTED_STREAM_EVENT_HANDLER_MESSAGE })
227269
.start()
228270
.completed();
229271
});

packages/aws-serverless/README.md

Lines changed: 41 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -15,16 +15,53 @@
1515
This package is a wrapper around `@sentry/node`, with added functionality related to AWS Lambda. All
1616
methods available in `@sentry/node` can be imported from `@sentry/aws-serverless`.
1717

18-
To use this SDK, call `Sentry.init(options)` at the very beginning of your JavaScript file.
18+
### Automatic Setup
19+
20+
To use this SDK with an automatic setup, set the following environment variables in your Lambda function configuration:
21+
22+
```bash
23+
NODE_OPTIONS="--import @sentry/aws-serverless/awslambda-auto"
24+
SENTRY_DSN="__DSN__"
25+
# Add Tracing by setting tracesSampleRate and adding integration
26+
# Set tracesSampleRate to 1.0 to capture 100% of transactions
27+
# We recommend adjusting this value in production
28+
# Learn more at
29+
# https://docs.sentry.io/platforms/javascript/configuration/options/#traces-sample-rate
30+
SENTRY_TRACES_SAMPLE_RATE="1.0"
31+
```
32+
33+
### Manual Setup
34+
35+
Alternatively, to further customize the SDK setup, you can also manually initialize the SDK in your lambda function. The benefit of this installation method is that you can fully customize your Sentry SDK setup in a Sentry.init call.
1936

20-
```javascript
37+
Create a new file, for example `instrument.js` to initialize the SDK:
38+
39+
```js
2140
import * as Sentry from '@sentry/aws-serverless';
2241

2342
Sentry.init({
2443
dsn: '__DSN__',
25-
// ...
44+
// Adds request headers and IP for users, for more info visit:
45+
// https://docs.sentry.io/platforms/javascript/guides/aws-lambda/configuration/options/#sendDefaultPii
46+
sendDefaultPii: true,
47+
// Add Tracing by setting tracesSampleRate and adding integration
48+
// Set tracesSampleRate to 1.0 to capture 100% of transactions
49+
// We recommend adjusting this value in production
50+
// Learn more at
51+
// https://docs.sentry.io/platforms/javascript/configuration/options/#traces-sample-rate
52+
tracesSampleRate: 1.0,
2653
});
54+
```
55+
56+
And then load the SDK before your function starts by importing the instrument.js file via a NODE_OPTIONS environment variable:
57+
58+
```bash
59+
NODE_OPTIONS="--import ./instrument.js"
60+
```
61+
62+
## Verify
2763

64+
```js
2865
// async (recommended)
2966
export const handler = async (event, context) => {
3067
throw new Error('oh, hello there!');
@@ -36,19 +73,7 @@ export const handler = (event, context, callback) => {
3673
};
3774
```
3875

39-
If you also want to trace performance of all the incoming requests and also outgoing AWS service requests, just set the
40-
`tracesSampleRate` option.
41-
42-
```javascript
43-
import * as Sentry from '@sentry/aws-serverless';
44-
45-
Sentry.init({
46-
dsn: '__DSN__',
47-
tracesSampleRate: 1.0,
48-
});
49-
```
50-
51-
#### Integrate Sentry using the Sentry Lambda layer
76+
## Integrate Sentry using the Sentry Lambda layer
5277

5378
Another much simpler way to integrate Sentry to your AWS Lambda function is to add the official layer.
5479

packages/aws-serverless/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@
7979
},
8080
"scripts": {
8181
"build": "run-p build:transpile build:types",
82-
"build:layer": "rollup -c rollup.lambda-extension.config.mjs && yarn ts-node scripts/buildLambdaLayer.ts",
82+
"build:layer": "rimraf build/aws && rollup -c rollup.lambda-extension.config.mjs && yarn ts-node scripts/buildLambdaLayer.ts",
8383
"build:dev": "run-p build:transpile build:types",
8484
"build:transpile": "rollup -c rollup.npm.config.mjs && yarn build:layer",
8585
"build:types": "run-s build:types:core build:types:downlevel",

packages/aws-serverless/scripts/buildLambdaLayer.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,9 +51,11 @@ async function buildLambdaLayer(): Promise<void> {
5151
fs.chmodSync('./build/aws/dist-serverless/sentry-extension/index.mjs', 0o755);
5252

5353
const zipFilename = `sentry-node-serverless-${version}.zip`;
54+
// Only include these directories in the zip file
55+
const dirsToZip = ['nodejs', 'extensions', 'sentry-extension'];
5456
console.log(`Creating final layer zip file ${zipFilename}.`);
5557
// need to preserve the symlink above with -y
56-
run(`zip -r -y ${zipFilename} .`, { cwd: 'build/aws/dist-serverless' });
58+
run(`zip -r -y ${zipFilename} ${dirsToZip.join(' ')}`, { cwd: 'build/aws/dist-serverless' });
5759
}
5860

5961
// eslint-disable-next-line @typescript-eslint/no-floating-promises
@@ -79,7 +81,11 @@ async function pruneNodeModules(): Promise<void> {
7981
'./build/aws/dist-serverless/nodejs/node_modules/@sentry/aws-serverless/build/npm/esm/awslambda-auto.js',
8082
];
8183

82-
const { fileList } = await nodeFileTrace(entrypoints);
84+
const { fileList } = await nodeFileTrace(entrypoints, {
85+
// import-in-the-middle uses mixed require and import syntax in their `hook.mjs` file.
86+
// So we need to set `mixedModules` to `true` to ensure that all modules are tracked.
87+
mixedModules: true,
88+
});
8389

8490
const allFiles = getAllFiles('./build/aws/dist-serverless/nodejs/node_modules');
8591

0 commit comments

Comments
 (0)