Skip to content

Monitoring and Logging Systems for Element Plus Applications

Overview

This guide covers comprehensive monitoring and logging strategies for Element Plus applications in production environments, including performance monitoring, error tracking, user analytics, and operational insights.

Application Performance Monitoring (APM)

Core Web Vitals Monitoring

typescript
// src/utils/performance-monitor.ts
export interface PerformanceMetrics {
  lcp: number // Largest Contentful Paint
  fid: number // First Input Delay
  cls: number // Cumulative Layout Shift
  fcp: number // First Contentful Paint
  ttfb: number // Time to First Byte
}

export class WebVitalsMonitor {
  private metrics: Partial<PerformanceMetrics> = {}
  private observers: PerformanceObserver[] = []
  
  constructor(private reportCallback: (metrics: PerformanceMetrics) => void) {
    this.initializeObservers()
  }
  
  private initializeObservers() {
    // Largest Contentful Paint
    this.observeMetric('largest-contentful-paint', (entries) => {
      const lastEntry = entries[entries.length - 1]
      this.metrics.lcp = lastEntry.startTime
      this.reportMetric('LCP', lastEntry.startTime)
    })
    
    // First Input Delay
    this.observeMetric('first-input', (entries) => {
      entries.forEach((entry: any) => {
        const fid = entry.processingStart - entry.startTime
        this.metrics.fid = fid
        this.reportMetric('FID', fid)
      })
    })
    
    // Cumulative Layout Shift
    let clsValue = 0
    this.observeMetric('layout-shift', (entries) => {
      entries.forEach((entry: any) => {
        if (!entry.hadRecentInput) {
          clsValue += entry.value
        }
      })
      this.metrics.cls = clsValue
      this.reportMetric('CLS', clsValue)
    })
    
    // First Contentful Paint
    this.observeMetric('paint', (entries) => {
      entries.forEach((entry) => {
        if (entry.name === 'first-contentful-paint') {
          this.metrics.fcp = entry.startTime
          this.reportMetric('FCP', entry.startTime)
        }
      })
    })
    
    // Time to First Byte
    this.observeMetric('navigation', (entries) => {
      entries.forEach((entry: any) => {
        this.metrics.ttfb = entry.responseStart - entry.requestStart
        this.reportMetric('TTFB', this.metrics.ttfb)
      })
    })
  }
  
  private observeMetric(
    entryType: string,
    callback: (entries: PerformanceEntry[]) => void
  ) {
    try {
      const observer = new PerformanceObserver((list) => {
        callback(list.getEntries())
      })
      
      observer.observe({ entryTypes: [entryType] })
      this.observers.push(observer)
    } catch (error) {
      console.warn(`Failed to observe ${entryType}:`, error)
    }
  }
  
  private reportMetric(name: string, value: number) {
    // Send to analytics service
    this.sendToAnalytics({
      event: 'web_vital',
      metric: name,
      value: Math.round(value),
      url: window.location.pathname,
      timestamp: Date.now()
    })
    
    // Check thresholds and alert if needed
    this.checkThresholds(name, value)
  }
  
  private checkThresholds(metric: string, value: number) {
    const thresholds = {
      LCP: { good: 2500, poor: 4000 },
      FID: { good: 100, poor: 300 },
      CLS: { good: 0.1, poor: 0.25 },
      FCP: { good: 1800, poor: 3000 },
      TTFB: { good: 800, poor: 1800 }
    }
    
    const threshold = thresholds[metric as keyof typeof thresholds]
    if (!threshold) return
    
    let status: 'good' | 'needs-improvement' | 'poor'
    if (value <= threshold.good) {
      status = 'good'
    } else if (value <= threshold.poor) {
      status = 'needs-improvement'
    } else {
      status = 'poor'
    }
    
    this.sendToAnalytics({
      event: 'performance_threshold',
      metric,
      value,
      status,
      url: window.location.pathname
    })
  }
  
  private sendToAnalytics(data: any) {
    // Send to your analytics service (Google Analytics, Mixpanel, etc.)
    if (typeof gtag !== 'undefined') {
      gtag('event', data.event, {
        custom_parameter_1: data.metric,
        custom_parameter_2: data.value,
        custom_parameter_3: data.status
      })
    }
    
    // Send to custom analytics endpoint
    fetch('/api/analytics', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(data)
    }).catch(console.error)
  }
  
  public getMetrics(): Partial<PerformanceMetrics> {
    return { ...this.metrics }
  }
  
  public destroy() {
    this.observers.forEach(observer => observer.disconnect())
    this.observers = []
  }
}

// Usage
const monitor = new WebVitalsMonitor((metrics) => {
  console.log('Performance metrics:', metrics)
})

Custom Performance Tracking

typescript
// src/utils/custom-performance.ts
export class CustomPerformanceTracker {
  private timers: Map<string, number> = new Map()
  private marks: Map<string, number> = new Map()
  
  // Track component render times
  trackComponentRender(componentName: string) {
    const startTime = performance.now()
    
    return () => {
      const endTime = performance.now()
      const duration = endTime - startTime
      
      this.reportCustomMetric({
        type: 'component_render',
        name: componentName,
        duration,
        timestamp: Date.now()
      })
      
      return duration
    }
  }
  
  // Track route navigation times
  trackRouteNavigation(fromRoute: string, toRoute: string) {
    const startTime = performance.now()
    
    return () => {
      const endTime = performance.now()
      const duration = endTime - startTime
      
      this.reportCustomMetric({
        type: 'route_navigation',
        from: fromRoute,
        to: toRoute,
        duration,
        timestamp: Date.now()
      })
      
      return duration
    }
  }
  
  // Track API call performance
  trackApiCall(endpoint: string, method: string) {
    const startTime = performance.now()
    
    return (status: number, responseSize?: number) => {
      const endTime = performance.now()
      const duration = endTime - startTime
      
      this.reportCustomMetric({
        type: 'api_call',
        endpoint,
        method,
        status,
        duration,
        responseSize,
        timestamp: Date.now()
      })
      
      return duration
    }
  }
  
  // Track user interactions
  trackUserInteraction(action: string, element: string, metadata?: any) {
    this.reportCustomMetric({
      type: 'user_interaction',
      action,
      element,
      metadata,
      timestamp: Date.now(),
      url: window.location.pathname
    })
  }
  
  // Track Element Plus component usage
  trackElementPlusUsage(component: string, props?: any) {
    this.reportCustomMetric({
      type: 'element_plus_usage',
      component,
      props: props ? Object.keys(props) : [],
      timestamp: Date.now(),
      url: window.location.pathname
    })
  }
  
  // Memory usage tracking
  trackMemoryUsage() {
    if ('memory' in performance) {
      const memory = (performance as any).memory
      
      this.reportCustomMetric({
        type: 'memory_usage',
        usedJSHeapSize: memory.usedJSHeapSize,
        totalJSHeapSize: memory.totalJSHeapSize,
        jsHeapSizeLimit: memory.jsHeapSizeLimit,
        timestamp: Date.now()
      })
    }
  }
  
  // Bundle size tracking
  trackBundleSize() {
    if ('getEntriesByType' in performance) {
      const resources = performance.getEntriesByType('resource') as PerformanceResourceTiming[]
      
      const jsResources = resources.filter(resource => 
        resource.name.endsWith('.js') && resource.name.includes('/assets/')
      )
      
      const cssResources = resources.filter(resource => 
        resource.name.endsWith('.css') && resource.name.includes('/assets/')
      )
      
      const totalJSSize = jsResources.reduce((total, resource) => 
        total + (resource.transferSize || 0), 0
      )
      
      const totalCSSSize = cssResources.reduce((total, resource) => 
        total + (resource.transferSize || 0), 0
      )
      
      this.reportCustomMetric({
        type: 'bundle_size',
        jsSize: totalJSSize,
        cssSize: totalCSSSize,
        totalSize: totalJSSize + totalCSSSize,
        jsFiles: jsResources.length,
        cssFiles: cssResources.length,
        timestamp: Date.now()
      })
    }
  }
  
  private reportCustomMetric(metric: any) {
    // Send to analytics service
    fetch('/api/metrics', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(metric)
    }).catch(console.error)
    
