Creating a Custom Monitoring Provider
Learn how to implement custom monitoring providers for services like Datadog, New Relic, or any error tracking platform.
While Makerkit includes Sentry support out of the box, you can implement custom providers for any monitoring service. This guide shows you how to create and register a custom monitoring provider.
Custom Provider Guide
Create a custom monitoring provider
Extend the MonitoringService Abstract Class
Create a class that extends the MonitoringService abstract class:
packages/monitoring/my-provider/src/my-monitoring-service.ts
import { MonitoringService } from '@kit/monitoring-core';export class MyMonitoringService extends MonitoringService { private initialized = false; async ready(): Promise<void> { // Wait for the service to be ready // Return immediately if already initialized if (this.initialized) return; // Initialize your SDK here await this.initialize(); } captureException< Extra extends Record<string, unknown>, Config extends Record<string, unknown>, >( error: Error & { digest?: string }, extra?: Extra, config?: Config, ): void { // Send the error to your monitoring service } captureEvent<Extra extends object>( event: string, extra?: Extra, ): void { // Track a custom event } identifyUser<Info extends { id: string }>(info: Info): void { // Associate subsequent errors with this user } private async initialize(): Promise<void> { // Load SDKs, configure clients, etc. this.initialized = true; }}Register the Server Provider
Add your provider to the server monitoring registry:
packages/monitoring/api/src/services/get-server-monitoring-service.ts
import { ConsoleMonitoringService, MonitoringService,} from '@kit/monitoring-core';import { createRegistry } from '@kit/shared/registry';import { MonitoringProvider, getMonitoringProvider,} from '../get-monitoring-provider';const serverMonitoringRegistry = createRegistry< MonitoringService, NonNullable<MonitoringProvider>>();// Register SentryserverMonitoringRegistry.register('sentry', async () => { const { SentryMonitoringService } = await import('@kit/sentry'); return new SentryMonitoringService();});// Register your custom providerserverMonitoringRegistry.register('my-provider', async () => { const { MyMonitoringService } = await import('@kit/my-provider'); return new MyMonitoringService();});export async function getServerMonitoringService() { const provider = getMonitoringProvider(); if (!provider) { return new ConsoleMonitoringService(); } return serverMonitoringRegistry.get(provider);}Register the Client Provider
Create a React provider component for client-side monitoring:
packages/monitoring/my-provider/src/provider.tsx
'use client';import { MonitoringContext } from '@kit/monitoring-core';import { MyMonitoringService } from './my-monitoring-service';const service = new MyMonitoringService();export function MyMonitoringProvider({ children }: React.PropsWithChildren) { return ( <MonitoringContext.Provider value={service}> {children} </MonitoringContext.Provider> );}Register it in the client provider registry:
packages/monitoring/api/src/components/provider.tsx
import { createRegistry } from '@kit/shared/registry';const monitoringProviderRegistry = createRegistry< { default: React.ComponentType<React.PropsWithChildren> }, NonNullable<MonitoringProvider>>();// Register Sentry providermonitoringProviderRegistry.register('sentry', async () => { return import('@kit/sentry/provider');});// Register your custom providermonitoringProviderRegistry.register('my-provider', async () => { return import('@kit/my-provider/provider');});Update the Provider Schema
Add your provider to the allowed providers list:
packages/monitoring/api/src/get-monitoring-provider.ts
import * as z from 'zod';const MONITORING_PROVIDERS = [ 'sentry', 'my-provider', // Add your provider here '',] as const;export const MONITORING_PROVIDER = z .enum(MONITORING_PROVIDERS) .optional() .transform((value) => value || undefined);Now you can enable your provider with:
.env.local
NEXT_PUBLIC_MONITORING_PROVIDER=my-providerExample: Datadog Provider
Here's a complete example implementing a Datadog RUM (Real User Monitoring) provider:
packages/monitoring/datadog/src/datadog-monitoring-service.ts
import { datadogRum } from '@datadog/browser-rum';import { MonitoringService } from '@kit/monitoring-core';export class DatadogMonitoringService extends MonitoringService { private initialized = false; private readyPromise: Promise<void>; private readyResolver?: () => void; constructor() { this.readyPromise = new Promise((resolve) => { this.readyResolver = resolve; }); void this.initialize(); } async ready(): Promise<void> { return this.readyPromise; } captureException< Extra extends Record<string, unknown>, Config extends Record<string, unknown>, >( error: Error & { digest?: string }, extra?: Extra, config?: Config, ): void { if (!this.initialized) return; datadogRum.addError(error, { ...extra, digest: error.digest, }); } captureEvent<Extra extends object>( event: string, extra?: Extra, ): void { if (!this.initialized) return; datadogRum.addAction(event, extra); } identifyUser<Info extends { id: string }>(info: Info): void { if (!this.initialized) return; datadogRum.setUser({ id: info.id, ...info, }); } private async initialize(): Promise<void> { if (typeof window === 'undefined') { this.readyResolver?.(); return; } const applicationId = process.env.NEXT_PUBLIC_DATADOG_APPLICATION_ID; const clientToken = process.env.NEXT_PUBLIC_DATADOG_CLIENT_TOKEN; if (!applicationId || !clientToken) { console.warn('Datadog credentials not configured'); this.readyResolver?.(); return; } datadogRum.init({ applicationId, clientToken, site: 'datadoghq.com', service: 'my-saas-app', env: process.env.NODE_ENV, sessionSampleRate: 100, sessionReplaySampleRate: 20, trackUserInteractions: true, trackResources: true, trackLongTasks: true, defaultPrivacyLevel: 'mask-user-input', }); this.initialized = true; this.readyResolver?.(); }}Environment variables:
.env.local
NEXT_PUBLIC_MONITORING_PROVIDER=datadogNEXT_PUBLIC_DATADOG_APPLICATION_ID=your-application-idNEXT_PUBLIC_DATADOG_CLIENT_TOKEN=your-client-tokenTesting Your Provider
- Set the environment variable to your provider
- Trigger a test error
- Check your monitoring dashboard
Test component
'use client';import { useMonitoring } from '@kit/monitoring/hooks';export function TestMonitoring() { const monitoring = useMonitoring(); return ( <div> <button onClick={() => { monitoring.captureException(new Error('Test error')); }} > Test Exception </button> <button onClick={() => { monitoring.captureEvent('test_event', { foo: 'bar' }); }} > Test Event </button> </div> );}