Skip to content

Commit 97c1be9

Browse files
committed
feat(nextjs): Add Sentry.logger examples to example pages when logs feature is enabled
1 parent 7f75464 commit 97c1be9

File tree

4 files changed

+174
-32
lines changed

4 files changed

+174
-32
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# Changelog
22

3+
## Unreleased
4+
5+
### Features
6+
7+
- feat(nextjs): Add Sentry.logger examples to example pages when logs feature is enabled
8+
39
## 6.8.0
410

511
### Features

src/nextjs/nextjs-wizard.ts

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -142,10 +142,10 @@ export async function runNextjsWizardWithTelemetry(
142142
({ selectedProject, authToken, selfHosted, sentryUrl } = projectData);
143143
}
144144

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

148-
await createOrMergeNextJsFiles(
148+
return await createOrMergeNextJsFiles(
149149
selectedProject,
150150
selfHosted,
151151
sentryUrl,
@@ -385,6 +385,7 @@ export async function runNextjsWizardWithTelemetry(
385385
selectedProject,
386386
sentryUrl,
387387
typeScriptDetected,
388+
logsEnabled,
388389
),
389390
);
390391
}
@@ -463,7 +464,7 @@ async function createOrMergeNextJsFiles(
463464
sentryUrl: string,
464465
sdkConfigOptions: SDKConfigOptions,
465466
spotlight = false,
466-
) {
467+
): Promise<{ logsEnabled: boolean }> {
467468
const dsn = selectedProject.keys[0].dsn.public;
468469
const selectedFeatures = await featureSelectionPrompt([
469470
{
@@ -968,6 +969,8 @@ async function createOrMergeNextJsFiles(
968969
}
969970
}
970971
});
972+
973+
return { logsEnabled: selectedFeatures.logs };
971974
}
972975

973976
function hasDirectoryPathFromRoot(dirnameOrDirs: string | string[]): boolean {
@@ -983,6 +986,7 @@ async function createExamplePage(
983986
selectedProject: SentryProjectData,
984987
sentryUrl: string,
985988
typeScriptDetected: boolean,
989+
logsEnabled: boolean,
986990
): Promise<void> {
987991
const hasSrcDirectory = hasDirectoryPathFromRoot('src');
988992
const hasRootAppDirectory = hasDirectoryPathFromRoot('app');
@@ -1052,6 +1056,7 @@ async function createExamplePage(
10521056
sentryUrl,
10531057
useClient: true,
10541058
isTypeScript: typeScriptDetected,
1059+
logsEnabled,
10551060
});
10561061

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

10811086
await fs.promises.writeFile(
10821087
path.join(appFolderPath, 'api', 'sentry-example-api', newRouteFileName),
1083-
getSentryExampleAppDirApiRoute({ isTypeScript: typeScriptDetected }),
1088+
getSentryExampleAppDirApiRoute({
1089+
isTypeScript: typeScriptDetected,
1090+
logsEnabled,
1091+
}),
10841092
{ encoding: 'utf8', flag: 'w' },
10851093
);
10861094

@@ -1102,6 +1110,7 @@ async function createExamplePage(
11021110
sentryUrl,
11031111
useClient: false,
11041112
isTypeScript: typeScriptDetected,
1113+
logsEnabled,
11051114
});
11061115

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

11311140
await fs.promises.writeFile(
11321141
path.join(process.cwd(), ...pagesFolderLocation, 'api', apiRouteFileName),
1133-
getSentryExamplePagesDirApiRoute({ isTypeScript: typeScriptDetected }),
1142+
getSentryExamplePagesDirApiRoute({
1143+
isTypeScript: typeScriptDetected,
1144+
logsEnabled,
1145+
}),
11341146
{ encoding: 'utf8', flag: 'w' },
11351147
);
11361148

src/nextjs/templates.ts