    // Log to console in development
    if (process.env.NODE_ENV === 'development') {
      console.log('Custom metric:', metric)
    }
  }
}

// Global instance
export const performanceTracker = new CustomPerformanceTracker()

Error Tracking and Monitoring

Error Boundary Implementation

vue
<!-- src/components/ErrorBoundary.vue -->
<template>
  <div v-if="hasError" class="error-boundary">
    <el-result
      icon="error"
      title="Something went wrong"
      :sub-title="errorMessage"
    >
      <template #extra>
        <el-button type="primary" @click="retry">
          Try Again
        </el-button>
        <el-button @click="reportError">
          Report Issue
        </el-button>
      </template>
    </el-result>
  </div>
  <slot v-else />
</template>

<script setup lang="ts">
import { ref, onErrorCaptured, nextTick } from 'vue'
import { ElResult, ElButton } from 'element-plus'
import { errorReporter } from '@/utils/error-reporter'

interface Props {
  fallback?: string
  onError?: (error: Error, instance: any, info: string) => void
}

const props = withDefaults(defineProps<Props>(), {
  fallback: 'An unexpected error occurred'
})

const hasError = ref(false)
const errorMessage = ref('')
const errorDetails = ref<any>(null)

onErrorCaptured((error: Error, instance: any, info: string) => {
  hasError.value = true
  errorMessage.value = error.message || props.fallback
  errorDetails.value = {
    error: {
      name: error.name,
      message: error.message,
      stack: error.stack
    },
    instance,
    info,
    timestamp: new Date().toISOString(),
    url: window.location.href,
    userAgent: navigator.userAgent
  }
  
  // Report error
  errorReporter.captureException(error, {
    tags: {
      component: 'ErrorBoundary',
      info
    },
    extra: errorDetails.value
  })
  
  // Call custom error handler
  props.onError?.(error, instance, info)
  
  // Prevent the error from propagating
  return false
})

const retry = async () => {
  hasError.value = false
  errorMessage.value = ''
  errorDetails.value = null
  
  await nextTick()
}

const reportError = () => {
  errorReporter.showReportDialog(errorDetails.value)
}
</script>

<style scoped>
.error-boundary {
  padding: 20px;
  min-height: 400px;
  display: flex;
  align-items: center;
  justify-content: center;
}
</style>

Comprehensive Error Reporter

typescript
// src/utils/error-reporter.ts
import { ElMessageBox, ElNotification } from 'element-plus'

export interface ErrorContext {
  tags?: Record<string, string>
  extra?: Record<string, any>
  user?: {
    id: string
    email: string
    name: string
  }
  level?: 'error' | 'warning' | 'info' | 'debug'
}

export class ErrorReporter {
  private userId: string | null = null
  private sessionId: string
  private errorQueue: any[] = []
  private isOnline = navigator.onLine
  
  constructor() {
    this.sessionId = this.generateSessionId()
    this.setupGlobalErrorHandlers()
    this.setupNetworkMonitoring()
  }
  
  private generateSessionId(): string {
    return `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`
  }
  
  private setupGlobalErrorHandlers() {
    // Capture unhandled JavaScript errors
    window.addEventListener('error', (event) => {
      this.captureException(event.error || new Error(event.message), {
        tags: {
          type: 'javascript_error',
          filename: event.filename,
          lineno: event.lineno?.toString(),
          colno: event.colno?.toString()
        }
      })
    })
    
    // Capture unhandled promise rejections
    window.addEventListener('unhandledrejection', (event) => {
      this.captureException(
        event.reason instanceof Error 
          ? event.reason 
          : new Error(String(event.reason)),
        {
          tags: {
            type: 'unhandled_promise_rejection'
          }
        }
      )
    })
    
    // Capture resource loading errors
    window.addEventListener('error', (event) => {
      if (event.target !== window) {
        const target = event.target as HTMLElement
        this.captureMessage('Resource loading error', {
          level: 'error',
          tags: {
            type: 'resource_error',
            tagName: target.tagName,
            src: (target as any).src || (target as any).href
          }
        })
      }
    }, true)
  }
  
