Refactor monitoring services into separate packages

Separated and isolated the responsibilities of monitoring tools. Reorganized the code by introducing a core package that contains common code related to monitoring and moved all the service operations like error capturing and identification of users into their respective packages. This ensures each tool is independent and easy to maintain.
This commit is contained in:
giancarlo
2024-04-24 09:02:02 +07:00
parent a004cbae63
commit 34d9034e65
31 changed files with 210 additions and 96 deletions

View File

@@ -4,9 +4,9 @@
"sideEffects": false, "sideEffects": false,
"version": "0.1.0", "version": "0.1.0",
"scripts": { "scripts": {
"clean": "git clean -xdf .turbo node_modules", "clean": "git clean -xdf ../.turbo node_modules",
"format": "prettier --check \"**/*.{ts,tsx}\"", "format": "prettier --check \"**/*.{ts,tsx}\"",
"lint": "eslint .", "lint": "eslint ..",
"typecheck": "tsc --noEmit" "typecheck": "tsc --noEmit"
}, },
"prettier": "@kit/prettier-config", "prettier": "@kit/prettier-config",
@@ -17,6 +17,7 @@
"./components": "./src/components/index.ts" "./components": "./src/components/index.ts"
}, },
"devDependencies": { "devDependencies": {
"@kit/monitoring-core": "workspace:*",
"@kit/baselime": "workspace:*", "@kit/baselime": "workspace:*",
"@kit/eslint-config": "workspace:*", "@kit/eslint-config": "workspace:*",
"@kit/prettier-config": "workspace:*", "@kit/prettier-config": "workspace:*",

View File

@@ -13,6 +13,14 @@ const BaselimeProvider = lazy(async () => {
}; };
}); });
const SentryProvider = lazy(async () => {
const { SentryProvider } = await import('@kit/sentry/provider');
return {
default: SentryProvider,
};
});
type Config = { type Config = {
provider: InstrumentationProvider; provider: InstrumentationProvider;
providerToken: string; providerToken: string;
@@ -35,9 +43,8 @@ export function MonitoringProvider(
</BaselimeProvider> </BaselimeProvider>
); );
// sentry does not require a provider
case InstrumentationProvider.Sentry: case InstrumentationProvider.Sentry:
return <>{props.children}</>; return <SentryProvider>{props.children}</SentryProvider>;
default: default:
return <>{props.children}</>; return <>{props.children}</>;

View File

@@ -0,0 +1,12 @@
import { useContext } from 'react';
import { MonitoringContext } from '@kit/monitoring-core';
/**
* @name useMonitoring
* @description Asynchronously load the monitoring service based on the MONITORING_PROVIDER environment variable.
* Use Suspense to suspend while loading the service.
*/
export function useMonitoring() {
return useContext(MonitoringContext);
}

View File

@@ -1,6 +1,7 @@
import { ConsoleMonitoringService } from '@kit/monitoring-core';
import { getMonitoringProvider } from '../get-monitoring-provider'; import { getMonitoringProvider } from '../get-monitoring-provider';
import { InstrumentationProvider } from '../monitoring-providers.enum'; import { InstrumentationProvider } from '../monitoring-providers.enum';
import { ConsoleMonitoringService } from './console-monitoring.service';
const MONITORING_PROVIDER = getMonitoringProvider(); const MONITORING_PROVIDER = getMonitoringProvider();

View File

@@ -18,16 +18,22 @@
"dependencies": { "dependencies": {
"@baselime/node-opentelemetry": "^0.5.8", "@baselime/node-opentelemetry": "^0.5.8",
"@baselime/react-rum": "^0.2.9", "@baselime/react-rum": "^0.2.9",
"@kit/monitoring-core": "workspace:*",
"@tanstack/react-table": "^8.16.0", "@tanstack/react-table": "^8.16.0",
"next": "14.3.0-canary.7", "next": "14.3.0-canary.7",
"tailwind-merge": "^2.3.0", "tailwind-merge": "^2.3.0",
"zod": "^3.23.0" "zod": "^3.23.0"
}, },
"devDependencies": { "devDependencies": {
"@types/react": "^18.2.79",
"@kit/eslint-config": "workspace:*", "@kit/eslint-config": "workspace:*",
"@kit/prettier-config": "workspace:*", "@kit/prettier-config": "workspace:*",
"@kit/tailwind-config": "workspace:*", "@kit/tailwind-config": "workspace:*",
"@kit/tsconfig": "workspace:*" "@kit/tsconfig": "workspace:*",
"react": "18.2.0"
},
"peerDependencies": {
"react": "18.2.0"
}, },
"eslintConfig": { "eslintConfig": {
"root": true, "root": true,

View File

@@ -1,5 +1,11 @@
import { useRef } from 'react';
import { BaselimeRum } from '@baselime/react-rum'; import { BaselimeRum } from '@baselime/react-rum';
import { MonitoringContext } from '@kit/monitoring-core';
import { useBaselime } from '../hooks/use-baselime';
export function BaselimeProvider({ export function BaselimeProvider({
children, children,
apiKey, apiKey,
@@ -16,7 +22,18 @@ export function BaselimeProvider({
enableWebVitals={enableWebVitals} enableWebVitals={enableWebVitals}
fallback={ErrorPage ?? null} fallback={ErrorPage ?? null}
> >
{children} <MonitoringProvider>{children}</MonitoringProvider>
</BaselimeRum> </BaselimeRum>
); );
} }
function MonitoringProvider(props: React.PropsWithChildren) {
const service = useBaselime();
const provider = useRef(service);
return (
<MonitoringContext.Provider value={provider.current}>
{props.children}
</MonitoringContext.Provider>
);
}

View File

@@ -1,6 +1,8 @@
import { useMemo } from 'react';
import { useBaselimeRum } from '@baselime/react-rum'; import { useBaselimeRum } from '@baselime/react-rum';
import { MonitoringService } from '../../../src/services/monitoring.service'; import { MonitoringService } from '@kit/monitoring-core';
/** /**
* @name useBaselime * @name useBaselime
@@ -9,12 +11,14 @@ import { MonitoringService } from '../../../src/services/monitoring.service';
export function useBaselime(): MonitoringService { export function useBaselime(): MonitoringService {
const { captureException, setUser } = useBaselimeRum(); const { captureException, setUser } = useBaselimeRum();
return { return useMemo(() => {
captureException(error: Error, extra?: React.ErrorInfo | undefined) { return {
void captureException(error, extra); captureException(error: Error, extra?: React.ErrorInfo | undefined) {
}, void captureException(error, extra);
identifyUser(params) { },
setUser(params.id); identifyUser(params) {
}, setUser(params.id);
} satisfies MonitoringService; },
} satisfies MonitoringService;
}, [captureException, setUser]);
} }

View File

@@ -1,11 +1,11 @@
import process from 'node:process'; import process from 'node:process';
import { z } from 'zod'; import { z } from 'zod';
import { MonitoringService } from '../../../src/services/monitoring.service'; import { MonitoringService } from '@kit/monitoring-core';
const apiKey = z const apiKey = z
.string({ .string({
required_error: 'API_KEY is required', required_error: 'BASELIME_API_KEY is required',
}) })
.parse(process.env.BASELIME_API_KEY); .parse(process.env.BASELIME_API_KEY);

View File

@@ -0,0 +1,41 @@
{
"name": "@kit/monitoring-core",
"private": true,
"sideEffects": false,
"version": "0.1.0",
"scripts": {
"clean": "git clean -xdf .turbo node_modules",
"format": "prettier --check \"**/*.{ts,tsx}\"",
"lint": "eslint .",
"typecheck": "tsc --noEmit"
},
"prettier": "@kit/prettier-config",
"exports": {
".": "./src/index.ts"
},
"devDependencies": {
"@kit/eslint-config": "workspace:*",
"@kit/prettier-config": "workspace:*",
"@kit/tailwind-config": "workspace:*",
"@kit/tsconfig": "workspace:*",
"@types/react": "^18.2.79",
"react": "18.2.0"
},
"peerDependencies": {
"react": "^18.2.0"
},
"eslintConfig": {
"root": true,
"extends": [
"@kit/eslint-config/base",
"@kit/eslint-config/react"
]
},
"typesVersions": {
"*": {
"*": [
"src/*"
]
}
}
}

View File

@@ -1,4 +1,4 @@
import { MonitoringService } from './monitoring.service'; import { MonitoringService } from '@kit/monitoring-core';
export class ConsoleMonitoringService implements MonitoringService { export class ConsoleMonitoringService implements MonitoringService {
identifyUser() { identifyUser() {

View File

@@ -0,0 +1,3 @@
export * from './monitoring.service';
export * from './monitoring.context';
export * from './console-monitoring.service';

View File

@@ -0,0 +1,8 @@
import { createContext } from 'react';
import { ConsoleMonitoringService } from './console-monitoring.service';
import { MonitoringService } from './monitoring.service';
export const MonitoringContext = createContext<MonitoringService>(
new ConsoleMonitoringService(),
);

View File

@@ -0,0 +1,8 @@
{
"extends": "@kit/tsconfig/base.json",
"compilerOptions": {
"tsBuildInfoFile": "node_modules/.cache/tsbuildinfo.json"
},
"include": ["*.ts", "src"],
"exclude": ["node_modules"]
}

View File

@@ -12,13 +12,14 @@
"exports": { "exports": {
".": "./src/index.ts", ".": "./src/index.ts",
"./server": "./src/server.ts", "./server": "./src/server.ts",
"./client": "./src/client.ts", "./provider": "./src/components/provider.tsx",
"./instrumentation": "./src/instrumentation.ts", "./instrumentation": "./src/instrumentation.ts",
"./config/client": "./src/config/sentry.client.config.ts", "./config/client": "./src/config/sentry.client.config.ts",
"./config/server": "./src/config/sentry.server.config.ts", "./config/server": "./src/config/sentry.server.config.ts",
"./config/edge": "./src/config/sentry.server.edge.ts" "./config/edge": "./src/config/sentry.server.edge.ts"
}, },
"dependencies": { "dependencies": {
"@kit/monitoring-core": "workspace:*",
"@opentelemetry/resources": "1.23.0", "@opentelemetry/resources": "1.23.0",
"@opentelemetry/sdk-node": "0.50.0", "@opentelemetry/sdk-node": "0.50.0",
"@opentelemetry/semantic-conventions": "^1.23.0", "@opentelemetry/semantic-conventions": "^1.23.0",
@@ -30,10 +31,15 @@
"zod": "^3.23.0" "zod": "^3.23.0"
}, },
"devDependencies": { "devDependencies": {
"@types/react": "^18.2.79",
"@kit/eslint-config": "workspace:*", "@kit/eslint-config": "workspace:*",
"@kit/prettier-config": "workspace:*", "@kit/prettier-config": "workspace:*",
"@kit/tailwind-config": "workspace:*", "@kit/tailwind-config": "workspace:*",
"@kit/tsconfig": "workspace:*" "@kit/tsconfig": "workspace:*",
"react": "18.2.0"
},
"peerDependencies": {
"react": "18.2.0"
}, },
"eslintConfig": { "eslintConfig": {
"root": true, "root": true,

View File

@@ -1 +0,0 @@
export * from './hooks/use-sentry';

View File

@@ -0,0 +1,19 @@
import { useRef } from 'react';
import { MonitoringContext } from '@kit/monitoring-core';
import { SentryServerMonitoringService } from '../services/sentry-server-monitoring.service';
export function SentryProvider({ children }: React.PropsWithChildren) {
return <MonitoringProvider>{children}</MonitoringProvider>;
}
function MonitoringProvider(props: React.PropsWithChildren) {
const service = useRef(new SentryServerMonitoringService());
return (
<MonitoringContext.Provider value={service.current}>
{props.children}
</MonitoringContext.Provider>
);
}

View File

@@ -1,12 +0,0 @@
import { useMemo } from 'react';
import { SentryServerMonitoringService } from '../services/sentry-server-monitoring.service';
/**
* @name useSentry
* @description Get the Sentry monitoring service. Sentry can be used in the browser and server - so we don't need to differentiate between the two.
* @returns {SentryServerMonitoringService}
*/
export function useSentry(): SentryServerMonitoringService {
return useMemo(() => new SentryServerMonitoringService(), []);
}

View File

@@ -1,6 +1,6 @@
import * as Sentry from '@sentry/nextjs'; import * as Sentry from '@sentry/nextjs';
import { MonitoringService } from '../../../src/services/monitoring.service'; import { MonitoringService } from '@kit/monitoring-core';
/** /**
* @class * @class

View File

@@ -1,51 +0,0 @@
import { getMonitoringProvider } from '../get-monitoring-provider';
import { InstrumentationProvider } from '../monitoring-providers.enum';
import { ConsoleMonitoringService } from '../services/console-monitoring.service';
import { MonitoringService } from '../services/monitoring.service';
const MONITORING = getMonitoringProvider();
let serviceFactory: () => MonitoringService;
/**
* @name useMonitoring
* @description Asynchronously load the monitoring service based on the MONITORING_PROVIDER environment variable.
* Use Suspense to suspend while loading the service.
*/
export function useMonitoring() {
if (!serviceFactory) {
throw withMonitoringService();
}
return serviceFactory();
}
async function withMonitoringService() {
serviceFactory = await loadMonitoringService();
}
async function loadMonitoringService(): Promise<() => MonitoringService> {
if (!MONITORING) {
return Promise.resolve(() => new ConsoleMonitoringService());
}
switch (MONITORING) {
case InstrumentationProvider.Baselime: {
const { useBaselime } = await import('@kit/baselime/client');
return useBaselime;
}
case InstrumentationProvider.Sentry: {
const { useSentry } = await import('@kit/sentry/client');
return useSentry;
}
default: {
throw new Error(
`Unknown instrumentation provider: ${MONITORING as string}`,
);
}
}
}

65
pnpm-lock.yaml generated
View File

@@ -103,7 +103,7 @@ importers:
version: link:../../packages/mailers version: link:../../packages/mailers
'@kit/monitoring': '@kit/monitoring':
specifier: workspace:^ specifier: workspace:^
version: link:../../packages/monitoring version: link:../../packages/monitoring/api
'@kit/next': '@kit/next':
specifier: workspace:^ specifier: workspace:^
version: link:../../packages/next version: link:../../packages/next
@@ -672,7 +672,7 @@ importers:
version: link:../../mailers version: link:../../mailers
'@kit/monitoring': '@kit/monitoring':
specifier: workspace:^ specifier: workspace:^
version: link:../../monitoring version: link:../../monitoring/api
'@kit/prettier-config': '@kit/prettier-config':
specifier: workspace:* specifier: workspace:*
version: link:../../../tooling/prettier version: link:../../../tooling/prettier
@@ -791,6 +791,9 @@ importers:
react: react:
specifier: 18.2.0 specifier: 18.2.0
version: 18.2.0 version: 18.2.0
react-dom:
specifier: 18.2.0
version: 18.2.0(react@18.2.0)
react-hook-form: react-hook-form:
specifier: ^7.51.3 specifier: ^7.51.3
version: 7.51.3(react@18.2.0) version: 7.51.3(react@18.2.0)
@@ -894,7 +897,7 @@ importers:
version: link:../../mailers version: link:../../mailers
'@kit/monitoring': '@kit/monitoring':
specifier: workspace:* specifier: workspace:*
version: link:../../monitoring version: link:../../monitoring/api
'@kit/prettier-config': '@kit/prettier-config':
specifier: workspace:* specifier: workspace:*
version: link:../../../tooling/prettier version: link:../../../tooling/prettier
@@ -1042,7 +1045,7 @@ importers:
specifier: ^3.23.0 specifier: ^3.23.0
version: 3.23.0 version: 3.23.0
packages/monitoring: packages/monitoring/api:
dependencies: dependencies:
'@tanstack/react-table': '@tanstack/react-table':
specifier: ^8.16.0 specifier: ^8.16.0
@@ -1059,22 +1062,25 @@ importers:
devDependencies: devDependencies:
'@kit/baselime': '@kit/baselime':
specifier: workspace:* specifier: workspace:*
version: link:baselime version: link:../baselime
'@kit/eslint-config': '@kit/eslint-config':
specifier: workspace:* specifier: workspace:*
version: link:../../tooling/eslint version: link:../../../tooling/eslint
'@kit/monitoring-core':
specifier: workspace:*
version: link:../core
'@kit/prettier-config': '@kit/prettier-config':
specifier: workspace:* specifier: workspace:*
version: link:../../tooling/prettier version: link:../../../tooling/prettier
'@kit/sentry': '@kit/sentry':
specifier: workspace:* specifier: workspace:*
version: link:sentry version: link:../sentry
'@kit/tailwind-config': '@kit/tailwind-config':
specifier: workspace:* specifier: workspace:*
version: link:../../tooling/tailwind version: link:../../../tooling/tailwind
'@kit/tsconfig': '@kit/tsconfig':
specifier: workspace:* specifier: workspace:*
version: link:../../tooling/typescript version: link:../../../tooling/typescript
'@types/react': '@types/react':
specifier: ^18.2.79 specifier: ^18.2.79
version: 18.2.79 version: 18.2.79
@@ -1090,6 +1096,9 @@ importers:
'@baselime/react-rum': '@baselime/react-rum':
specifier: ^0.2.9 specifier: ^0.2.9
version: 0.2.9(react@18.2.0) version: 0.2.9(react@18.2.0)
'@kit/monitoring-core':
specifier: workspace:*
version: link:../core
'@tanstack/react-table': '@tanstack/react-table':
specifier: ^8.16.0 specifier: ^8.16.0
version: 8.16.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0) version: 8.16.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
@@ -1115,9 +1124,39 @@ importers:
'@kit/tsconfig': '@kit/tsconfig':
specifier: workspace:* specifier: workspace:*
version: link:../../../tooling/typescript version: link:../../../tooling/typescript
'@types/react':
specifier: ^18.2.79
version: 18.2.79
react:
specifier: 18.2.0
version: 18.2.0
packages/monitoring/core:
devDependencies:
'@kit/eslint-config':
specifier: workspace:*
version: link:../../../tooling/eslint
'@kit/prettier-config':
specifier: workspace:*
version: link:../../../tooling/prettier
'@kit/tailwind-config':
specifier: workspace:*
version: link:../../../tooling/tailwind
'@kit/tsconfig':
specifier: workspace:*
version: link:../../../tooling/typescript
'@types/react':
specifier: ^18.2.79
version: 18.2.79
react:
specifier: 18.2.0
version: 18.2.0
packages/monitoring/sentry: packages/monitoring/sentry:
dependencies: dependencies:
'@kit/monitoring-core':
specifier: workspace:*
version: link:../core
'@opentelemetry/resources': '@opentelemetry/resources':
specifier: 1.23.0 specifier: 1.23.0
version: 1.23.0(@opentelemetry/api@1.8.0) version: 1.23.0(@opentelemetry/api@1.8.0)
@@ -1158,6 +1197,12 @@ importers:
'@kit/tsconfig': '@kit/tsconfig':
specifier: workspace:* specifier: workspace:*
version: link:../../../tooling/typescript version: link:../../../tooling/typescript
'@types/react':
specifier: ^18.2.79
version: 18.2.79
react:
specifier: 18.2.0
version: 18.2.0
packages/next: packages/next:
dependencies: dependencies: