Skip to content
Draft
Show file tree
Hide file tree
Changes from 9 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
11 changes: 11 additions & 0 deletions e2e-tests/tests/nextjs-14.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,13 @@
(await wizardInstance.sendStdinAndWaitForOutput(
// Selecting `No` for CI/CD tool
[KEYS.DOWN, KEYS.ENTER],
'Do you want to create a sentryrules file',
));


sentryRulesPrompted &&

Check failure on line 85 in e2e-tests/tests/nextjs-14.test.ts

View workflow job for this annotation

GitHub Actions / Build

Cannot find name 'sentryRulesPrompted'.
(await wizardInstance.sendStdinAndWaitForOutput(
[KEYS.ENTER],
'Successfully installed the Sentry Next.js SDK!',
));

Expand Down Expand Up @@ -134,6 +141,10 @@
]);
});

test('sentryrules file exists', () => {
checkFileExists(`${projectDir}/.rules/sentryrules.md`);
});

test('next.config file contains Sentry wrapper', () => {
checkFileContents(`${projectDir}/next.config.mjs`, [
"import {withSentryConfig} from '@sentry/nextjs'",
Expand Down
13 changes: 13 additions & 0 deletions e2e-tests/tests/nextjs-15.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,19 @@ describe('NextJS-15', () => {
(await wizardInstance.sendStdinAndWaitForOutput(
// Selecting `No` for CI/CD tool
[KEYS.DOWN, KEYS.ENTER],
'Do you want to create a sentryrules file',
));

const sentryRulesPrompted =
ciCdPrompted &&
(await wizardInstance.sendStdinAndWaitForOutput(
[KEYS.ENTER],
'Do you want to create a sentryrules file',
));

sentryRulesPrompted &&
(await wizardInstance.sendStdinAndWaitForOutput(
[KEYS.ENTER],
'Successfully installed the Sentry Next.js SDK!',
));

Expand Down
80 changes: 80 additions & 0 deletions src/nextjs/nextjs-wizard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
getSimpleUnderscoreErrorCopyPasteSnippet,
getWithSentryConfigOptionsTemplate,
getInstrumentationClientHookCopyPasteSnippet,
getAiRulesFileContent,
} from './templates';
import { getNextJsVersionBucket } from './utils';

Expand Down Expand Up @@ -344,6 +345,51 @@
const packageManagerForOutro =
packageManagerFromInstallStep ?? (await getPackageManager());

await traceStep('create-ai-rules-file', async () => {
const shouldCreateAiRulesFile = await askShouldCreateAiRulesFile();
if (shouldCreateAiRulesFile) {
try {
const rulesDir = path.join(process.cwd(), '.rules');
const rulesDirExists = fs.existsSync(rulesDir);

Check warning on line 353 in src/nextjs/nextjs-wizard.ts

View check run for this annotation

Codecov / codecov/patch

src/nextjs/nextjs-wizard.ts#L348-L353

Added lines #L348 - L353 were not covered by tests

// Create .rules directory if it doesn't exist
if (!rulesDirExists) {
await fs.promises.mkdir(rulesDir, { recursive: true });
}

Check warning on line 358 in src/nextjs/nextjs-wizard.ts

View check run for this annotation

Codecov / codecov/patch

src/nextjs/nextjs-wizard.ts#L356-L358

Added lines #L356 - L358 were not covered by tests

const aiRulesContent = getAiRulesFileContent();
await fs.promises.writeFile(
path.join(rulesDir, 'sentryrules.md'),
aiRulesContent,
{ encoding: 'utf8', flag: 'w' },
);

Check warning on line 365 in src/nextjs/nextjs-wizard.ts

View check run for this annotation

Codecov / codecov/patch

src/nextjs/nextjs-wizard.ts#L360-L365

Added lines #L360 - L365 were not covered by tests

clack.log.success(
`Created ${chalk.cyan('sentryrules.md')} in ${chalk.cyan(
'.rules',
)} directory.`,
);
} catch (error) {
clack.log.error(
`Failed to create ${chalk.cyan('sentryrules.md')} in ${chalk.cyan(
'.rules',
)} directory.`,
);

Check warning on line 377 in src/nextjs/nextjs-wizard.ts

View check run for this annotation

Codecov / codecov/patch

src/nextjs/nextjs-wizard.ts#L367-L377

Added lines #L367 - L377 were not covered by tests

const aiRulesContent = getAiRulesFileContent();
await showCopyPasteInstructions({
filename: '.rules/sentryrules.md',
codeSnippet: aiRulesContent,
hint: "create the .rules directory and file if they don't already exist",
});

Check warning on line 384 in src/nextjs/nextjs-wizard.ts

View check run for this annotation

Codecov / codecov/patch

src/nextjs/nextjs-wizard.ts#L379-L384

Added lines #L379 - L384 were not covered by tests

Sentry.captureException(error);
}
} else {
clack.log.info('Skipped creating sentryrules.md.');
}
});

Check warning on line 391 in src/nextjs/nextjs-wizard.ts

View check run for this annotation

Codecov / codecov/patch

src/nextjs/nextjs-wizard.ts#L386-L391

Added lines #L386 - L391 were not covered by tests

await runPrettierIfInstalled({ cwd: undefined });

clack.outro(`
Expand Down Expand Up @@ -1071,6 +1117,40 @@
});
}

/**
* Ask users if they want to create a .sentryrules file with AI rule examples for Sentry.
* This is useful for giving the LLM context on common actions in Sentry like custom spans,
* logging, and error / exception handling.
*/

async function askShouldCreateAiRulesFile(): Promise<boolean> {
return await traceStep('ask-create-ai-rules-file', async (span) => {
const shouldCreateAiRulesFile = await abortIfCancelled(
clack.select({
message:
'Do you want to create a ./rules/sentryrules.md file with AI rule examples for Sentry?',
options: [
{
label: 'Yes',
value: true,
hint: 'Creates .rules/sentryrules.md in your project',
},
{
label: 'No',
value: false,
},
],
initialValue: true,
}),
);

Check warning on line 1145 in src/nextjs/nextjs-wizard.ts

View check run for this annotation

Codecov / codecov/patch

src/nextjs/nextjs-wizard.ts#L1126-L1145

Added lines #L1126 - L1145 were not covered by tests

span?.setAttribute('shouldCreateAiRulesFile', shouldCreateAiRulesFile);
Sentry.setTag('shouldCreateAiRulesFile', shouldCreateAiRulesFile);

Check warning on line 1148 in src/nextjs/nextjs-wizard.ts

View check run for this annotation

Codecov / codecov/patch

src/nextjs/nextjs-wizard.ts#L1147-L1148

Added lines #L1147 - L1148 were not covered by tests

return shouldCreateAiRulesFile;
});
}

Check warning on line 1152 in src/nextjs/nextjs-wizard.ts

View check run for this annotation

Codecov / codecov/patch

src/nextjs/nextjs-wizard.ts#L1150-L1152

Added lines #L1150 - L1152 were not covered by tests

/**
* Returns true or false depending on whether we think the user is using Turbopack. May return null in case we aren't sure.
*/
Expand Down
136 changes: 136 additions & 0 deletions src/nextjs/templates.ts
Original file line number Diff line number Diff line change
Expand Up @@ -711,3 +711,139 @@
)
}
`;

export function getAiRulesFileContent(): string {
return `These examples should be used as guidance when configuring Sentry functionality within a project.

Check warning on line 716 in src/nextjs/templates.ts

View check run for this annotation

Codecov / codecov/patch

src/nextjs/templates.ts#L716

Added line #L716 was not covered by tests
# Exception Catching
- Use \`Sentry.captureException(error)\` to capture an exception and log the error in Sentry.
- Use this in try catch blocks or areas where exceptions are expected
# Tracing Examples
- Spans should be created for meaningful actions within an applications like button clicks, API calls, and function calls
- Ensure you are creating custom spans with meaningful names and operations
- Span names should be parameterized. For example, when get a user by an id, name the span \`fetch /users/:id\` instead of \`fetch /users/1234\`
- Use the \`Sentry.startSpan\` function to create a span
- Child spans can exist within a parent span
## Custom Span instrumentation in component actions
- The \`name\` and \`op\` properties should be meaninful for the activities in the call.
- Attach attribute based on relevant information and metrics from the request
\`\`\`javascript
function TestComponent() {
const handleTestButtonClick = () => {
// Create a transaction/span to measure performance
Sentry.startSpan({
op: "ui.click",
name: "Test Button Click"
}, (span) => {
const value = "some config"
const metric = "some metric"
// Metrics can be added to the span
span.setAttribute("config", value)
span.setAttribute("metric", metric)
doSomething();
});
};
return (
<button
type="button"
onClick={handleTestButtonClick}
>
Test Sentry
</button>
);
}
\`\`\`
## Custom span instrumentation in API calls
- The \`name\` and \`op\` properties should be meaninful for the activities in the call.
- Attach attributes based on relevant information and metrics from the request
\`\`\`javascript
async function fetchUserData(userId) {
return Sentry.startSpan(
{
name: \'fetch /api/users/:userId',
},
async () => {
try {
const response = await fetch(\`/api/users/\${userId}\`);
const data = await response.json();
return data;
}
);
}
\`\`\`
# Logs
- Where logs are used, ensure Sentry is imported using \`import * as Sentry from "@sentry/nextjs"\`.
- Enable logging in Sentry using \`Sentry.init({ _experiments: { enableLogs: true } })\`
- Reference the logger using \`const { logger } = Sentry\`.
- Sentry offers a consoleLoggingIntegration that can be used to log specific console error types automatically without instrumenting the individual logger calls
## Configuration
- In NextJS the client side Sentry initialization is in \`instrumentation-client.ts\`, the server initialization is in \`sentry.edge.config.ts\` and the edge initialization is in \`sentry.server.config.ts\`
- Initialization does not need to be repeated in other files, it only needs to happen the files mentioned above. You should use \`import * as Sentry from "@sentry/nextjs"\` to reference Sentry functionality
### Baseline
\`\`\`javascript
import * as Sentry from "@sentry/nextjs";
Sentry.init({
dsn: "https://[email protected]/0",
_experiments: {
enableLogs: true,
},
});
\`\`\`
### Logger Integration
\`\`\`javascript
Sentry.init({
dsn: "https://[email protected]/0",
integrations: [
// send console.log, console.error, and console.warn calls as logs to Sentry
Sentry.consoleLoggingIntegration({ levels: ["log", "error", "warn"] }),
],
});
\`\`\`
## Logger Examples
\`\`\`javascript
logger.trace("Starting database connection", { database: "users" });
logger.debug("Cache miss for user", { userId: 123 });
logger.info("Updated profile", { profileId: 345 });
logger.warn("Rate limit reached for endpoint", {
endpoint: "/api/results/",
isEnterprise: false,
});
logger.error("Failed to process payment", {
orderId: "order_123",
amount: 99.99,
});
logger.fatal("Database connection pool exhausted", {
database: "users",
activeConnections: 100,
});
\`\`\`
`;
}

Check warning on line 849 in src/nextjs/templates.ts

View check run for this annotation

Codecov / codecov/patch

src/nextjs/templates.ts#L849

Added line #L849 was not covered by tests
Loading