  private setupNetworkMonitoring() {
    window.addEventListener('online', () => {
      this.isOnline = true
      this.flushErrorQueue()
    })
    
    window.addEventListener('offline', () => {
      this.isOnline = false
    })
  }
  
  public setUser(user: { id: string; email: string; name: string }) {
    this.userId = user.id
  }
  
  public captureException(error: Error, context: ErrorContext = {}) {
    const errorData = {
      type: 'exception',
      error: {
        name: error.name,
        message: error.message,
        stack: error.stack
      },
      context: {
        ...context,
        timestamp: new Date().toISOString(),
        url: window.location.href,
        userAgent: navigator.userAgent,
        sessionId: this.sessionId,
        userId: this.userId
      },
      breadcrumbs: this.getBreadcrumbs(),
      environment: {
        viewport: {
          width: window.innerWidth,
          height: window.innerHeight
        },
        screen: {
          width: screen.width,
          height: screen.height
        },
        memory: this.getMemoryInfo(),
        connection: this.getConnectionInfo()
      }
    }
    
    this.sendError(errorData)
  }
  
  public captureMessage(message: string, context: ErrorContext = {}) {
    const messageData = {
      type: 'message',
      message,
      level: context.level || 'info',
      context: {
        ...context,
        timestamp: new Date().toISOString(),
        url: window.location.href,
        sessionId: this.sessionId,
        userId: this.userId
      }
    }
    
    this.sendError(messageData)
  }
  
  public addBreadcrumb(message: string, category: string, data?: any) {
    const breadcrumbs = this.getBreadcrumbs()
    breadcrumbs.push({
      message,
      category,
      data,
      timestamp: new Date().toISOString()
    })
    
    // Keep only last 50 breadcrumbs
    if (breadcrumbs.length > 50) {
      breadcrumbs.shift()
    }
    
    localStorage.setItem('error_breadcrumbs', JSON.stringify(breadcrumbs))
  }
  
  private getBreadcrumbs(): any[] {
    try {
      return JSON.parse(localStorage.getItem('error_breadcrumbs') || '[]')
    } catch {
      return []
    }
  }
  
  private getMemoryInfo(): any {
    if ('memory' in performance) {
      const memory = (performance as any).memory
      return {
        usedJSHeapSize: memory.usedJSHeapSize,
        totalJSHeapSize: memory.totalJSHeapSize,
        jsHeapSizeLimit: memory.jsHeapSizeLimit
      }
    }
    return null
  }
  
  private getConnectionInfo(): any {
    if ('connection' in navigator) {
      const connection = (navigator as any).connection
      return {
        effectiveType: connection.effectiveType,
        downlink: connection.downlink,
        rtt: connection.rtt,
        saveData: connection.saveData
      }
    }
    return null
  }
  
  private sendError(errorData: any) {
    if (!this.isOnline) {
      this.errorQueue.push(errorData)
      return
    }
    
    fetch('/api/errors', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json'
      },
      body: JSON.stringify(errorData)
    }).catch((error) => {
      console.error('Failed to send error report:', error)
      this.errorQueue.push(errorData)
    })
  }
  
  private flushErrorQueue() {
    if (this.errorQueue.length === 0) return
    
    const errors = [...this.errorQueue]
    this.errorQueue = []
    
    fetch('/api/errors/batch', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({ errors })
    }).catch((error) => {
      console.error('Failed to flush error queue:', error)
      // Re-add errors to queue
      this.errorQueue.unshift(...errors)
    })
  }
  
  public showReportDialog(errorDetails: any) {
    ElMessageBox.prompt(
      'Please describe what you were doing when this error occurred:',
      'Report Error',
      {
        confirmButtonText: 'Send Report',
        cancelButtonText: 'Cancel',
        inputType: 'textarea',
        inputPlaceholder: 'Describe the steps that led to this error...'
      }
    ).then(({ value }) => {
      this.captureMessage('User reported error', {
        level: 'error',
        extra: {
          userDescription: value,
          errorDetails
        },
        tags: {
          type: 'user_report'
        }
      })
      
      ElNotification({
        title: 'Thank you!',
        message: 'Your error report has been sent.',
        type: 'success'
      })
    }).catch(() => {
      // User cancelled
    })
  }
}

