Skip to content
Open
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
@@ -1,5 +1,11 @@
# Changelog

## Unreleased

### Features

- Add Sentry.logger examples to example pages when logs are enabled ([#1127](https://github.com/getsentry/sentry-wizard/pull/1127))

## 6.8.0

### Features
Expand Down
22 changes: 17 additions & 5 deletions src/nextjs/nextjs-wizard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,10 +142,10 @@ export async function runNextjsWizardWithTelemetry(
({ selectedProject, authToken, selfHosted, sentryUrl } = projectData);
}

await traceStep('configure-sdk', async () => {
const { logsEnabled } = await traceStep('configure-sdk', async () => {
const tunnelRoute = await askShouldSetTunnelRoute();

await createOrMergeNextJsFiles(
return await createOrMergeNextJsFiles(
selectedProject,
selfHosted,
sentryUrl,
Expand Down Expand Up @@ -385,6 +385,7 @@ export async function runNextjsWizardWithTelemetry(
selectedProject,
sentryUrl,
typeScriptDetected,
logsEnabled,
),
);
}
Expand Down Expand Up @@ -463,7 +464,7 @@ async function createOrMergeNextJsFiles(
sentryUrl: string,
sdkConfigOptions: SDKConfigOptions,
spotlight = false,
) {
): Promise<{ logsEnabled: boolean }> {
const dsn = selectedProject.keys[0].dsn.public;
const selectedFeatures = await featureSelectionPrompt([
{
Expand Down Expand Up @@ -968,6 +969,8 @@ async function createOrMergeNextJsFiles(
}
}
});

return { logsEnabled: selectedFeatures.logs };
}

function hasDirectoryPathFromRoot(dirnameOrDirs: string | string[]): boolean {
Expand All @@ -983,6 +986,7 @@ async function createExamplePage(
selectedProject: SentryProjectData,
sentryUrl: string,
typeScriptDetected: boolean,
logsEnabled: boolean,
): Promise<void> {
const hasSrcDirectory = hasDirectoryPathFromRoot('src');
const hasRootAppDirectory = hasDirectoryPathFromRoot('app');
Expand Down Expand Up @@ -1052,6 +1056,7 @@ async function createExamplePage(
sentryUrl,
useClient: true,
isTypeScript: typeScriptDetected,
logsEnabled,
});

fs.mkdirSync(path.join(appFolderPath, 'sentry-example-page'), {
Expand Down Expand Up @@ -1080,7 +1085,10 @@ async function createExamplePage(

await fs.promises.writeFile(
path.join(appFolderPath, 'api', 'sentry-example-api', newRouteFileName),
getSentryExampleAppDirApiRoute({ isTypeScript: typeScriptDetected }),
getSentryExampleAppDirApiRoute({
isTypeScript: typeScriptDetected,
logsEnabled,
}),
{ encoding: 'utf8', flag: 'w' },
);

Expand All @@ -1102,6 +1110,7 @@ async function createExamplePage(
sentryUrl,
useClient: false,
isTypeScript: typeScriptDetected,
logsEnabled,
});

const examplePageFileName = `sentry-example-page.${
Expand Down Expand Up @@ -1130,7 +1139,10 @@ async function createExamplePage(

await fs.promises.writeFile(
path.join(process.cwd(), ...pagesFolderLocation, 'api', apiRouteFileName),
getSentryExamplePagesDirApiRoute({ isTypeScript: typeScriptDetected }),
getSentryExamplePagesDirApiRoute({
isTypeScript: typeScriptDetected,
logsEnabled,
}),
{ encoding: 'utf8', flag: 'w' },
);

Expand Down
68 changes: 57 additions & 11 deletions src/nextjs/templates.ts
Original file line number Diff line number Diff line change
Expand Up @@ -261,17 +261,34 @@ export function getSentryExamplePageContents(options: {
projectId: string;
useClient: boolean;
isTypeScript?: boolean;
logsEnabled?: boolean;
}): string {
const issuesPageLink = options.selfHosted
? `${options.sentryUrl}organizations/${options.orgSlug}/issues/?project=${options.projectId}`
: `https://${options.orgSlug}.sentry.io/issues/?project=${options.projectId}`;

const loggerDeclaration = options.logsEnabled
? `
const { logger } = Sentry;
`
: '';

const loggerPageLoad = options.logsEnabled
? `
logger.info("Sentry example page loaded");`
: '';

const loggerUserAction = options.logsEnabled
? `
logger.info("User clicked the button, throwing a sample error");`
: '';

return `${
options.useClient ? '"use client";\n\n' : ''
}import Head from "next/head";
import * as Sentry from "@sentry/nextjs";
import { useState, useEffect } from "react";

${loggerDeclaration}
class SentryExampleFrontendError extends Error {
constructor(message${options.isTypeScript ? ': string | undefined' : ''}) {
super(message);
Expand All @@ -282,8 +299,8 @@ class SentryExampleFrontendError extends Error {
export default function Page() {
const [hasSentError, setHasSentError] = useState(false);
const [isConnected, setIsConnected] = useState(true);
useEffect(() => {

useEffect(() => {${loggerPageLoad}
async function checkConnectivity() {
const result = await Sentry.diagnoseSdkConnectivity();
setIsConnected(result !== 'sentry-unreachable');
Expand Down Expand Up @@ -315,7 +332,7 @@ export default function Page() {

<button
type="button"
onClick={async () => {
onClick={async () => {${loggerUserAction}
await Sentry.startSpan({
name: 'Example Frontend/Backend Span',
op: 'test'
Expand Down Expand Up @@ -347,7 +364,7 @@ export default function Page() {
)}

<div className="flex-spacer" />

</main>

<style>{\`
Expand Down Expand Up @@ -418,7 +435,7 @@ export default function Page() {
&:disabled {
cursor: not-allowed;
opacity: 0.6;

& > span {
transform: translateY(0);
border: none
Expand Down Expand Up @@ -466,7 +483,7 @@ export default function Page() {
text-align: center;
margin: 0;
}

.connectivity-error a {
color: #FFFFFF;
text-decoration: underline;
Expand All @@ -480,10 +497,25 @@ export default function Page() {

export function getSentryExamplePagesDirApiRoute({
isTypeScript,
logsEnabled,
}: {
isTypeScript: boolean;
logsEnabled?: boolean;
}) {
return `// Custom error class for Sentry testing
const sentryImport = logsEnabled
? `import * as Sentry from "@sentry/nextjs";

const { logger } = Sentry;

`
: '';

const loggerCall = logsEnabled
? ` logger.info("Sentry example API called");
`
: '';

return `${sentryImport}// Custom error class for Sentry testing
class SentryExampleAPIError extends Error {
constructor(message${isTypeScript ? ': string | undefined' : ''}) {
super(message);
Expand All @@ -492,19 +524,33 @@ class SentryExampleAPIError extends Error {
}
// A faulty API route to test Sentry's error monitoring
export default function handler(_req, res) {
throw new SentryExampleAPIError("This error is raised on the backend called by the example page.");
${loggerCall}throw new SentryExampleAPIError("This error is raised on the backend called by the example page.");
res.status(200).json({ name: "John Doe" });
}
`;
}

export function getSentryExampleAppDirApiRoute({
isTypeScript,
logsEnabled,
}: {
isTypeScript: boolean;
logsEnabled?: boolean;
}) {
return `import { NextResponse } from "next/server";
const sentryImport = logsEnabled
? `import * as Sentry from "@sentry/nextjs";

const { logger } = Sentry;
`
: '';

const loggerCall = logsEnabled
? `
logger.info("Sentry example API called");`
: '';

return `import { NextResponse } from "next/server";
${sentryImport}
export const dynamic = "force-dynamic";
class SentryExampleAPIError extends Error {
constructor(message${isTypeScript ? ': string | undefined' : ''}) {
Expand All @@ -513,7 +559,7 @@ class SentryExampleAPIError extends Error {
}
}
// A faulty API route to test Sentry's error monitoring
export function GET() {
export function GET() {${loggerCall}
throw new SentryExampleAPIError("This error is raised on the backend called by the example page.");
return NextResponse.json({ data: "Testing Sentry Error..." });
}
Expand Down
110 changes: 94 additions & 16 deletions test/nextjs/templates.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -628,21 +628,21 @@ describe('Next.js code templates', () => {
const template = getGenerateMetadataSnippet(false);

expect(template).toMatchInlineSnapshot(`
"
import * as Sentry from '@sentry/nextjs';


// Add or edit your "generateMetadata" to include the Sentry trace data:
export function generateMetadata() {
return {
// ... your existing metadata
other: {
...Sentry.getTraceData()
}
};
}
"
`);
"
import * as Sentry from '@sentry/nextjs';

// Add or edit your "generateMetadata" to include the Sentry trace data:
export function generateMetadata() {
return {
// ... your existing metadata
other: {
...Sentry.getTraceData()
}
};
}
"
`);
});
});

Expand Down Expand Up @@ -685,7 +685,7 @@ describe('Next.js code templates', () => {
"// This file was generated by the Sentry wizard because we couldn't find a root layout file.
import * as Sentry from '@sentry/nextjs';


export function generateMetadata() {
return {
other: {
Expand Down Expand Up @@ -758,6 +758,39 @@ describe('Next.js code templates', () => {
'https://my-org.sentry.io/issues/?project=123',
);
});

it('generates example page with logger calls when logsEnabled is true', () => {
const template = getSentryExamplePageContents({
selfHosted: false,
sentryUrl: 'https://sentry.io',
orgSlug: 'my-org',
projectId: '123',
useClient: true,
isTypeScript: true,
logsEnabled: true,
});

expect(template).toContain('const { logger } = Sentry;');
expect(template).toContain('logger.info("Sentry example page loaded")');
expect(template).toContain(
'logger.info("User clicked the button, throwing a sample error")',
);
});

it('generates example page without logger calls when logsEnabled is false', () => {
const template = getSentryExamplePageContents({
selfHosted: false,
sentryUrl: 'https://sentry.io',
orgSlug: 'my-org',
projectId: '123',
useClient: true,
isTypeScript: true,
logsEnabled: false,
});

expect(template).not.toContain('const { logger } = Sentry;');
expect(template).not.toContain('logger.info');
});
});

describe('getSentryExamplePagesDirApiRoute', () => {
Expand All @@ -780,6 +813,30 @@ describe('Next.js code templates', () => {
expect(template).toContain('class SentryExampleAPIError extends Error');
expect(template).toContain('export default function handler(_req, res)');
});

it('generates Pages Router API route with logger calls when logsEnabled is true', () => {
const template = getSentryExamplePagesDirApiRoute({
isTypeScript: true,
logsEnabled: true,
});

expect(template).toContain('import * as Sentry from "@sentry/nextjs";');
expect(template).toContain('const { logger } = Sentry;');
expect(template).toContain('logger.info("Sentry example API called")');
});

it('generates Pages Router API route without logger calls when logsEnabled is false', () => {
const template = getSentryExamplePagesDirApiRoute({
isTypeScript: true,
logsEnabled: false,
});

expect(template).not.toContain(
'import * as Sentry from "@sentry/nextjs"',
);
expect(template).not.toContain('const { logger } = Sentry;');
expect(template).not.toContain('logger.info');
});
});

describe('getSentryExampleAppDirApiRoute', () => {
Expand All @@ -804,5 +861,26 @@ describe('Next.js code templates', () => {
expect(template).toContain('export function GET()');
expect(template).toContain('export const dynamic = "force-dynamic";');
});

it('generates App Router API route with logger calls when logsEnabled is true', () => {
const template = getSentryExampleAppDirApiRoute({
isTypeScript: true,
logsEnabled: true,
});

expect(template).toContain('import * as Sentry from "@sentry/nextjs";');
expect(template).toContain('const { logger } = Sentry;');
expect(template).toContain('logger.info("Sentry example API called")');
});

it('generates App Router API route without logger calls when logsEnabled is false', () => {
const template = getSentryExampleAppDirApiRoute({
isTypeScript: true,
logsEnabled: false,
});

expect(template).not.toContain('const { logger } = Sentry;');
expect(template).not.toContain('logger.info');
});
});
});
Loading