Lines changed: 57 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -261,17 +261,34 @@ export function getSentryExamplePageContents(options: {
261261
projectId: string;
262262
useClient: boolean;
263263
isTypeScript?: boolean;
264+
logsEnabled?: boolean;
264265
}): string {
265266
const issuesPageLink = options.selfHosted
266267
? `${options.sentryUrl}organizations/${options.orgSlug}/issues/?project=${options.projectId}`
267268
: `https://${options.orgSlug}.sentry.io/issues/?project=${options.projectId}`;
268269

270+
const loggerDeclaration = options.logsEnabled
271+
? `
272+
const { logger } = Sentry;
273+
`
274+
: '';
275+
276+
const loggerPageLoad = options.logsEnabled
277+
? `
278+
logger.info("Sentry example page loaded");`
279+
: '';
280+
281+
const loggerUserAction = options.logsEnabled
282+
? `
283+
logger.info("User clicked the button, throwing a sample error");`
284+
: '';
285+
269286
return `${
270287
options.useClient ? '"use client";\n\n' : ''
271288
}import Head from "next/head";
272289
import * as Sentry from "@sentry/nextjs";
273290
import { useState, useEffect } from "react";
274-
291+
${loggerDeclaration}
275292
class SentryExampleFrontendError extends Error {
276293
constructor(message${options.isTypeScript ? ': string | undefined' : ''}) {
277294
super(message);
@@ -282,8 +299,8 @@ class SentryExampleFrontendError extends Error {
282299
export default function Page() {
283300
const [hasSentError, setHasSentError] = useState(false);
284301
const [isConnected, setIsConnected] = useState(true);
285-
286-
useEffect(() => {
302+
303+
useEffect(() => {${loggerPageLoad}
287304
async function checkConnectivity() {
288305
const result = await Sentry.diagnoseSdkConnectivity();
289306
setIsConnected(result !== 'sentry-unreachable');
@@ -315,7 +332,7 @@ export default function Page() {
315332
316333
<button
317334
type="button"
318-
onClick={async () => {
335+
onClick={async () => {${loggerUserAction}
319336
await Sentry.startSpan({
320337
name: 'Example Frontend/Backend Span',
321338
op: 'test'
@@ -347,7 +364,7 @@ export default function Page() {
347364
)}
348365
349366
<div className="flex-spacer" />
350-
367+
351368
</main>
352369
353370
<style>{\`
@@ -418,7 +435,7 @@ export default function Page() {
418435
&:disabled {
419436
cursor: not-allowed;
420437
opacity: 0.6;
421-
438+
422439
& > span {
423440
transform: translateY(0);
424441
border: none
@@ -466,7 +483,7 @@ export default function Page() {
466483
text-align: center;
467484
margin: 0;
468485
}
469-
486+
470487
.connectivity-error a {
471488
color: #FFFFFF;
472489
text-decoration: underline;
@@ -480,10 +497,25 @@ export default function Page() {
480497

481498
export function getSentryExamplePagesDirApiRoute({
482499
isTypeScript,
500+
logsEnabled,
483501
}: {
484502
isTypeScript: boolean;
503+
logsEnabled?: boolean;
485504
}) {
486-
return `// Custom error class for Sentry testing
505+
const sentryImport = logsEnabled
506+
? `import * as Sentry from "@sentry/nextjs";
507+
508+
const { logger } = Sentry;
509+
510+
`
511+
: '';
512+
513+
const loggerCall = logsEnabled
514+
? ` logger.info("Sentry example API called");
515+
`
516+
: '';
517+
518+
return `${sentryImport}// Custom error class for Sentry testing
487519
class SentryExampleAPIError extends Error {
488520
constructor(message${isTypeScript ? ': string | undefined' : ''}) {
489521
super(message);
@@ -492,19 +524,33 @@ class SentryExampleAPIError extends Error {
492524
}
493525
// A faulty API route to test Sentry's error monitoring
494526
export default function handler(_req, res) {
495-
throw new SentryExampleAPIError("This error is raised on the backend called by the example page.");
527+
${loggerCall}throw new SentryExampleAPIError("This error is raised on the backend called by the example page.");
496528
res.status(200).json({ name: "John Doe" });
497529
}
498530
`;
499531
}
500532