// Global instance
export const errorReporter = new ErrorReporter()

User Analytics and Behavior Tracking

typescript
// src/utils/analytics.ts
export interface AnalyticsEvent {
  name: string
  properties?: Record<string, any>
  timestamp?: number
  userId?: string
  sessionId?: string
}

export class AnalyticsTracker {
  private sessionId: string
  private userId: string | null = null
  private eventQueue: AnalyticsEvent[] = []
  private isInitialized = false
  
  constructor() {
    this.sessionId = this.generateSessionId()
    this.initialize()
  }
  
  private generateSessionId(): string {
    return `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`
  }
  
  private async initialize() {
    // Initialize analytics services
    await this.initializeGoogleAnalytics()
    await this.initializeMixpanel()
    
    this.isInitialized = true
    this.flushEventQueue()
  }
  
  private async initializeGoogleAnalytics() {
    if (typeof gtag !== 'undefined') {
      gtag('config', 'GA_MEASUREMENT_ID', {
        session_id: this.sessionId,
        custom_map: {
          custom_parameter_1: 'element_plus_component',
          custom_parameter_2: 'user_interaction_type'
        }
      })
    }
  }
  
  private async initializeMixpanel() {
    // Initialize Mixpanel if available
    if (typeof mixpanel !== 'undefined') {
      mixpanel.register({
        session_id: this.sessionId,
        app_version: import.meta.env.VITE_APP_VERSION,
        environment: import.meta.env.MODE
      })
    }
  }
  
  public setUserId(userId: string) {
    this.userId = userId
    
    // Set user ID in analytics services
    if (typeof gtag !== 'undefined') {
      gtag('config', 'GA_MEASUREMENT_ID', {
        user_id: userId
      })
    }
    
    if (typeof mixpanel !== 'undefined') {
      mixpanel.identify(userId)
    }
  }
  
  public track(eventName: string, properties: Record<string, any> = {}) {
    const event: AnalyticsEvent = {
      name: eventName,
      properties: {
        ...properties,
        timestamp: Date.now(),
        url: window.location.pathname,
        referrer: document.referrer,
        viewport: {
          width: window.innerWidth,
          height: window.innerHeight
        }
      },
      timestamp: Date.now(),
      userId: this.userId,
      sessionId: this.sessionId
    }
    
    if (!this.isInitialized) {
      this.eventQueue.push(event)
      return
    }
    
    this.sendEvent(event)
  }
  
  // Track Element Plus component usage
  public trackComponentUsage(component: string, action: string, properties: Record<string, any> = {}) {
    this.track('element_plus_component_usage', {
      component,
      action,
      ...properties
    })
  }
  
  // Track page views
  public trackPageView(path: string, title?: string) {
    this.track('page_view', {
      path,
      title: title || document.title,
      timestamp: Date.now()
    })
    
    // Send to Google Analytics
    if (typeof gtag !== 'undefined') {
      gtag('event', 'page_view', {
        page_title: title || document.title,
        page_location: window.location.href,
        page_path: path
      })
    }
  }
  
  // Track user interactions
  public trackInteraction(element: string, action: string, properties: Record<string, any> = {}) {
    this.track('user_interaction', {
      element,
      action,
      ...properties
    })
  }
  
  // Track form submissions
  public trackFormSubmission(formName: string, success: boolean, errors?: string[]) {
    this.track('form_submission', {
      form_name: formName,
      success,
      errors,
      timestamp: Date.now()
    })
  }
  
  // Track search queries
  public trackSearch(query: string, results: number, filters?: Record<string, any>) {
    this.track('search', {
      query,
      results_count: results,
      filters,
      timestamp: Date.now()
    })
  }
  
  // Track feature usage
  public trackFeatureUsage(feature: string, context?: Record<string, any>) {
    this.track('feature_usage', {
      feature,
      context,
      timestamp: Date.now()
    })
  }
  
