Refactor monitoring package and improve error handling

The monitoring package has been significantly refactored to improve the granularity of error capture. Code from the 'capture-exception.ts' files in different locations have been deleted and replaced by a more unified approach in the 'use-baselime.ts' and 'use-sentry.ts' hooks. The README documentation has also been updated to reflect these changes and provide additional information about error monitoring setup and usage.
This commit is contained in:
giancarlo
2024-04-22 15:24:01 +08:00
parent 3ce6c62425
commit b6d303f90e
37 changed files with 2362 additions and 2106 deletions

View File

@@ -1,44 +0,0 @@
import { InstrumentationProvider } from './monitoring-providers.enum';
/**
* @name MONITORING_PROVIDER
* @description Register monitoring instrumentation based on the MONITORING_PROVIDER environment variable.
*/
const MONITORING_PROVIDER = process.env.MONITORING_PROVIDER as
| InstrumentationProvider
| undefined;
/**
* @name captureException
* @description Capture an exception and send it to the monitoring provider defined.
* @param error
*/
export async function captureException(error: Error) {
if (!MONITORING_PROVIDER) {
console.info(
`No instrumentation provider specified. Logging to console...`,
);
return console.error(`Caught exception: ${JSON.stringify(error)}`);
}
switch (MONITORING_PROVIDER) {
case InstrumentationProvider.Baselime: {
const { captureException } = await import('@kit/baselime');
return captureException(error);
}
case InstrumentationProvider.Sentry: {
const { captureException } = await import('@kit/sentry');
return captureException(error);
}
default: {
throw new Error(
`Please set the MONITORING_PROVIDER environment variable to register the monitoring instrumentation provider.`,
);
}
}
}

View File

@@ -1,8 +1,6 @@
import type { ErrorInfo, ReactNode } from 'react';
import { Component } from 'react';
import { captureException } from '../capture-exception';
interface Props {
onError?: (error: Error, info: ErrorInfo) => void;
fallback: ReactNode;
@@ -23,10 +21,8 @@ export class ErrorBoundary extends Component<Props> {
};
}
async componentDidCatch(error: Error, info: ErrorInfo) {
componentDidCatch(error: Error, info: ErrorInfo) {
this.props.onError?.(error, info);
await captureException(error);
}
render() {

View File

@@ -0,0 +1,7 @@
import { InstrumentationProvider } from './monitoring-providers.enum';
export function getMonitoringProvider() {
return process.env.NEXT_PUBLIC_MONITORING_PROVIDER as
| InstrumentationProvider
| undefined;
}

View File

@@ -1 +1,2 @@
export * from './use-monitoring';
export * from './use-capture-exception';

View File

@@ -1,11 +1,7 @@
import { useEffect } from 'react';
import { captureException } from '../capture-exception';
import { useMonitoring } from './use-monitoring';
export function useCaptureException(error: Error) {
useEffect(() => {
void captureException(error);
}, [error]);
const service = useMonitoring();
return null;
return service.captureException(error);
}

View File

@@ -0,0 +1,51 @@
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 service: 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 (!service) {
throw withMonitoringService();
}
console.log(service);
return service;
}
async function withMonitoringService() {
service = await loadMonitoringService();
}
async function loadMonitoringService() {
if (!MONITORING) {
return 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}`);
}
}
}

View File

@@ -1 +0,0 @@
export * from './capture-exception';

View File

@@ -1,5 +1,8 @@
import { getMonitoringProvider } from './get-monitoring-provider';
import { InstrumentationProvider } from './monitoring-providers.enum';
const PROVIDER = getMonitoringProvider();
/**
* @name registerMonitoringInstrumentation
* @description Register monitoring instrumentation based on the MONITORING_PROVIDER environment variable.
@@ -7,13 +10,13 @@ import { InstrumentationProvider } from './monitoring-providers.enum';
* Please set the MONITORING_PROVIDER environment variable to register the monitoring instrumentation provider.
*/
export async function registerMonitoringInstrumentation() {
if (!process.env.MONITORING_PROVIDER) {
if (!PROVIDER) {
console.info(`No instrumentation provider specified. Skipping...`);
return;
}
switch (process.env.MONITORING_PROVIDER as InstrumentationProvider) {
switch (PROVIDER) {
case InstrumentationProvider.Baselime: {
const { registerInstrumentation } = await import(
'@kit/baselime/instrumentation'

View File

@@ -0,0 +1 @@
export * from './services/get-server-monitoring-service';

View File

@@ -0,0 +1,11 @@
import { MonitoringService } from './monitoring.service';
export class ConsoleMonitoringService implements MonitoringService {
identifyUser() {
// noop
}
captureException(error: Error) {
console.error(`Caught exception: ${JSON.stringify(error)}`);
}
}

View File

@@ -0,0 +1,43 @@
import { getMonitoringProvider } from '../get-monitoring-provider';
import { InstrumentationProvider } from '../monitoring-providers.enum';
import { ConsoleMonitoringService } from './console-monitoring.service';
const MONITORING_PROVIDER = getMonitoringProvider();
/**
* @name getServerMonitoringService
* @description Get the monitoring service based on the MONITORING_PROVIDER environment variable.
*/
export async function getServerMonitoringService() {
if (!MONITORING_PROVIDER) {
console.info(
`No instrumentation provider specified. Returning console service...`,
);
return new ConsoleMonitoringService();
}
switch (MONITORING_PROVIDER) {
case InstrumentationProvider.Baselime: {
const { BaselimeServerMonitoringService } = await import(
'@kit/baselime/server'
);
return new BaselimeServerMonitoringService();
}
case InstrumentationProvider.Sentry: {
const { SentryServerMonitoringService } = await import(
'@kit/sentry/server'
);
return new SentryServerMonitoringService();
}
default: {
throw new Error(
`Please set the MONITORING_PROVIDER environment variable to register the monitoring instrumentation provider.`,
);
}
}
}

View File

@@ -0,0 +1,22 @@
/**
* Monitoring service interface
* @description This service is used to capture exceptions and identify users in the monitoring service
* @example
*/
export abstract class MonitoringService {
/**
* Capture an exception
* @param error
* @param extra
*/
abstract captureException<Extra extends object>(
error: Error & { digest?: string },
extra?: Extra,
): unknown;
/**
* Identify a user in the monitoring service - used for tracking user actions
* @param info
*/
abstract identifyUser<Info extends { id: string }>(info: Info): unknown;
}