diff --git a/README.md b/README.md
index 3fd75aa09..085d1adca 100644
--- a/README.md
+++ b/README.md
@@ -1314,7 +1314,7 @@ NEXT_PUBLIC_MONITORING_PROVIDER=sentry
To use Baselime, you need to set the following environment variables:
```bash
-BASELIME_KEY=your_key
+NEXT_PUBLIC_BASELIME_KEY=your_key
NEXT_PUBLIC_MONITORING_PROVIDER=baselime
```
diff --git a/apps/web/components/root-providers.tsx b/apps/web/components/root-providers.tsx
index 4b01ee6d5..223d83a1f 100644
--- a/apps/web/components/root-providers.tsx
+++ b/apps/web/components/root-providers.tsx
@@ -8,6 +8,7 @@ import { ThemeProvider } from 'next-themes';
import { CaptchaProvider } from '@kit/auth/captcha/client';
import { I18nProvider } from '@kit/i18n/provider';
+import { MonitoringProvider } from '@kit/monitoring/components';
import { AuthChangeListener } from '@kit/supabase/components/auth-change-listener';
import appConfig from '~/config/app.config';
@@ -42,26 +43,28 @@ export function RootProviders({
const i18nSettings = getI18nSettings(lang);
return (
-
-
-
-
-
+
+
+
+
+
+
-
-
- {children}
-
-
-
-
-
-
+
+
+ {children}
+
+
+
+
+
+
+
);
}
diff --git a/apps/web/middleware.ts b/apps/web/middleware.ts
index cb6e4da12..87298412e 100644
--- a/apps/web/middleware.ts
+++ b/apps/web/middleware.ts
@@ -21,6 +21,10 @@ export const config = {
export async function middleware(request: NextRequest) {
const response = NextResponse.next();
+ // set a unique request ID for each request
+ // this helps us log and trace requests
+ setRequestId(request);
+
// apply CSRF and session middleware
const csrfResponse = await withCsrfMiddleware(request, response);
@@ -109,6 +113,9 @@ async function adminMiddleware(request: NextRequest, response: NextResponse) {
return response;
}
+/**
+ * Define URL patterns and their corresponding handlers.
+ */
function getPatterns() {
return [
{
@@ -170,6 +177,10 @@ function getPatterns() {
];
}
+/**
+ * Match URL patterns to specific handlers.
+ * @param url
+ */
function matchUrlPattern(url: string) {
const patterns = getPatterns();
const input = url.split('?')[0];
@@ -182,3 +193,11 @@ function matchUrlPattern(url: string) {
}
}
}
+
+/**
+ * Set a unique request ID for each request.
+ * @param request
+ */
+function setRequestId(request: Request) {
+ request.headers.set('x-correlation-id', crypto.randomUUID());
+}
diff --git a/packages/monitoring/baselime/README.md b/packages/monitoring/baselime/README.md
index d7856fd4e..2e8c528ab 100644
--- a/packages/monitoring/baselime/README.md
+++ b/packages/monitoring/baselime/README.md
@@ -3,6 +3,6 @@
Please set the following environment variables:
```
-BASELIME_KEY=your_key
+NEXT_PUBLIC_BASELIME_KEY=your_key
NEXT_PUBLIC_MONITORING_PROVIDER=baselime
```
\ No newline at end of file
diff --git a/packages/monitoring/baselime/src/components/provider.tsx b/packages/monitoring/baselime/src/components/provider.tsx
index a78ff5d39..e35d14fd7 100644
--- a/packages/monitoring/baselime/src/components/provider.tsx
+++ b/packages/monitoring/baselime/src/components/provider.tsx
@@ -1,6 +1,6 @@
import { BaselimeRum } from '@baselime/react-rum';
-export function BaselineProvider({
+export function BaselimeProvider({
children,
apiKey,
enableWebVitals,
diff --git a/packages/monitoring/baselime/src/services/baselime-server-monitoring.service.ts b/packages/monitoring/baselime/src/services/baselime-server-monitoring.service.ts
index 9599bcfda..313b4d338 100644
--- a/packages/monitoring/baselime/src/services/baselime-server-monitoring.service.ts
+++ b/packages/monitoring/baselime/src/services/baselime-server-monitoring.service.ts
@@ -1,9 +1,75 @@
+import process from 'node:process';
+import { z } from 'zod';
+
import { MonitoringService } from '../../../src/services/monitoring.service';
+const apiKey = z
+ .string({
+ required_error: 'API_KEY is required',
+ })
+ .parse(process.env.BASELIME_API_KEY);
+
export class BaselimeServerMonitoringService implements MonitoringService {
- captureException(error: Error | null) {
- console.error(`Caught exception: ${JSON.stringify(error)}`);
+ userId: string | null = null;
+
+ async captureException(
+ error: Error | null,
+ extra?: {
+ requestId?: string;
+ sessionId?: string;
+ namespace?: string;
+ service?: string;
+ },
+ ) {
+ const formattedError = error ? getFormattedError(error) : {};
+
+ const event = {
+ level: 'error',
+ data: { error },
+ error: {
+ ...formattedError,
+ },
+ message: error ? `${error.name}: ${error.message}` : `Unknown error`,
+ };
+
+ const response = await fetch(`https://events.baselime.io/v1/web`, {
+ method: 'POST',
+ headers: {
+ contentType: 'application/json',
+ 'x-api-key': apiKey,
+ 'x-service': extra?.service ?? '',
+ 'x-namespace': extra?.namespace ?? '',
+ },
+ body: JSON.stringify([
+ {
+ userId: this.userId,
+ sessionId: extra?.sessionId,
+ namespace: extra?.namespace,
+ ...event,
+ },
+ ]),
+ });
+
+ if (!response.ok) {
+ console.error(
+ {
+ response,
+ event,
+ },
+ 'Failed to send event to Baselime',
+ );
+ }
}
- identifyUser(info: Info) {}
+ identifyUser(info: Info) {
+ this.userId = info.id;
+ }
+}
+
+function getFormattedError(error: Error) {
+ return {
+ name: error.name,
+ message: error.message,
+ stack: error.stack,
+ };
}
diff --git a/packages/monitoring/src/components/index.ts b/packages/monitoring/src/components/index.ts
index 39d945d40..dccefb102 100644
--- a/packages/monitoring/src/components/index.ts
+++ b/packages/monitoring/src/components/index.ts
@@ -1 +1,2 @@
export * from './error-boundary';
+export * from './provider';
diff --git a/packages/monitoring/src/components/provider.tsx b/packages/monitoring/src/components/provider.tsx
new file mode 100644
index 000000000..bc7fea910
--- /dev/null
+++ b/packages/monitoring/src/components/provider.tsx
@@ -0,0 +1,45 @@
+'use client';
+
+import { lazy } from 'react';
+
+import { getMonitoringProvider } from '../get-monitoring-provider';
+import { InstrumentationProvider } from '../monitoring-providers.enum';
+
+const BaselimeProvider = lazy(async () => {
+ const { BaselimeProvider } = await import('@kit/baselime/provider');
+
+ return {
+ default: BaselimeProvider,
+ };
+});
+
+type Config = {
+ provider: InstrumentationProvider;
+ providerToken: string;
+};
+
+export function MonitoringProvider(
+ props: React.PropsWithChildren<{ config?: Config }>,
+) {
+ const provider = getMonitoringProvider();
+
+ if (!props.config) {
+ return <>{props.children}>;
+ }
+
+ switch (provider) {
+ case InstrumentationProvider.Baselime:
+ return (
+
+ {props.children}
+
+ );
+
+ // sentry does not require a provider
+ case InstrumentationProvider.Sentry:
+ return <>{props.children}>;
+
+ default:
+ return <>{props.children}>;
+ }
+}
diff --git a/packages/monitoring/src/hooks/use-monitoring.ts b/packages/monitoring/src/hooks/use-monitoring.ts
index e660abc71..411d36be0 100644
--- a/packages/monitoring/src/hooks/use-monitoring.ts
+++ b/packages/monitoring/src/hooks/use-monitoring.ts
@@ -5,7 +5,7 @@ import { MonitoringService } from '../services/monitoring.service';
const MONITORING = getMonitoringProvider();
-let service: MonitoringService;
+let serviceFactory: () => MonitoringService;
/**
* @name useMonitoring
@@ -13,22 +13,20 @@ let service: MonitoringService;
* Use Suspense to suspend while loading the service.
*/
export function useMonitoring() {
- if (!service) {
+ if (!serviceFactory) {
throw withMonitoringService();
}
- console.log(service);
-
- return service;
+ return serviceFactory();
}
async function withMonitoringService() {
- service = await loadMonitoringService();
+ serviceFactory = await loadMonitoringService();
}
-async function loadMonitoringService() {
+async function loadMonitoringService(): Promise<() => MonitoringService> {
if (!MONITORING) {
- return new ConsoleMonitoringService();
+ return Promise.resolve(() => new ConsoleMonitoringService());
}
switch (MONITORING) {
@@ -45,7 +43,9 @@ async function loadMonitoringService() {
}
default: {
- throw new Error(`Unknown instrumentation provider: ${MONITORING}`);
+ throw new Error(
+ `Unknown instrumentation provider: ${MONITORING as string}`,
+ );
}
}
}
diff --git a/packages/monitoring/src/services/console-monitoring.service.ts b/packages/monitoring/src/services/console-monitoring.service.ts
index 854d44cf0..1b77c2026 100644
--- a/packages/monitoring/src/services/console-monitoring.service.ts
+++ b/packages/monitoring/src/services/console-monitoring.service.ts
@@ -6,6 +6,8 @@ export class ConsoleMonitoringService implements MonitoringService {
}
captureException(error: Error) {
- console.error(`Caught exception: ${JSON.stringify(error)}`);
+ console.error(
+ `[Console Monitoring] Caught exception: ${JSON.stringify(error)}`,
+ );
}
}