  private sendEvent(event: AnalyticsEvent) {
    // Send to custom analytics endpoint
    fetch('/api/analytics/events', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json'
      },
      body: JSON.stringify(event)
    }).catch(console.error)
    
    // Send to Google Analytics
    if (typeof gtag !== 'undefined') {
      gtag('event', event.name, event.properties)
    }
    
    // Send to Mixpanel
    if (typeof mixpanel !== 'undefined') {
      mixpanel.track(event.name, event.properties)
    }
  }
  
  private flushEventQueue() {
    const events = [...this.eventQueue]
    this.eventQueue = []
    
    events.forEach(event => this.sendEvent(event))
  }
}

// Global instance
export const analytics = new AnalyticsTracker()

Logging System

Structured Logging

typescript
// src/utils/logger.ts
export enum LogLevel {
  DEBUG = 0,
  INFO = 1,
  WARN = 2,
  ERROR = 3
}

export interface LogEntry {
  level: LogLevel
  message: string
  timestamp: string
  context?: Record<string, any>
  tags?: string[]
  userId?: string
  sessionId?: string
  url?: string
}

export class Logger {
  private logLevel: LogLevel
  private sessionId: string
  private userId: string | null = null
  private logBuffer: LogEntry[] = []
  private maxBufferSize = 1000
  
  constructor(logLevel: LogLevel = LogLevel.INFO) {
    this.logLevel = logLevel
    this.sessionId = this.generateSessionId()
    this.setupPeriodicFlush()
  }
  
  private generateSessionId(): string {
    return `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`
  }
  
  private setupPeriodicFlush() {
    // Flush logs every 30 seconds
    setInterval(() => {
      this.flush()
    }, 30000)
    
    // Flush logs before page unload
    window.addEventListener('beforeunload', () => {
      this.flush()
    })
  }
  
  public setUserId(userId: string) {
    this.userId = userId
  }
  
  public setLogLevel(level: LogLevel) {
    this.logLevel = level
  }
  
  public debug(message: string, context?: Record<string, any>, tags?: string[]) {
    this.log(LogLevel.DEBUG, message, context, tags)
  }
  
  public info(message: string, context?: Record<string, any>, tags?: string[]) {
    this.log(LogLevel.INFO, message, context, tags)
  }
  
  public warn(message: string, context?: Record<string, any>, tags?: string[]) {
    this.log(LogLevel.WARN, message, context, tags)
  }
  
  public error(message: string, context?: Record<string, any>, tags?: string[]) {
    this.log(LogLevel.ERROR, message, context, tags)
  }
  
  private log(level: LogLevel, message: string, context?: Record<string, any>, tags?: string[]) {
    if (level < this.logLevel) return
    
    const logEntry: LogEntry = {
      level,
      message,
      timestamp: new Date().toISOString(),
      context,
      tags,
      userId: this.userId,
      sessionId: this.sessionId,
      url: window.location.href
    }
    
    // Add to buffer
    this.logBuffer.push(logEntry)
    
    // Flush if buffer is full
    if (this.logBuffer.length >= this.maxBufferSize) {
      this.flush()
    }
    
    // Console output in development
    if (import.meta.env.DEV) {
      this.consoleOutput(logEntry)
    }
  }
  
  private consoleOutput(entry: LogEntry) {
    const levelNames = ['DEBUG', 'INFO', 'WARN', 'ERROR']
    const levelColors = ['#888', '#007acc', '#ff8c00', '#ff0000']
    
    const style = `color: ${levelColors[entry.level]}; font-weight: bold;`
    const prefix = `[${levelNames[entry.level]}] ${entry.timestamp}`
    
    console.log(`%c${prefix}`, style, entry.message, entry.context || '')
  }
  
