diff --git a/apps/web/.env.production b/apps/web/.env.production index e7f400b5c..13f173141 100644 --- a/apps/web/.env.production +++ b/apps/web/.env.production @@ -13,4 +13,4 @@ NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY= # MONITORING NEXT_PUBLIC_MONITORING_PROVIDER= -MONITORING_INSTRUMENTATION_ENABLED=false +ENABLE_MONITORING_INSTRUMENTATION=false diff --git a/apps/web/instrumentation.ts b/apps/web/instrumentation.ts index 9f751880c..a7cd2ce42 100644 --- a/apps/web/instrumentation.ts +++ b/apps/web/instrumentation.ts @@ -2,18 +2,12 @@ * This file is used to register monitoring instrumentation * for your Next.js application. */ - export async function register() { - // only run in nodejs runtime - if ( - process.env.NEXT_RUNTIME === 'nodejs' && - process.env.MONITORING_INSTRUMENTATION_ENABLED === 'true' - ) { - const { registerMonitoringInstrumentation } = await import( - '@kit/monitoring/instrumentation' - ); + const { registerMonitoringInstrumentation } = await import( + '@kit/monitoring/instrumentation' + ); - // Register monitoring instrumentation based on the MONITORING_PROVIDER environment variable. - return registerMonitoringInstrumentation(); - } + // Register monitoring instrumentation + // based on the MONITORING_PROVIDER environment variable. + await registerMonitoringInstrumentation(); } diff --git a/packages/monitoring/api/README.md b/packages/monitoring/api/README.md index c69428686..8481b7c32 100644 --- a/packages/monitoring/api/README.md +++ b/packages/monitoring/api/README.md @@ -4,7 +4,7 @@ Please set the following environment variable to your preferred monitoring provi ``` NEXT_PUBLIC_MONITORING_PROVIDER= -MONITORING_INSTRUMENTATION_ENABLED=true +ENABLE_MONITORING_INSTRUMENTATION=true ``` ## Available Providers @@ -32,8 +32,8 @@ NEXT_PUBLIC_MONITORING_PROVIDER=sentry ## Instrumentation -To enable instrumentation, set the `MONITORING_INSTRUMENTATION_ENABLED` environment variable to `true`. +To enable instrumentation, set the `ENABLE_MONITORING_INSTRUMENTATION` environment variable to `true`. ``` -MONITORING_INSTRUMENTATION_ENABLED=true +ENABLE_MONITORING_INSTRUMENTATION=true ``` \ No newline at end of file diff --git a/packages/monitoring/api/src/services/get-server-monitoring-service.ts b/packages/monitoring/api/src/services/get-server-monitoring-service.ts index bfc67ae4d..236e6d77c 100644 --- a/packages/monitoring/api/src/services/get-server-monitoring-service.ts +++ b/packages/monitoring/api/src/services/get-server-monitoring-service.ts @@ -28,11 +28,9 @@ export async function getServerMonitoringService() { } case InstrumentationProvider.Sentry: { - const { SentryServerMonitoringService } = await import( - '@kit/sentry/server' - ); + const { SentryMonitoringService } = await import('@kit/sentry'); - return new SentryServerMonitoringService(); + return new SentryMonitoringService(); } default: { diff --git a/packages/monitoring/baselime/src/instrumentation.ts b/packages/monitoring/baselime/src/instrumentation.ts index b570cc28a..8f9654276 100644 --- a/packages/monitoring/baselime/src/instrumentation.ts +++ b/packages/monitoring/baselime/src/instrumentation.ts @@ -5,6 +5,10 @@ * Please set the MONITORING_PROVIDER environment variable to 'baselime' to register Baselime instrumentation. */ export async function registerInstrumentation() { + if (!process.env.ENABLE_MONITORING_INSTRUMENTATION) { + return; + } + const serviceName = process.env.INSTRUMENTATION_SERVICE_NAME; if (!serviceName) { @@ -14,19 +18,20 @@ export async function registerInstrumentation() { `); } - const { BaselimeSDK, BetterHttpInstrumentation, VercelPlugin } = await import( - '@baselime/node-opentelemetry' - ); + if (process.env.NEXT_RUNTIME === 'nodejs') { + const { BaselimeSDK, BetterHttpInstrumentation, VercelPlugin } = + await import('@baselime/node-opentelemetry'); - const sdk = new BaselimeSDK({ - serverless: true, - service: serviceName, - instrumentations: [ - new BetterHttpInstrumentation({ - plugins: [new VercelPlugin()], - }), - ], - }); + const sdk = new BaselimeSDK({ + serverless: true, + service: serviceName, + instrumentations: [ + new BetterHttpInstrumentation({ + plugins: [new VercelPlugin()], + }), + ], + }); - sdk.start(); + sdk.start(); + } } diff --git a/packages/monitoring/sentry/package.json b/packages/monitoring/sentry/package.json index 25e531f50..c273ae9e1 100644 --- a/packages/monitoring/sentry/package.json +++ b/packages/monitoring/sentry/package.json @@ -11,12 +11,10 @@ "prettier": "@kit/prettier-config", "exports": { ".": "./src/index.ts", - "./server": "./src/server.ts", "./provider": "./src/components/provider.tsx", "./instrumentation": "./src/instrumentation.ts", - "./config/client": "./src/config/sentry.client.config.ts", - "./config/server": "./src/config/sentry.server.config.ts", - "./config/edge": "./src/config/sentry.edge.config.ts" + "./config/client": "./src/sentry.client.config.ts", + "./config/server": "./src/sentry.client.server.ts" }, "dependencies": { "@opentelemetry/exporter-jaeger": "1.24.1", diff --git a/packages/monitoring/sentry/src/components/provider.tsx b/packages/monitoring/sentry/src/components/provider.tsx index 1a71e3765..f5bb2c948 100644 --- a/packages/monitoring/sentry/src/components/provider.tsx +++ b/packages/monitoring/sentry/src/components/provider.tsx @@ -2,14 +2,14 @@ import { useRef } from 'react'; import { MonitoringContext } from '@kit/monitoring-core'; -import { SentryServerMonitoringService } from '../services/sentry-server-monitoring.service'; +import { SentryMonitoringService } from '../services/sentry-monitoring.service'; export function SentryProvider({ children }: React.PropsWithChildren) { return {children}; } function MonitoringProvider(props: React.PropsWithChildren) { - const service = useRef(new SentryServerMonitoringService()); + const service = useRef(new SentryMonitoringService()); return ( diff --git a/packages/monitoring/sentry/src/config/sentry.client.config.ts b/packages/monitoring/sentry/src/config/sentry.client.config.ts deleted file mode 100644 index c6ab3ca5f..000000000 --- a/packages/monitoring/sentry/src/config/sentry.client.config.ts +++ /dev/null @@ -1,23 +0,0 @@ -import * as Sentry from '@sentry/nextjs'; - -Sentry.init({ - dsn: process.env.NEXT_PUBLIC_SENTRY_DSN, - // Replay may only be enabled for the client-side - integrations: [Sentry.replayIntegration()], - - // Set tracesSampleRate to 1.0 to capture 100% - // of transactions for performance monitoring. - // We recommend adjusting this value in production - tracesSampleRate: 1.0, - - // Capture Replay for 10% of all sessions, - // plus for 100% of sessions with an error - replaysSessionSampleRate: 0.1, - replaysOnErrorSampleRate: 1.0, - - // ... - - // Note: if you want to override the automatic release value, do not set a - // `release` value here - use the environment variable `SENTRY_RELEASE`, so - // that it will also get attached to your source maps -}); diff --git a/packages/monitoring/sentry/src/config/sentry.edge.config.ts b/packages/monitoring/sentry/src/config/sentry.edge.config.ts deleted file mode 100644 index 8b74c1b56..000000000 --- a/packages/monitoring/sentry/src/config/sentry.edge.config.ts +++ /dev/null @@ -1,6 +0,0 @@ -import * as Sentry from '@sentry/nextjs'; - -Sentry.init({ - dsn: process.env.NEXT_PUBLIC_SENTRY_DSN, - tracesSampleRate: 1.0, -}); diff --git a/packages/monitoring/sentry/src/config/sentry.server.config.ts b/packages/monitoring/sentry/src/config/sentry.server.config.ts deleted file mode 100644 index 8b74c1b56..000000000 --- a/packages/monitoring/sentry/src/config/sentry.server.config.ts +++ /dev/null @@ -1,6 +0,0 @@ -import * as Sentry from '@sentry/nextjs'; - -Sentry.init({ - dsn: process.env.NEXT_PUBLIC_SENTRY_DSN, - tracesSampleRate: 1.0, -}); diff --git a/packages/monitoring/sentry/src/index.ts b/packages/monitoring/sentry/src/index.ts new file mode 100644 index 000000000..d4d9ce20c --- /dev/null +++ b/packages/monitoring/sentry/src/index.ts @@ -0,0 +1 @@ +export * from './services/sentry-monitoring.service'; diff --git a/packages/monitoring/sentry/src/instrumentation.ts b/packages/monitoring/sentry/src/instrumentation.ts index 3a15c301c..244668b9f 100644 --- a/packages/monitoring/sentry/src/instrumentation.ts +++ b/packages/monitoring/sentry/src/instrumentation.ts @@ -5,32 +5,46 @@ * Please set the MONITORING_PROVIDER environment variable to 'sentry' to register Sentry instrumentation. */ export async function registerInstrumentation() { - const serviceName = process.env.INSTRUMENTATION_SERVICE_NAME; + const { initializeSentryServerClient } = await import( + './sentry.server.config' + ); - if (!serviceName) { - throw new Error( - `You have set the Sentry instrumentation provider, but have not set the INSTRUMENTATION_SERVICE_NAME environment variable. Please set the INSTRUMENTATION_SERVICE_NAME environment variable.`, - ); + // initialize the Sentry client in the server + void initializeSentryServerClient(); + + if (!process.env.ENABLE_MONITORING_INSTRUMENTATION) { + return; } - const { Resource } = await import('@opentelemetry/resources'); - const { NodeSDK } = await import('@opentelemetry/sdk-node'); + // make sure the instrumentation is only run in a Node.js environment + if (process.env.NEXT_RUNTIME === 'nodejs') { + const serviceName = process.env.INSTRUMENTATION_SERVICE_NAME; - const { SEMRESATTRS_SERVICE_NAME } = await import( - '@opentelemetry/semantic-conventions' - ); + if (!serviceName) { + throw new Error( + `You have set the Sentry instrumentation provider, but have not set the INSTRUMENTATION_SERVICE_NAME environment variable. Please set the INSTRUMENTATION_SERVICE_NAME environment variable.`, + ); + } - const { SentrySpanProcessor, SentryPropagator } = await import( - '@sentry/opentelemetry-node' - ); + const { Resource } = await import('@opentelemetry/resources'); + const { NodeSDK } = await import('@opentelemetry/sdk-node'); - const sdk = new NodeSDK({ - resource: new Resource({ - [SEMRESATTRS_SERVICE_NAME]: serviceName, - }), - spanProcessors: [new SentrySpanProcessor()], - textMapPropagator: new SentryPropagator(), - }); + const { SEMRESATTRS_SERVICE_NAME } = await import( + '@opentelemetry/semantic-conventions' + ); - sdk.start(); + const { SentrySpanProcessor, SentryPropagator } = await import( + '@sentry/opentelemetry-node' + ); + + const sdk = new NodeSDK({ + resource: new Resource({ + [SEMRESATTRS_SERVICE_NAME]: serviceName, + }), + spanProcessors: [new SentrySpanProcessor()], + textMapPropagator: new SentryPropagator(), + }); + + sdk.start(); + } } diff --git a/packages/monitoring/sentry/src/sentry.client.config.ts b/packages/monitoring/sentry/src/sentry.client.config.ts new file mode 100644 index 000000000..195e3f100 --- /dev/null +++ b/packages/monitoring/sentry/src/sentry.client.config.ts @@ -0,0 +1,39 @@ +import * as Sentry from '@sentry/nextjs'; + +type Parameters unknown> = T extends ( + ...args: infer P +) => unknown + ? P + : never; + +/** + * @name initializeSentryBrowserClient + * @description Initialize the Sentry client + * @param props + */ +export function initializeSentryBrowserClient( + props: Parameters[0] = {}, +) { + return Sentry.init({ + dsn: process.env.NEXT_PUBLIC_SENTRY_DSN, + // Replay may only be enabled for the client-side + integrations: [Sentry.replayIntegration()], + + // Set tracesSampleRate to 1.0 to capture 100% + // of transactions for performance monitoring. + // We recommend adjusting this value in production + tracesSampleRate: props?.tracesSampleRate ?? 1.0, + + // Capture Replay for 10% of all sessions, + // plus for 100% of sessions with an error + replaysSessionSampleRate: 0.1, + replaysOnErrorSampleRate: 1.0, + + // ... + + // Note: if you want to override the automatic release value, do not set a + // `release` value here - use the environment variable `SENTRY_RELEASE`, so + // that it will also get attached to your source maps, + ...props, + }); +} diff --git a/packages/monitoring/sentry/src/sentry.server.config.ts b/packages/monitoring/sentry/src/sentry.server.config.ts new file mode 100644 index 000000000..7fb9f6aef --- /dev/null +++ b/packages/monitoring/sentry/src/sentry.server.config.ts @@ -0,0 +1,27 @@ +import * as Sentry from '@sentry/nextjs'; + +type Parameters unknown> = T extends ( + ...args: infer P +) => unknown + ? P + : never; + +/** + * @name initializeSentryServerClient + * @description Initialize the Sentry client in the server + * @param props + */ +export function initializeSentryServerClient( + props: Parameters[0] = {}, +) { + return Sentry.init({ + dsn: process.env.NEXT_PUBLIC_SENTRY_DSN, + + // ... + + // Note: if you want to override the automatic release value, do not set a + // `release` value here - use the environment variable `SENTRY_RELEASE`, so + // that it will also get attached to your source maps, + ...props, + }); +} diff --git a/packages/monitoring/sentry/src/server.ts b/packages/monitoring/sentry/src/server.ts deleted file mode 100644 index 400c7c280..000000000 --- a/packages/monitoring/sentry/src/server.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './services/sentry-server-monitoring.service'; diff --git a/packages/monitoring/sentry/src/services/sentry-server-monitoring.service.ts b/packages/monitoring/sentry/src/services/sentry-monitoring.service.ts similarity index 58% rename from packages/monitoring/sentry/src/services/sentry-server-monitoring.service.ts rename to packages/monitoring/sentry/src/services/sentry-monitoring.service.ts index bf09a69f3..cdb2f49f7 100644 --- a/packages/monitoring/sentry/src/services/sentry-server-monitoring.service.ts +++ b/packages/monitoring/sentry/src/services/sentry-monitoring.service.ts @@ -7,7 +7,11 @@ import { MonitoringService } from '@kit/monitoring-core'; * @implements {MonitoringService} * ServerSentryMonitoringService is responsible for capturing exceptions and identifying users using the Sentry monitoring service. */ -export class SentryServerMonitoringService implements MonitoringService { +export class SentryMonitoringService implements MonitoringService { + constructor() { + void this.initialize(); + } + captureException(error: Error | null) { return Sentry.captureException(error); } @@ -22,4 +26,18 @@ export class SentryServerMonitoringService implements MonitoringService { identifyUser(user: Sentry.User) { Sentry.setUser(user); } + + private initialize() { + return this.initializeIfBrowser(); + } + + private async initializeIfBrowser() { + if (typeof document !== 'undefined') { + const { initializeSentryBrowserClient } = await import( + '../sentry.client.config' + ); + + initializeSentryBrowserClient(); + } + } }