501533
export function getSentryExampleAppDirApiRoute({
502534
isTypeScript,
535+
logsEnabled,
503536
}: {
504537
isTypeScript: boolean;
538+
logsEnabled?: boolean;
505539
}) {
506-
return `import { NextResponse } from "next/server";
540+
const sentryImport = logsEnabled
541+
? `import * as Sentry from "@sentry/nextjs";
542+
543+
const { logger } = Sentry;
544+
`
545+
: '';
507546

547+
const loggerCall = logsEnabled
548+
? `
549+
logger.info("Sentry example API called");`
550+
: '';
551+
552+
return `import { NextResponse } from "next/server";
553+
${sentryImport}
508554
export const dynamic = "force-dynamic";
509555
class SentryExampleAPIError extends Error {
510556
constructor(message${isTypeScript ? ': string | undefined' : ''}) {
@@ -513,7 +559,7 @@ class SentryExampleAPIError extends Error {
513559
}
514560
}
515561
// A faulty API route to test Sentry's error monitoring
516-
export function GET() {
562+
export function GET() {${loggerCall}
517563
throw new SentryExampleAPIError("This error is raised on the backend called by the example page.");
518564
return NextResponse.json({ data: "Testing Sentry Error..." });
519565
}

test/nextjs/templates.test.ts

Lines changed: 94 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -628,21 +628,21 @@ describe('Next.js code templates', () => {
628628
const template = getGenerateMetadataSnippet(false);
629629

630630
expect(template).toMatchInlineSnapshot(`
631-
"
632-
import * as Sentry from '@sentry/nextjs';
633-
634-
635-
// Add or edit your "generateMetadata" to include the Sentry trace data:
636-
export function generateMetadata() {
637-
return {
638-
// ... your existing metadata
639-
other: {
640-
...Sentry.getTraceData()
641-
}
642-
};
643-
}
644-
"
645-
`);
631+
"
632+
import * as Sentry from '@sentry/nextjs';
633+
634+
635+
// Add or edit your "generateMetadata" to include the Sentry trace data:
636+
export function generateMetadata() {
637+
return {
638+
// ... your existing metadata
639+
other: {
640+
...Sentry.getTraceData()
641+
}
642+
};
643+
}
644+
"
645+
`);
646646
});
647647
});
648648

@@ -685,7 +685,7 @@ describe('Next.js code templates', () => {
685685
"// This file was generated by the Sentry wizard because we couldn't find a root layout file.
686686
import * as Sentry from '@sentry/nextjs';
687687
688-
688+
689689
export function generateMetadata() {
690690
return {
691691
other: {
@@ -758,6 +758,39 @@ describe('Next.js code templates', () => {
758758
'https://my-org.sentry.io/issues/?project=123',
759759
);
760760
});
761+
762+
it('generates example page with logger calls when logsEnabled is true', () => {
763+
const template = getSentryExamplePageContents({
764+
selfHosted: false,
765+
sentryUrl: 'https://sentry.io',
766+
orgSlug: 'my-org',
767+
projectId: '123',
768+
useClient: true,
769+
isTypeScript: true,
770+
logsEnabled: true,
771+
});
772+
773+
expect(template).toContain('const { logger } = Sentry;');
774+
expect(template).toContain('logger.info("Sentry example page loaded")');
775+
expect(template).toContain(
776+
'logger.info("User clicked the button, throwing a sample error")',
777+
);
778+
});
779+
780+
it('generates example page without logger calls when logsEnabled is false', () => {
781+
const template = getSentryExamplePageContents({
782+
selfHosted: false,
783+
sentryUrl: 'https://sentry.io',
784+
orgSlug: 'my-org',
785+
projectId: '123',
786+
useClient: true,
787+
isTypeScript: true,
788+
logsEnabled: false,
789+
});
790+
791+
expect(template).not.toContain('const { logger } = Sentry;');
792+
expect(template).not.toContain('logger.info');
793+
});
761794
});
762795

763796
describe('getSentryExamplePagesDirApiRoute', () => {
@@ -780,6 +813,30 @@ describe('Next.js code templates', () => {
780813
expect(template).toContain('class SentryExampleAPIError extends Error');
781814
expect(template).toContain('export default function handler(_req, res)');
782815
});
816+
817+
it('generates Pages Router API route with logger calls when logsEnabled is true', () => {
818+
const template = getSentryExamplePagesDirApiRoute({
819+
isTypeScript: true,
820+
logsEnabled: true,
821+
});
822+
823+
expect(template).toContain('import * as Sentry from "@sentry/nextjs";');
824+
expect(template).toContain('const { logger } = Sentry;');
825+
expect(template).toContain('logger.info("Sentry example API called")');
826+
});
827+
828+
it('generates Pages Router API route without logger calls when logsEnabled is false', () => {
829+
const template = getSentryExamplePagesDirApiRoute({
830+
isTypeScript: true,
831+
logsEnabled: false,
832+
});
833+
834+
expect(template).not.toContain(
835+
'import * as Sentry from "@sentry/nextjs"',
836+
);
837+
expect(template).not.toContain('const { logger } = Sentry;');
838+
expect(template).not.toContain('logger.info');
839+
});
783840
});
784841

785842
describe('getSentryExampleAppDirApiRoute', () => {
@@ -804,5 +861,26 @@ describe('Next.js code templates', () => {
804861
expect(template).toContain('export function GET()');
805862
expect(template).toContain('export const dynamic = "force-dynamic";');
806863
});
864+
865+
it('generates App Router API route with logger calls when logsEnabled is true', () => {
866+
const template = getSentryExampleAppDirApiRoute({
867+
isTypeScript: true,
868+
logsEnabled: true,
869+
});
870+
871+
expect(template).toContain('import * as Sentry from "@sentry/nextjs";');
872+
expect(template).toContain('const { logger } = Sentry;');
873+
expect(template).toContain('logger.info("Sentry example API called")');
874+
});
875+
876+
it('generates App Router API route without logger calls when logsEnabled is false', () => {
877+
const template = getSentryExampleAppDirApiRoute({
878+
isTypeScript: true,
879+
logsEnabled: false,
880+
});
881+
882+
expect(template).not.toContain('const { logger } = Sentry;');
883+
expect(template).not.toContain('logger.info');
884+
});
807885
});
808886
});

0 commit comments

Comments
 (0)