  public flush() {
    if (this.logBuffer.length === 0) return
    
    const logs = [...this.logBuffer]
    this.logBuffer = []
    
    // Send to logging service
    fetch('/api/logs', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({ logs })
    }).catch((error) => {
      console.error('Failed to send logs:', error)
      // Re-add logs to buffer if sending fails
      this.logBuffer.unshift(...logs)
    })
  }
  
  // Specialized logging methods
  public logApiCall(method: string, url: string, status: number, duration: number, error?: Error) {
    const level = status >= 400 ? LogLevel.ERROR : LogLevel.INFO
    this.log(level, `API ${method} ${url}`, {
      method,
      url,
      status,
      duration,
      error: error ? {
        name: error.name,
        message: error.message,
        stack: error.stack
      } : undefined
    }, ['api'])
  }
  
  public logComponentRender(component: string, duration: number, props?: any) {
    this.debug(`Component ${component} rendered`, {
      component,
      duration,
      props: props ? Object.keys(props) : []
    }, ['component', 'render'])
  }
  
  public logRouteChange(from: string, to: string, duration: number) {
    this.info(`Route changed from ${from} to ${to}`, {
      from,
      to,
      duration
    }, ['routing'])
  }
  
  public logUserAction(action: string, element: string, context?: Record<string, any>) {
    this.info(`User ${action} on ${element}`, {
      action,
      element,
      ...context
    }, ['user-action'])
  }
}

// Global logger instance
export const logger = new Logger(
  import.meta.env.DEV ? LogLevel.DEBUG : LogLevel.INFO
)

Real User Monitoring (RUM)

typescript
// src/utils/rum.ts
export class RealUserMonitoring {
  private sessionId: string
  private userId: string | null = null
  private pageLoadTime: number
  private interactions: any[] = []
  
  constructor() {
    this.sessionId = this.generateSessionId()
    this.pageLoadTime = performance.now()
    this.setupMonitoring()
  }
  
  private generateSessionId(): string {
    return `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`
  }
  
  private setupMonitoring() {
    // Monitor page visibility changes
    document.addEventListener('visibilitychange', () => {
      this.trackVisibilityChange()
    })
    
    // Monitor user interactions
    this.setupInteractionTracking()
    
    // Monitor network conditions
    this.setupNetworkMonitoring()
    
    // Monitor device information
    this.trackDeviceInfo()
    
    // Monitor page performance
    this.setupPerformanceMonitoring()
  }
  
  private setupInteractionTracking() {
    const events = ['click', 'scroll', 'keydown', 'touchstart']
    
    events.forEach(eventType => {
      document.addEventListener(eventType, (event) => {
        this.trackInteraction(eventType, event)
      }, { passive: true })
    })
  }
  
  private trackInteraction(type: string, event: Event) {
    const interaction = {
      type,
      timestamp: Date.now(),
      target: this.getElementSelector(event.target as Element),
      viewport: {
        x: window.scrollX,
        y: window.scrollY
      }
    }
    
    this.interactions.push(interaction)
    
    // Keep only last 50 interactions
    if (this.interactions.length > 50) {
      this.interactions.shift()
    }
  }
  
  private getElementSelector(element: Element): string {
    if (!element) return 'unknown'
    
    const id = element.id ? `#${element.id}` : ''
    const classes = element.className ? `.${element.className.split(' ').join('.')}` : ''
    const tag = element.tagName.toLowerCase()
    
    return `${tag}${id}${classes}`
  }
  
  private setupNetworkMonitoring() {
    if ('connection' in navigator) {
      const connection = (navigator as any).connection
      
      const trackConnection = () => {
        this.sendRUMData({
          type: 'network_change',
          effectiveType: connection.effectiveType,
          downlink: connection.downlink,
          rtt: connection.rtt,
          saveData: connection.saveData,
          timestamp: Date.now()
        })
      }
      
      connection.addEventListener('change', trackConnection)
      trackConnection() // Initial state
    }
  }
  
  private trackDeviceInfo() {
    const deviceInfo = {
      type: 'device_info',
      userAgent: navigator.userAgent,
      language: navigator.language,
      platform: navigator.platform,
      cookieEnabled: navigator.cookieEnabled,
      onLine: navigator.onLine,
      screen: {
        width: screen.width,
        height: screen.height,
        colorDepth: screen.colorDepth
      },
      viewport: {
        width: window.innerWidth,
        height: window.innerHeight
      },
      timestamp: Date.now()
    }
    
    this.sendRUMData(deviceInfo)
  }
  
