feat: integrate observability
This commit is contained in:
@@ -22,6 +22,9 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@mintel/next-utils": "workspace:*",
|
||||
"@mintel/observability": "workspace:*",
|
||||
"@mintel/next-observability": "workspace:*",
|
||||
"@sentry/nextjs": "^8.55.0",
|
||||
"next": "15.1.6",
|
||||
"next-intl": "^4.8.2",
|
||||
"react": "^19.0.0",
|
||||
|
||||
9
apps/sample-website/sentry.client.config.ts
Normal file
9
apps/sample-website/sentry.client.config.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { initSentry } from "@mintel/next-observability";
|
||||
|
||||
initSentry({
|
||||
// Use a placeholder DSN on the client if you want to bypass ad-blockers via tunnel
|
||||
// Or just use the real DSN if you don't care about ad-blockers for errors.
|
||||
// The Mintel standard is to use the relay.
|
||||
dsn: "https://public@errors.infra.mintel.me/1", // Placeholder for relay
|
||||
tunnel: "/errors/api/relay",
|
||||
});
|
||||
8
apps/sample-website/sentry.edge.config.ts
Normal file
8
apps/sample-website/sentry.edge.config.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import { initSentry } from "@mintel/next-observability";
|
||||
import { validateMintelEnv } from "@mintel/next-utils";
|
||||
|
||||
const env = validateMintelEnv();
|
||||
|
||||
initSentry({
|
||||
dsn: env.SENTRY_DSN,
|
||||
});
|
||||
8
apps/sample-website/sentry.server.config.ts
Normal file
8
apps/sample-website/sentry.server.config.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import { initSentry } from "@mintel/next-observability";
|
||||
import { validateMintelEnv } from "@mintel/next-utils";
|
||||
|
||||
const env = validateMintelEnv();
|
||||
|
||||
initSentry({
|
||||
dsn: env.SENTRY_DSN,
|
||||
});
|
||||
6
apps/sample-website/src/app/errors/api/relay/route.ts
Normal file
6
apps/sample-website/src/app/errors/api/relay/route.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { createSentryRelayHandler } from "@mintel/next-observability";
|
||||
import { validateMintelEnv } from "@mintel/next-utils";
|
||||
|
||||
export const POST = createSentryRelayHandler({
|
||||
dsn: validateMintelEnv().SENTRY_DSN,
|
||||
});
|
||||
@@ -1,5 +1,11 @@
|
||||
import type { Metadata } from "next";
|
||||
import { Suspense } from "react";
|
||||
import "./globals.css";
|
||||
import {
|
||||
AnalyticsContextProvider,
|
||||
AnalyticsAutoTracker,
|
||||
} from "@mintel/next-observability/client";
|
||||
import { getAnalyticsConfig } from "@/lib/observability";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Sample Website",
|
||||
@@ -11,9 +17,18 @@ export default function RootLayout({
|
||||
}: Readonly<{
|
||||
children: React.ReactNode;
|
||||
}>) {
|
||||
const analyticsConfig = getAnalyticsConfig();
|
||||
|
||||
return (
|
||||
<html lang="en">
|
||||
<body>{children}</body>
|
||||
<body>
|
||||
<AnalyticsContextProvider config={analyticsConfig}>
|
||||
<Suspense fallback={null}>
|
||||
<AnalyticsAutoTracker />
|
||||
</Suspense>
|
||||
{children}
|
||||
</AnalyticsContextProvider>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
|
||||
7
apps/sample-website/src/app/stats/api/send/route.ts
Normal file
7
apps/sample-website/src/app/stats/api/send/route.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { createUmamiProxyHandler } from "@mintel/next-observability";
|
||||
import { validateMintelEnv } from "@mintel/next-utils";
|
||||
|
||||
export const POST = createUmamiProxyHandler({
|
||||
websiteId: validateMintelEnv().UMAMI_WEBSITE_ID,
|
||||
apiEndpoint: validateMintelEnv().UMAMI_API_ENDPOINT,
|
||||
});
|
||||
13
apps/sample-website/src/instrumentation.ts
Normal file
13
apps/sample-website/src/instrumentation.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import * as Sentry from "@sentry/nextjs";
|
||||
|
||||
export async function register() {
|
||||
if (process.env.NEXT_RUNTIME === "nodejs") {
|
||||
await import("../sentry.server.config");
|
||||
}
|
||||
|
||||
if (process.env.NEXT_RUNTIME === "edge") {
|
||||
await import("../sentry.edge.config");
|
||||
}
|
||||
}
|
||||
|
||||
export const onRequestError = Sentry.captureRequestError;
|
||||
54
apps/sample-website/src/lib/observability.ts
Normal file
54
apps/sample-website/src/lib/observability.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
import {
|
||||
UmamiAnalyticsService,
|
||||
GotifyNotificationService,
|
||||
NoopNotificationService,
|
||||
} from "@mintel/observability";
|
||||
import { validateMintelEnv } from "@mintel/next-utils";
|
||||
|
||||
let analyticsService: any = null;
|
||||
let notificationService: any = null;
|
||||
|
||||
export function getAnalyticsConfig() {
|
||||
const isClient = typeof window !== "undefined";
|
||||
|
||||
if (isClient) {
|
||||
return {
|
||||
enabled: true,
|
||||
apiEndpoint: "/stats",
|
||||
};
|
||||
}
|
||||
|
||||
const env = validateMintelEnv();
|
||||
return {
|
||||
enabled: Boolean(env.UMAMI_WEBSITE_ID),
|
||||
websiteId: env.UMAMI_WEBSITE_ID,
|
||||
apiEndpoint: env.UMAMI_API_ENDPOINT,
|
||||
};
|
||||
}
|
||||
|
||||
export function getAnalyticsService() {
|
||||
if (analyticsService) return analyticsService;
|
||||
|
||||
const config = getAnalyticsConfig();
|
||||
analyticsService = new UmamiAnalyticsService(config);
|
||||
|
||||
return analyticsService;
|
||||
}
|
||||
|
||||
export function getNotificationService() {
|
||||
if (notificationService) return notificationService;
|
||||
|
||||
if (typeof window === "undefined") {
|
||||
const env = validateMintelEnv();
|
||||
notificationService = new GotifyNotificationService({
|
||||
enabled: Boolean(env.GOTIFY_URL && env.GOTIFY_TOKEN),
|
||||
url: env.GOTIFY_URL || "",
|
||||
token: env.GOTIFY_TOKEN || "",
|
||||
});
|
||||
} else {
|
||||
// Notifications are typically server-side only to protect tokens
|
||||
notificationService = new NoopNotificationService();
|
||||
}
|
||||
|
||||
return notificationService;
|
||||
}
|
||||
Reference in New Issue
Block a user