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:
102
README.md
102
README.md
@@ -1296,6 +1296,108 @@ Doing the opposite is also okay - but:
|
||||
|
||||
My two cents - but you do you - anything that works for you is good.
|
||||
|
||||
## Monitoring
|
||||
|
||||
Monitoring is crucial for any application. Makerkit uses Sentry or Baselime for error tracking. Additionally, Makerkit makes use of Next.js experimental instrumentation for performance monitoring.
|
||||
|
||||
### Sentry
|
||||
|
||||
To use Sentry, you need to set the following environment variables:
|
||||
|
||||
```bash
|
||||
NEXT_PUBLIC_SENTRY_DSN=
|
||||
NEXT_PUBLIC_MONITORING_PROVIDER=sentry
|
||||
```
|
||||
|
||||
### Baselime
|
||||
|
||||
To use Baselime, you need to set the following environment variables:
|
||||
|
||||
```bash
|
||||
BASELIME_KEY=your_key
|
||||
NEXT_PUBLIC_MONITORING_PROVIDER=baselime
|
||||
```
|
||||
|
||||
### Next.js Instrumentation
|
||||
|
||||
To enable instrumentation, you need to set the following environment variables:
|
||||
|
||||
```bash
|
||||
MONITORING_INSTRUMENTATION_ENABLED=true
|
||||
```
|
||||
|
||||
That's it! Makerkit will take care of the rest.
|
||||
|
||||
### Capturing Errors
|
||||
|
||||
By default, Makerkit captures errors that are caught when rendering the upper `error.tsx` component. This is a good place to catch errors that are not caught by the error boundary.
|
||||
|
||||
If you want to capture errors manually, you have two ways
|
||||
|
||||
#### Capturing Errors using a hook
|
||||
|
||||
```tsx
|
||||
import { useMonitoring } from '@kit/monitoring/client';
|
||||
|
||||
function Component() {
|
||||
const { captureException } = useMonitoring();
|
||||
|
||||
const onClick = () => {
|
||||
try {
|
||||
throw new Error('An error occurred');
|
||||
} catch (error) {
|
||||
captureException(error);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<button onClick={onClick}>Throw an error</button>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
Alternatively, directly use `useCaptureException`:
|
||||
|
||||
```tsx
|
||||
import { useCaptureException } from '@kit/monitoring/client';
|
||||
|
||||
function Component() {
|
||||
const captureException = useCaptureException();
|
||||
|
||||
const onClick = () => {
|
||||
try {
|
||||
throw new Error('An error occurred');
|
||||
} catch (error) {
|
||||
captureException(error);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<button onClick={onClick}>Throw an error</button>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### Server Errors
|
||||
|
||||
To capture server errors, you can use the `captureException` function from the `monitoring` package:
|
||||
|
||||
```tsx
|
||||
import { getMonitoringService } from '@kit/monitoring/server';
|
||||
|
||||
async function serverSideFunction() {
|
||||
try {
|
||||
await someAsyncFunction();
|
||||
} catch (error) {
|
||||
const monitoring = await getMonitoringService();
|
||||
|
||||
await monitoring.captureException(error);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
In the future - this will be automatically captured by the `enhanceAction` and `enhanceRouteHandler` functions - so you won't need to do this manually unless you're swallowing the errors in the inner function (in which case, you should rethrow the error or capture manually).
|
||||
|
||||
## Going to Production
|
||||
|
||||
When you are ready to go to production, please follow the checklist below. This is an overview, a more detailed guide will be provided in the future.
|
||||
|
||||
@@ -20,6 +20,8 @@ import pathsConfig from '~/config/paths.config';
|
||||
import { withI18n } from '~/lib/i18n/with-i18n';
|
||||
|
||||
function Home() {
|
||||
throw new Error(`This is a test error`);
|
||||
|
||||
return (
|
||||
<div className={'mt-4 flex flex-col space-y-24 py-16'}>
|
||||
<div className={'container mx-auto flex flex-col space-y-20'}>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -3,12 +3,37 @@
|
||||
Please set the following environment variable to your preferred monitoring provider:
|
||||
|
||||
```
|
||||
MONITORING_INSTRUMENTATION_PROVIDER=
|
||||
NEXT_PUBLIC_MONITORING_PROVIDER=
|
||||
MONITORING_INSTRUMENTATION_ENABLED=true
|
||||
```
|
||||
|
||||
## Available Providers
|
||||
|
||||
To use a specific provider, set the `MONITORING_INSTRUMENTATION_PROVIDER` environment variable to one of the following values:
|
||||
To use a specific provider, set the `NEXT_PUBLIC_MONITORING_PROVIDER` environment variable to one of the following values:
|
||||
|
||||
1. Baselime: `baselime`
|
||||
2. Sentry: `sentry`
|
||||
|
||||
## Baselime
|
||||
|
||||
To use Baselime, set the `NEXT_PUBLIC_MONITORING_PROVIDER` environment variable to `baselime`.
|
||||
|
||||
```
|
||||
NEXT_PUBLIC_MONITORING_PROVIDER=baselime
|
||||
```
|
||||
|
||||
## Sentry
|
||||
|
||||
To use Sentry, set the `NEXT_PUBLIC_MONITORING_PROVIDER` environment variable to `sentry`.
|
||||
|
||||
```
|
||||
NEXT_PUBLIC_MONITORING_PROVIDER=sentry
|
||||
```
|
||||
|
||||
## Instrumentation
|
||||
|
||||
To enable instrumentation, set the `MONITORING_INSTRUMENTATION_ENABLED` environment variable to `true`.
|
||||
|
||||
```
|
||||
MONITORING_INSTRUMENTATION_ENABLED=true
|
||||
```
|
||||
@@ -4,5 +4,5 @@ Please set the following environment variables:
|
||||
|
||||
```
|
||||
BASELIME_KEY=your_key
|
||||
MONITORING_INSTRUMENTATION_PROVIDER=baselime
|
||||
NEXT_PUBLIC_MONITORING_PROVIDER=baselime
|
||||
```
|
||||
@@ -10,7 +10,8 @@
|
||||
},
|
||||
"prettier": "@kit/prettier-config",
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
"./server": "./src/server.ts",
|
||||
"./client": "./src/client.ts",
|
||||
"./instrumentation": "./src/instrumentation.ts",
|
||||
"./provider": "./src/components/provider.tsx"
|
||||
},
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
export function captureException(error: Error) {
|
||||
console.info(`No yet defined...`);
|
||||
return console.error(`Caught exception: ${JSON.stringify(error)}`);
|
||||
}
|
||||
1
packages/monitoring/baselime/src/client.ts
Normal file
1
packages/monitoring/baselime/src/client.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from './hooks/use-baselime';
|
||||
20
packages/monitoring/baselime/src/hooks/use-baselime.ts
Normal file
20
packages/monitoring/baselime/src/hooks/use-baselime.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { useBaselimeRum } from '@baselime/react-rum';
|
||||
|
||||
import { MonitoringService } from '../../../src/services/monitoring.service';
|
||||
|
||||
/**
|
||||
* @name useBaselime
|
||||
* @description Get the Baselime monitoring service for the browser.
|
||||
*/
|
||||
export function useBaselime(): MonitoringService {
|
||||
const { captureException, setUser } = useBaselimeRum();
|
||||
|
||||
return {
|
||||
captureException(error: Error, extra?: React.ErrorInfo | undefined) {
|
||||
void captureException(error, extra);
|
||||
},
|
||||
identifyUser(params) {
|
||||
setUser(params.id);
|
||||
},
|
||||
} satisfies MonitoringService;
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
import { useCallback } from 'react';
|
||||
|
||||
import { useBaselimeRum } from '@baselime/react-rum';
|
||||
|
||||
export function useCaptureException() {
|
||||
const { captureException } = useBaselimeRum();
|
||||
|
||||
return useCallback(
|
||||
(error: Error) => captureException(error),
|
||||
[captureException],
|
||||
);
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
export * from './capture-exception';
|
||||
1
packages/monitoring/baselime/src/server.ts
Normal file
1
packages/monitoring/baselime/src/server.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from './services/baselime-server-monitoring.service';
|
||||
@@ -0,0 +1,9 @@
|
||||
import { MonitoringService } from '../../../src/services/monitoring.service';
|
||||
|
||||
export class BaselimeServerMonitoringService implements MonitoringService {
|
||||
captureException(error: Error | null) {
|
||||
console.error(`Caught exception: ${JSON.stringify(error)}`);
|
||||
}
|
||||
|
||||
identifyUser<Info extends { id: string }>(info: Info) {}
|
||||
}
|
||||
@@ -11,7 +11,7 @@
|
||||
},
|
||||
"prettier": "@kit/prettier-config",
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
"./server": "./src/server.ts",
|
||||
"./instrumentation": "./src/instrumentation.ts",
|
||||
"./hooks": "./src/hooks/index.ts",
|
||||
"./components": "./src/components/index.ts"
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
Please set the following environment variable:
|
||||
|
||||
```
|
||||
MONITORING_INSTRUMENTATION_PROVIDER=sentry
|
||||
NEXT_PUBLIC_MONITORING_PROVIDER=sentry
|
||||
NEXT_PUBLIC_SENTRY_DSN=your_dsn
|
||||
```
|
||||
|
||||
|
||||
@@ -11,6 +11,8 @@
|
||||
"prettier": "@kit/prettier-config",
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
"./server": "./src/server.ts",
|
||||
"./client": "./src/client.ts",
|
||||
"./instrumentation": "./src/instrumentation.ts",
|
||||
"./config/client": "./src/config/sentry.client.config.ts",
|
||||
"./config/server": "./src/config/sentry.server.config.ts",
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
import * as Sentry from '@sentry/nextjs';
|
||||
|
||||
export function captureException(error: Error & { digest?: string }) {
|
||||
return Sentry.captureException(error);
|
||||
}
|
||||
1
packages/monitoring/sentry/src/client.ts
Normal file
1
packages/monitoring/sentry/src/client.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from './hooks/use-sentry';
|
||||
12
packages/monitoring/sentry/src/hooks/use-sentry.ts
Normal file
12
packages/monitoring/sentry/src/hooks/use-sentry.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
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(), []);
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
export * from './capture-exception';
|
||||
1
packages/monitoring/sentry/src/server.ts
Normal file
1
packages/monitoring/sentry/src/server.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from './services/sentry-server-monitoring.service';
|
||||
@@ -0,0 +1,18 @@
|
||||
import * as Sentry from '@sentry/nextjs';
|
||||
|
||||
import { MonitoringService } from '../../../src/services/monitoring.service';
|
||||
|
||||
/**
|
||||
* @class
|
||||
* @implements {MonitoringService}
|
||||
* ServerSentryMonitoringService is responsible for capturing exceptions and identifying users using the Sentry monitoring service.
|
||||
*/
|
||||
export class SentryServerMonitoringService implements MonitoringService {
|
||||
captureException(error: Error | null) {
|
||||
return Sentry.captureException(error);
|
||||
}
|
||||
|
||||
identifyUser(user: Sentry.User) {
|
||||
Sentry.setUser(user);
|
||||
}
|
||||
}
|
||||
@@ -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.`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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() {
|
||||
|
||||
7
packages/monitoring/src/get-monitoring-provider.ts
Normal file
7
packages/monitoring/src/get-monitoring-provider.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { InstrumentationProvider } from './monitoring-providers.enum';
|
||||
|
||||
export function getMonitoringProvider() {
|
||||
return process.env.NEXT_PUBLIC_MONITORING_PROVIDER as
|
||||
| InstrumentationProvider
|
||||
| undefined;
|
||||
}
|
||||
@@ -1 +1,2 @@
|
||||
export * from './use-monitoring';
|
||||
export * from './use-capture-exception';
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
51
packages/monitoring/src/hooks/use-monitoring.ts
Normal file
51
packages/monitoring/src/hooks/use-monitoring.ts
Normal 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}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
export * from './capture-exception';
|
||||
@@ -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'
|
||||
|
||||
1
packages/monitoring/src/server.ts
Normal file
1
packages/monitoring/src/server.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from './services/get-server-monitoring-service';
|
||||
@@ -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)}`);
|
||||
}
|
||||
}
|
||||
@@ -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.`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
22
packages/monitoring/src/services/monitoring.service.ts
Normal file
22
packages/monitoring/src/services/monitoring.service.ts
Normal 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;
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -3,7 +3,7 @@
|
||||
"private": true,
|
||||
"version": "0.1.0",
|
||||
"exports": {
|
||||
".": "./index.ts"
|
||||
".": "./server.ts"
|
||||
},
|
||||
"typesVersions": {
|
||||
"*": {
|
||||
|
||||
Reference in New Issue
Block a user