  private setupPerformanceMonitoring() {
    // Monitor long tasks
    if ('PerformanceObserver' in window) {
      try {
        const observer = new PerformanceObserver((list) => {
          list.getEntries().forEach((entry) => {
            this.sendRUMData({
              type: 'long_task',
              duration: entry.duration,
              startTime: entry.startTime,
              timestamp: Date.now()
            })
          })
        })
        
        observer.observe({ entryTypes: ['longtask'] })
      } catch (error) {
        console.warn('Long task monitoring not supported:', error)
      }
    }
    
    // Monitor resource timing
    this.trackResourceTiming()
  }
  
  private trackResourceTiming() {
    const observer = new PerformanceObserver((list) => {
      list.getEntries().forEach((entry) => {
        const resource = entry as PerformanceResourceTiming
        
        this.sendRUMData({
          type: 'resource_timing',
          name: resource.name,
          duration: resource.duration,
          transferSize: resource.transferSize,
          encodedBodySize: resource.encodedBodySize,
          decodedBodySize: resource.decodedBodySize,
          timestamp: Date.now()
        })
      })
    })
    
    observer.observe({ entryTypes: ['resource'] })
  }
  
  private trackVisibilityChange() {
    this.sendRUMData({
      type: 'visibility_change',
      hidden: document.hidden,
      visibilityState: document.visibilityState,
      timestamp: Date.now()
    })
  }
  
  public setUserId(userId: string) {
    this.userId = userId
  }
  
  public trackCustomEvent(name: string, data: any) {
    this.sendRUMData({
      type: 'custom_event',
      name,
      data,
      timestamp: Date.now()
    })
  }
  
  private sendRUMData(data: any) {
    const rumData = {
      ...data,
      sessionId: this.sessionId,
      userId: this.userId,
      url: window.location.href,
      referrer: document.referrer
    }
    
    // Send to RUM endpoint
    fetch('/api/rum', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json'
      },
      body: JSON.stringify(rumData)
    }).catch(console.error)
  }
  
  public getSessionSummary() {
    return {
      sessionId: this.sessionId,
      userId: this.userId,
      sessionDuration: Date.now() - this.pageLoadTime,
      interactionCount: this.interactions.length,
      url: window.location.href,
      timestamp: Date.now()
    }
  }
}

// Global RUM instance
export const rum = new RealUserMonitoring()

Integration with Vue Application

typescript
// src/plugins/monitoring.ts
import type { App } from 'vue'
import { WebVitalsMonitor } from '@/utils/performance-monitor'
import { errorReporter } from '@/utils/error-reporter'
import { analytics } from '@/utils/analytics'
import { logger } from '@/utils/logger'
import { rum } from '@/utils/rum'

export function setupMonitoring(app: App) {
  // Initialize performance monitoring
  const webVitals = new WebVitalsMonitor((metrics) => {
    logger.info('Web Vitals updated', metrics, ['performance'])
  })
  
  // Setup global error handling
  app.config.errorHandler = (error, instance, info) => {
    errorReporter.captureException(error as Error, {
      tags: {
        vue_error_info: info
      },
      extra: {
        componentName: instance?.$options.name || 'Unknown',
        propsData: instance?.$props
      }
    })
  }
  
  // Track route changes
  app.config.globalProperties.$trackRoute = (to: any, from: any) => {
    analytics.trackPageView(to.path, to.meta?.title)
    logger.logRouteChange(from.path, to.path, performance.now())
  }
  
  // Track component usage
  app.config.globalProperties.$trackComponent = (name: string, action: string, props?: any) => {
    analytics.trackComponentUsage(name, action, props)
    logger.logComponentRender(name, performance.now(), props)
  }
  
  // Provide monitoring utilities globally
  app.provide('errorReporter', errorReporter)
  app.provide('analytics', analytics)
  app.provide('logger', logger)
  app.provide('rum', rum)
  
  return {
    webVitals,
    errorReporter,
    analytics,
    logger,
    rum
  }
}

This comprehensive monitoring and logging system provides complete visibility into your Element Plus application's performance, errors, and user behavior in production environments.

Element Plus Study Guide