Monitoring and Analytics Guide
This guide covers the implementation of comprehensive monitoring and analytics for the Kulcare platform's micro-frontend architecture.
Overview
The Kulcare platform requires robust monitoring to ensure reliability, track performance, and understand user behavior across multiple micro-frontends. This includes error tracking, analytics, performance monitoring, and logging.
Current State
Environment Variables Defined
The following monitoring-related environment variables are already defined in the deployment configuration but not yet implemented:
VITE_SENTRY_DSN- Sentry error trackingVITE_GOOGLE_ANALYTICS_ID- Google Analytics trackingVITE_ENABLE_ANALYTICS- Feature flag for analyticsVITE_ENABLE_PERFORMANCE_MONITORING- Performance monitoring flagVITE_LOG_LEVEL- Logging configuration
Existing Components
ErrorBoundarycomponent in@kulcare/ui-kitwith basic error handling- Module Federation error handling mentioned in deployment docs
- No actual implementation of monitoring services
Implementation Roadmap
Phase 1: Core Infrastructure (Week 1-2)
- Create
@kulcare/monitoringpackage - Implement error tracking with Sentry
- Set up basic logging infrastructure
- Add health check endpoints
Phase 2: Analytics (Week 3)
- Implement Google Analytics 4
- Set up cross-microfrontend tracking
- Add custom event tracking
- Configure conversion tracking
Phase 3: Performance (Week 4)
- Implement Web Vitals tracking
- Add Module Federation monitoring
- Set up performance budgets
- Configure real-user monitoring (RUM)
Phase 4: Advanced Features (Week 5-6)
- Implement A/B testing infrastructure
- Add session replay capabilities
- Set up alerting and dashboards
- Create monitoring documentation
Detailed Implementation
1. Error Tracking (Sentry)
Installation
pnpm add @sentry/react @sentry/vite-plugin --filter @kulcare/monitoringBasic Setup
Create packages/monitoring/src/sentry.ts:
import * as Sentry from '@sentry/react';
import { BrowserTracing } from '@sentry/tracing';
export interface SentryConfig {
dsn: string;
environment: string;
enabled?: boolean;
tracesSampleRate?: number;
debug?: boolean;
}
export function initSentry(config: SentryConfig) {
if (!config.enabled || !config.dsn) {
return;
}
Sentry.init({
dsn: config.dsn,
environment: config.environment,
integrations: [
new BrowserTracing({
routingInstrumentation: Sentry.reactRouterV6Instrumentation(
React.useEffect,
useLocation,
useNavigationType,
createRoutesFromChildren,
matchRoutes
),
}),
],
tracesSampleRate: config.tracesSampleRate || 0.1,
debug: config.debug || false,
beforeSend(event, hint) {
// Filter out known non-critical errors
if (event.exception?.values?.[0]?.type === 'ChunkLoadError') {
return null;
}
return event;
},
});
}
export function setUserContext(user: { id: string; email?: string; name?: string }) {
Sentry.setUser(user);
}
export function captureException(error: Error, context?: Record<string, any>) {
Sentry.captureException(error, { extra: context });
}Integration with ErrorBoundary
Update the ErrorBoundary to report to Sentry:
import { captureException } from '@kulcare/monitoring';
componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
console.error('ErrorBoundary caught an error:', error, errorInfo);
// Report to Sentry
captureException(error, {
componentStack: errorInfo.componentStack,
errorBoundary: true,
});
this.props.onError?.(error, errorInfo);
}App-Level Integration
In each app's main.tsx:
import { initSentry, setUserContext } from '@kulcare/monitoring';
import { useAuth } from '@kulcare/auth';
// Initialize Sentry
initSentry({
dsn: import.meta.env.VITE_SENTRY_DSN,
environment: import.meta.env.MODE,
enabled: import.meta.env.PROD,
tracesSampleRate: 0.1,
});
// Set user context when authenticated
function AppWithMonitoring() {
const { user } = useAuth();
useEffect(() => {
if (user) {
setUserContext({
id: user.id,
email: user.email,
name: user.name,
});
}
}, [user]);
return <App />;
}2. Analytics (Google Analytics 4)
Installation
pnpm add gtag.js --filter @kulcare/monitoringSetup
Create packages/monitoring/src/analytics.ts:
declare global {
interface Window {
gtag: (...args: any[]) => void;
dataLayer: any[];
}
}
export interface AnalyticsConfig {
measurementId: string;
enabled?: boolean;
debug?: boolean;
}
export function initAnalytics(config: AnalyticsConfig) {
if (!config.enabled || !config.measurementId) {
return;
}
// Load GA4 script
const script = document.createElement('script');
script.async = true;
script.src = `https://www.googletagmanager.com/gtag/js?id=${config.measurementId}`;
document.head.appendChild(script);
// Initialize dataLayer
window.dataLayer = window.dataLayer || [];
window.gtag = function gtag() {
window.dataLayer.push(arguments);
};
window.gtag('js', new Date());
window.gtag('config', config.measurementId, {
debug_mode: config.debug,
});
}
export function trackEvent(eventName: string, parameters?: Record<string, any>) {
if (typeof window.gtag === 'function') {
window.gtag('event', eventName, parameters);
}
}
export function trackPageView(pagePath: string, pageTitle?: string) {
if (typeof window.gtag === 'function') {
window.gtag('event', 'page_view', {
page_path: pagePath,
page_title: pageTitle,
});
}
}
export function setUserProperties(properties: Record<string, any>) {
if (typeof window.gtag === 'function') {
window.gtag('set', 'user_properties', properties);
}
}Route Change Tracking
Create a hook for tracking route changes:
import { useEffect } from 'react';
import { useLocation } from 'react-router-dom';
import { trackPageView } from '@kulcare/monitoring';
export function usePageTracking() {
const location = useLocation();
useEffect(() => {
trackPageView(location.pathname + location.search);
}, [location]);
}Custom Events
Standard events to track across the platform:
// User actions
trackEvent('login', { method: 'email' });
trackEvent('signup', { method: 'google' });
trackEvent('logout');
// Feature usage
trackEvent('feature_used', {
feature_name: 'appointment_booking',
feature_category: 'cms',
});
// Errors
trackEvent('error', {
error_type: 'api_error',
error_message: error.message,
error_code: error.code,
});
// Conversions
trackEvent('purchase', {
transaction_id: '12345',
value: 99.99,
currency: 'INR',
items: [{ id: 'pkg_001', name: 'Premium Package' }],
});3. Performance Monitoring
Web Vitals
Create packages/monitoring/src/performance.ts:
import { getCLS, getFID, getFCP, getLCP, getTTFB } from 'web-vitals';
export interface PerformanceConfig {
enabled?: boolean;
reportHandler?: (metric: any) => void;
}
export function initPerformanceMonitoring(config: PerformanceConfig) {
if (!config.enabled) {
return;
}
const reportHandler = config.reportHandler || defaultReportHandler;
getCLS(reportHandler);
getFID(reportHandler);
getFCP(reportHandler);
getLCP(reportHandler);
getTTFB(reportHandler);
}
function defaultReportHandler(metric: any) {
// Report to analytics
trackEvent('web_vitals', {
metric_name: metric.name,
metric_value: metric.value,
metric_delta: metric.delta,
metric_id: metric.id,
});
// Report to Sentry
if (metric.value > getThreshold(metric.name)) {
captureException(new Error(`Poor ${metric.name} performance`), {
metric,
});
}
}
function getThreshold(metricName: string): number {
const thresholds: Record<string, number> = {
CLS: 0.25,
FID: 300,
FCP: 3000,
LCP: 4000,
TTFB: 800,
};
return thresholds[metricName] || Infinity;
}Module Federation Monitoring
Monitor remote module loading:
export function monitorModuleFederation() {
// Track load times
const moduleLoadTimes: Record<string, number> = {};
window.addEventListener('error', (event) => {
if (event.filename?.includes('remoteEntry.js')) {
captureException(new Error('Module Federation Load Error'), {
filename: event.filename,
message: event.message,
colno: event.colno,
lineno: event.lineno,
});
trackEvent('module_federation_error', {
remote_url: event.filename,
error_message: event.message,
});
}
});
// Intercept dynamic imports
const originalImport = window.System.import;
window.System.import = async function(id: string) {
const startTime = performance.now();
try {
const module = await originalImport.call(this, id);
const loadTime = performance.now() - startTime;
moduleLoadTimes[id] = loadTime;
trackEvent('module_loaded', {
module_id: id,
load_time: loadTime,
});
return module;
} catch (error) {
trackEvent('module_load_failed', {
module_id: id,
error_message: error.message,
});
throw error;
}
};
}4. Logging Infrastructure
Logger Implementation
Create packages/monitoring/src/logger.ts:
export enum LogLevel {
DEBUG = 0,
INFO = 1,
WARN = 2,
ERROR = 3,
}
export interface LoggerConfig {
level: LogLevel;
enabled?: boolean;
remote?: boolean;
remoteUrl?: string;
}
class Logger {
private config: LoggerConfig;
private buffer: any[] = [];
private flushInterval?: number;
constructor(config: LoggerConfig) {
this.config = config;
if (config.remote && config.remoteUrl) {
this.startRemoteLogging();
}
}
private shouldLog(level: LogLevel): boolean {
return this.config.enabled !== false && level >= this.config.level;
}
private formatMessage(level: string, message: string, data?: any): string {
const timestamp = new Date().toISOString();
const clinic = localStorage.getItem('selectedClinic');
const user = localStorage.getItem('userId');
return JSON.stringify({
timestamp,
level,
message,
data,
context: {
clinic,
user,
url: window.location.href,
userAgent: navigator.userAgent,
},
});
}
private log(level: LogLevel, levelName: string, message: string, data?: any) {
if (!this.shouldLog(level)) {
return;
}
const formattedMessage = this.formatMessage(levelName, message, data);
// Console output in development
if (import.meta.env.DEV) {
console[levelName.toLowerCase()](message, data);
}
// Buffer for remote logging
if (this.config.remote) {
this.buffer.push(formattedMessage);
}
// Send to Sentry for errors
if (level === LogLevel.ERROR) {
captureException(new Error(message), data);
}
}
debug(message: string, data?: any) {
this.log(LogLevel.DEBUG, 'DEBUG', message, data);
}
info(message: string, data?: any) {
this.log(LogLevel.INFO, 'INFO', message, data);
}
warn(message: string, data?: any) {
this.log(LogLevel.WARN, 'WARN', message, data);
}
error(message: string, data?: any) {
this.log(LogLevel.ERROR, 'ERROR', message, data);
}
private startRemoteLogging() {
this.flushInterval = window.setInterval(() => {
this.flush();
}, 5000); // Flush every 5 seconds
}
private async flush() {
if (this.buffer.length === 0) {
return;
}
const logs = [...this.buffer];
this.buffer = [];
try {
await fetch(this.config.remoteUrl!, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ logs }),
});
} catch (error) {
console.error('Failed to send logs:', error);
// Put logs back in buffer
this.buffer.unshift(...logs);
}
}
destroy() {
if (this.flushInterval) {
clearInterval(this.flushInterval);
this.flush();
}
}
}
export function createLogger(config: LoggerConfig): Logger {
return new Logger(config);
}5. Health Monitoring
Health Check Endpoints
Add health.json to each app's public folder:
{
"status": "healthy",
"version": "1.0.0",
"app": "shell",
"timestamp": "2024-01-01T00:00:00Z",
"checks": {
"database": "ok",
"cache": "ok",
"remotes": {
"cms": "ok",
"growth": "ok"
}
}
}Health Check Service
Create packages/monitoring/src/health.ts:
export interface HealthCheckResult {
status: 'healthy' | 'degraded' | 'unhealthy';
checks: Record<string, boolean>;
timestamp: string;
}
export async function checkHealth(remotes: string[]): Promise<HealthCheckResult> {
const checks: Record<string, boolean> = {};
// Check API
try {
const response = await fetch('/api/health', {
method: 'HEAD',
timeout: 5000,
});
checks.api = response.ok;
} catch {
checks.api = false;
}
// Check remotes
for (const remote of remotes) {
try {
const response = await fetch(`${remote}/health.json`, {
timeout: 5000,
});
checks[`remote_${remote}`] = response.ok;
} catch {
checks[`remote_${remote}`] = false;
}
}
const allHealthy = Object.values(checks).every(v => v);
const someHealthy = Object.values(checks).some(v => v);
return {
status: allHealthy ? 'healthy' : someHealthy ? 'degraded' : 'unhealthy',
checks,
timestamp: new Date().toISOString(),
};
}
export function startHealthMonitoring(interval = 60000) {
setInterval(async () => {
const health = await checkHealth(['cms', 'growth']);
if (health.status !== 'healthy') {
trackEvent('health_check_failed', health);
}
}, interval);
}Implementation Checklist
Phase 1: Core Setup
- Create @kulcare/monitoring package
- Implement Sentry integration
- Update ErrorBoundary component
- Add Sentry to all apps
- Configure source maps
- Test error reporting
Phase 2: Analytics
- Implement GA4 integration
- Add page tracking
- Define standard events
- Add custom event tracking
- Set up user properties
- Configure conversions
Phase 3: Performance
- Add Web Vitals tracking
- Implement performance budgets
- Monitor Module Federation
- Track API performance
- Set up alerts
Phase 4: Infrastructure
- Implement logger
- Add health checks
- Set up dashboards
- Configure alerts
- Create runbooks
- Train team
Privacy and Compliance
GDPR Compliance
- Implement cookie consent banner
- Allow users to opt-out of tracking
- Provide data export functionality
- Document data retention policies
HIPAA Compliance
- Ensure no PHI in analytics
- Implement audit logging
- Encrypt all data in transit
- Regular security audits
Testing
Unit Tests
describe('Analytics', () => {
it('should track events when enabled', () => {
initAnalytics({ measurementId: 'G-TEST', enabled: true });
trackEvent('test_event');
expect(window.gtag).toHaveBeenCalledWith('event', 'test_event', undefined);
});
it('should not track when disabled', () => {
initAnalytics({ measurementId: 'G-TEST', enabled: false });
trackEvent('test_event');
expect(window.gtag).not.toHaveBeenCalled();
});
});Integration Tests
- Test cross-microfrontend tracking
- Verify error reporting
- Check performance metrics
- Validate health checks
Monitoring Dashboard
Key Metrics
- Error Rate: Errors per minute/hour
- Performance: Core Web Vitals scores
- Availability: Uptime percentage
- Usage: Active users, page views
- Business: Conversions, feature adoption
Alerting Rules
- Error rate > 1% of requests
- Any Web Vital in "poor" range
- Health check failures
- Module Federation load failures
- API response time > 3s
Best Practices
- Sampling: Use appropriate sampling rates to control costs
- PII Protection: Never log personally identifiable information
- Context: Always include relevant context with errors
- Versioning: Track app versions with all metrics
- Documentation: Keep runbooks updated
Troubleshooting
Common Issues
Sentry Not Reporting
- Check DSN is correctly set
- Verify environment is production
- Check network tab for requests
- Look for console errors
Analytics Not Tracking
- Verify measurement ID
- Check ad blockers
- Validate consent status
- Test in incognito mode
Performance Issues
- Check sampling rates
- Optimize bundle sizes
- Review network requests
- Profile with DevTools