JavaScript Monitoring & Logging
JavaScript monitoring captures errors, performance metrics, and user behavior in production. Unlike backend monitoring, frontend monitoring must account for diverse browser environments, network conditions, device capabilities, and user interactions. A comprehensive monitoring strategy includes error tracking, performance monitoring, logging, and user session replay.
Without monitoring, bugs that only occur in production (specific browser versions, network conditions, or user flows) go undetected. Monitoring provides the observability needed to debug issues you cannot reproduce locally and to measure the real-world performance impact of your code changes.
Error tracking aggregates and deduplicates JavaScript errors from production users. Services like Sentry, Datadog RUM, and Rollbar capture stack traces, browser metadata, user context, and breadcrumbs leading up to the error. Source maps are uploaded to translate minified stack traces back to original source.
| 1 | // Sentry initialization |
| 2 | import * as Sentry from '@sentry/react'; |
| 3 | |
| 4 | Sentry.init({ |
| 5 | dsn: 'https://your-dsn@sentry.io/project-id', |
| 6 | environment: process.env.NODE_ENV, |
| 7 | release: process.env.COMMIT_SHA, |
| 8 | tracesSampleRate: 0.1, // Performance tracing (10% of transactions) |
| 9 | replaysSessionSampleRate: 0.1, // Session replay (10%) |
| 10 | replaysOnErrorSampleRate: 1.0, // Always capture replay on error |
| 11 | }); |
| 12 | |
| 13 | // Manual error capture |
| 14 | try { |
| 15 | riskyOperation(); |
| 16 | } catch (error) { |
| 17 | Sentry.captureException(error, { |
| 18 | tags: { component: 'CheckoutForm' }, |
| 19 | extra: { cartTotal: 149.99, userId: 'usr_123' }, |
| 20 | }); |
| 21 | } |
| 22 | |
| 23 | // Breadcrumbs — user actions leading to error |
| 24 | Sentry.addBreadcrumb({ |
| 25 | category: 'ui', |
| 26 | message: 'User clicked "Submit Order"', |
| 27 | level: 'info', |
| 28 | }); |
| 29 | |
| 30 | // Set user context for grouping |
| 31 | Sentry.setUser({ id: userId, email: userEmail }); |
Core Web Vitals are the primary performance metrics: Largest Contentful Paint (LCP), First Input Delay (FID) / Interaction to Next Paint (INP), and Cumulative Layout Shift (CLS). The Performance API and web-vitals library provide programmatic access to these metrics for custom monitoring.
| 1 | // Web Vitals — metric reporting |
| 2 | import { onLCP, onFID, onCLS, onINP, onTTFB } from 'web-vitals'; |
| 3 | |
| 4 | function sendToAnalytics(metric) { |
| 5 | const body = { |
| 6 | name: metric.name, |
| 7 | value: metric.value, |
| 8 | rating: metric.rating, // 'good' | 'needs-improvement' | 'poor' |
| 9 | delta: metric.delta, |
| 10 | id: metric.id, // Unique ID for dedup |
| 11 | navigationType: metric.navigationType, |
| 12 | }; |
| 13 | |
| 14 | // Send to your analytics provider |
| 15 | navigator.sendBeacon('/api/vitals', JSON.stringify(body)); |
| 16 | } |
| 17 | |
| 18 | onLCP(sendToAnalytics); |
| 19 | onFID(sendToAnalytics); |
| 20 | onCLS(sendToAnalytics); |
| 21 | onINP(sendToAnalytics); |
| 22 | onTTFB(sendToAnalytics); |
| 23 | |
| 24 | // Custom performance marks for SPA navigation |
| 25 | function trackPageLoad(pageName) { |
| 26 | performance.mark(`${pageName}-start`); |
| 27 | // ... page renders ... |
| 28 | performance.mark(`${pageName}-end`); |
| 29 | performance.measure( |
| 30 | `${pageName}-load`, |
| 31 | `${pageName}-start`, |
| 32 | `${pageName}-end` |
| 33 | ); |
| 34 | const entries = performance.getEntriesByName(`${pageName}-load`); |
| 35 | console.log(`${pageName} loaded in ${entries[0].duration}ms`); |
| 36 | } |
| 37 | |
| 38 | // Long task monitoring |
| 39 | const observer = new PerformanceObserver((list) => { |
| 40 | for (const entry of list.getEntries()) { |
| 41 | if (entry.duration > 50) { |
| 42 | console.warn('Long task detected:', entry.duration, 'ms'); |
| 43 | reportLongTask(entry); |
| 44 | } |
| 45 | } |
| 46 | }); |
| 47 | observer.observe({ type: 'longtask', buffered: true }); |
Structured logging outputs JSON-formatted log entries with consistent fields, making them queryable in log management systems (Datadog, Grafana, ELK). In the browser, use structured log levels and include context like timestamps, user IDs, and action names. In Node.js, structured logging is essential for production observability.
| 1 | // Structured logger utility |
| 2 | const LOG_LEVELS = { |
| 3 | debug: 0, info: 1, warn: 2, error: 3, |
| 4 | }; |
| 5 | |
| 6 | class Logger { |
| 7 | constructor(context = {}) { |
| 8 | this.context = context; |
| 9 | } |
| 10 | |
| 11 | _log(level, message, data = {}) { |
| 12 | if (LOG_LEVELS[level] < LOG_LEVELS[this.context.level || 'info']) { |
| 13 | return; |
| 14 | } |
| 15 | const entry = { |
| 16 | timestamp: new Date().toISOString(), |
| 17 | level, |
| 18 | message, |
| 19 | ...this.context, |
| 20 | ...data, |
| 21 | }; |
| 22 | // In production, batch and send to logging endpoint |
| 23 | if (level === 'error') { |
| 24 | console.error(JSON.stringify(entry)); |
| 25 | } else if (level === 'warn') { |
| 26 | console.warn(JSON.stringify(entry)); |
| 27 | } else { |
| 28 | console.log(JSON.stringify(entry)); |
| 29 | } |
| 30 | // Network batching for production |
| 31 | this._buffer.push(entry); |
| 32 | } |
| 33 | |
| 34 | child(childContext) { |
| 35 | return new Logger({ ...this.context, ...childContext }); |
| 36 | } |
| 37 | } |
| 38 | |
| 39 | const logger = new Logger({ service: 'web-app', version: '1.2.0' }); |
| 40 | logger.info('User action', { action: 'checkout_start', cartValue: 59.99 }); |
| 41 | logger.error('Payment failed', { error: 'card_declined', code: 'declined_01' }); |
- Error tracking with source maps is essential for debugging minified production code
- Monitor Core Web Vitals (LCP, INP, CLS) for real-user performance data
- Use structured logging with consistent fields for queryability
- Session replay captures the user's viewport leading up to an error
- Set up performance budgets and alerts to catch regressions before they reach users
Community
Get help on Slack, Discord or VIP
Stuck on a guide? Join the community and ask.