活动公告

系统通知
05-18 21:22
系统通知
通知:本站资源由网友上传分享,如有违规等问题请到版务模块进行投诉,资源失效请在帖子内回复要求补档,会尽快处理!
10-23 09:31

JavaScript日志输出完全指南从基础consolelog到高级日志管理技术详解调试技巧错误追踪性能优化生产环境应用与日志分析工具使用提升开发效率与代码质量

SunJu_FaceMall

3万

主题

2860

科技点

3万

积分

白金月票

碾压王

积分
32872

塔罗立华奏

<font color=白金月票" /> 发表于 2025-9-2 20:20:01 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

x
引言

在JavaScript开发过程中,日志输出是一项不可或缺的技术。无论是简单的调试、复杂的错误追踪,还是生产环境中的问题排查,日志都扮演着至关重要的角色。本文将全面介绍JavaScript日志输出的各个方面,从基础的console.log方法到高级的日志管理技术,帮助开发者掌握调试技巧、错误追踪、性能优化和生产环境应用,最终提升开发效率和代码质量。

基础日志输出:console.log及其相关方法

console.log基础

console.log()是JavaScript中最基础也是最常用的日志输出方法。它可以将信息输出到浏览器的控制台或Node.js的终端中。
  1. console.log("Hello, World!"); // 输出字符串
  2. console.log(42); // 输出数字
  3. console.log({name: "John", age: 30}); // 输出对象
  4. console.log([1, 2, 3]); // 输出数组
复制代码

console.log()可以接受多个参数,它们会以空格分隔的形式输出:
  1. const name = "Alice";
  2. const age = 25;
  3. console.log("Name:", name, "Age:", age); // 输出: Name: Alice Age: 25
复制代码

其他console方法

除了console.log(),Console API还提供了许多其他有用的方法:

1. console.info():用于输出信息性消息,在大多数浏览器中与console.log()行为相同。
  1. console.info("This is an informational message");
复制代码

1. console.warn():用于输出警告信息,通常会有黄色背景或警告图标。
  1. console.warn("This is a warning message");
复制代码

1. console.error():用于输出错误信息,通常会有红色背景或错误图标。
  1. console.error("This is an error message");
复制代码

1. console.debug():用于输出调试信息,在默认情况下可能不可见,需要在控制台设置中启用。
  1. console.debug("This is a debug message");
复制代码

1. console.table():以表格形式显示数据,特别适合显示数组或对象数组。
  1. const users = [
  2.     {name: "John", age: 30, city: "New York"},
  3.     {name: "Alice", age: 25, city: "Los Angeles"},
  4.     {name: "Bob", age: 35, city: "Chicago"}
  5. ];
  6. console.table(users);
复制代码

1. console.time()和console.timeEnd():用于测量代码执行时间。
  1. console.time("Operation");
  2. // 执行一些操作
  3. for (let i = 0; i < 1000000; i++) {
  4.     Math.sqrt(i);
  5. }
  6. console.timeEnd("Operation"); // 输出: Operation: 12.345ms
复制代码

1. console.count():用于计数,可以标记某个代码段被执行的次数。
  1. for (let i = 0; i < 5; i++) {
  2.     console.count("Loop executed");
  3. }
  4. // 输出:
  5. // Loop executed: 1
  6. // Loop executed: 2
  7. // Loop executed: 3
  8. // Loop executed: 4
  9. // Loop executed: 5
复制代码

1. console.group()和console.groupEnd():用于将相关日志分组显示,使输出更有层次。
  1. console.group("User Details");
  2. console.log("Name: John");
  3. console.log("Age: 30");
  4. console.group("Address");
  5. console.log("Street: 123 Main St");
  6. console.log("City: New York");
  7. console.groupEnd();
  8. console.groupEnd();
复制代码

1. console.trace():输出当前调用堆栈的跟踪信息。
  1. function functionA() {
  2.     functionB();
  3. }
  4. function functionB() {
  5.     functionC();
  6. }
  7. function functionC() {
  8.     console.trace();
  9. }
  10. functionA();
复制代码

1. console.assert():当断言为false时输出错误信息。
  1. const value = 10;
  2. console.assert(value > 20, "Value is not greater than 20"); // 输出: Assertion failed: Value is not greater than 20
复制代码

格式化输出

console.log()支持格式化字符串,使用占位符来插入变量值:
  1. const name = "Alice";
  2. const age = 25;
  3. console.log("Name: %s, Age: %d", name, age); // 输出: Name: Alice, Age: 25
复制代码

支持的格式占位符包括:

• %s:字符串
• %d或%i:整数
• %f:浮点数
• %o:对象(可展开的DOM元素)
• %O:对象(可展开的JavaScript对象)
• %c:CSS样式

使用CSS样式:
  1. console.log("%cThis is a styled message", "color: blue; font-size: 20px; font-weight: bold;");
复制代码

高级日志管理技术

自定义日志系统

为了更好地控制日志输出,我们可以创建一个自定义的日志系统。这样的系统可以提供日志级别、格式化、过滤等功能。
  1. class Logger {
  2.     constructor(level = 'info') {
  3.         this.levels = {
  4.             error: 0,
  5.             warn: 1,
  6.             info: 2,
  7.             debug: 3
  8.         };
  9.         this.level = level;
  10.     }
  11.     setLevel(level) {
  12.         if (this.levels.hasOwnProperty(level)) {
  13.             this.level = level;
  14.         } else {
  15.             console.error(`Invalid log level: ${level}`);
  16.         }
  17.     }
  18.     _log(level, ...args) {
  19.         if (this.levels[level] <= this.levels[this.level]) {
  20.             const timestamp = new Date().toISOString();
  21.             console[level](`[${timestamp}] [${level.toUpperCase()}]`, ...args);
  22.         }
  23.     }
  24.     error(...args) {
  25.         this._log('error', ...args);
  26.     }
  27.     warn(...args) {
  28.         this._log('warn', ...args);
  29.     }
  30.     info(...args) {
  31.         this._log('info', ...args);
  32.     }
  33.     debug(...args) {
  34.         this._log('debug', ...args);
  35.     }
  36. }
  37. // 使用示例
  38. const logger = new Logger('debug');
  39. logger.error('This is an error message');
  40. logger.warn('This is a warning message');
  41. logger.info('This is an info message');
  42. logger.debug('This is a debug message');
  43. // 更改日志级别
  44. logger.setLevel('warn');
  45. logger.debug('This debug message will not be displayed');
  46. logger.warn('This warning message will still be displayed');
复制代码

日志级别管理

日志级别允许我们根据重要性过滤日志消息。常见的日志级别包括:

1. ERROR:严重错误,导致应用程序无法正常运行
2. WARN:潜在问题,不会阻止应用程序运行但需要注意
3. INFO:一般信息,表示应用程序正常运行
4. DEBUG:调试信息,通常仅在开发环境中使用

在生产环境中,我们通常将日志级别设置为WARN或ERROR,以减少日志量并提高性能。

日志格式化

统一的日志格式可以使日志更易于阅读和分析。我们可以扩展自定义日志系统以支持格式化:
  1. class FormattedLogger extends Logger {
  2.     constructor(level = 'info', options = {}) {
  3.         super(level);
  4.         this.options = {
  5.             showTimestamp: options.showTimestamp !== false,
  6.             showLevel: options.showLevel !== false,
  7.             template: options.template || '[{timestamp}] [{level}] {message}'
  8.         };
  9.     }
  10.     _formatMessage(level, message) {
  11.         let formatted = this.options.template;
  12.         
  13.         if (this.options.showTimestamp) {
  14.             formatted = formatted.replace('{timestamp}', new Date().toISOString());
  15.         }
  16.         
  17.         if (this.options.showLevel) {
  18.             formatted = formatted.replace('{level}', level.toUpperCase());
  19.         }
  20.         
  21.         formatted = formatted.replace('{message}', message);
  22.         
  23.         return formatted;
  24.     }
  25.     _log(level, ...args) {
  26.         if (this.levels[level] <= this.levels[this.level]) {
  27.             const formattedMessage = this._formatMessage(level, args.join(' '));
  28.             console[level](formattedMessage);
  29.         }
  30.     }
  31. }
  32. // 使用示例
  33. const formattedLogger = new FormattedLogger('debug', {
  34.     template: '[{timestamp}] [{level}] => {message}'
  35. });
  36. formattedLogger.info('Application started');
  37. formattedLogger.warn('Low disk space');
  38. formattedLogger.error('Database connection failed');
复制代码

日志持久化

在浏览器环境中,我们可以使用localStorage或IndexedDB来持久化日志:
  1. class PersistentLogger extends FormattedLogger {
  2.     constructor(level = 'info', options = {}) {
  3.         super(level, options);
  4.         this.maxLogs = options.maxLogs || 1000;
  5.         this.storageKey = options.storageKey || 'app_logs';
  6.         this._loadLogs();
  7.     }
  8.     _loadLogs() {
  9.         try {
  10.             const storedLogs = localStorage.getItem(this.storageKey);
  11.             this.logs = storedLogs ? JSON.parse(storedLogs) : [];
  12.         } catch (e) {
  13.             console.error('Failed to load logs from storage:', e);
  14.             this.logs = [];
  15.         }
  16.     }
  17.     _saveLogs() {
  18.         try {
  19.             localStorage.setItem(this.storageKey, JSON.stringify(this.logs));
  20.         } catch (e) {
  21.             console.error('Failed to save logs to storage:', e);
  22.         }
  23.     }
  24.     _log(level, ...args) {
  25.         super._log(level, ...args);
  26.         
  27.         const logEntry = {
  28.             timestamp: new Date().toISOString(),
  29.             level: level,
  30.             message: args.join(' ')
  31.         };
  32.         
  33.         this.logs.push(logEntry);
  34.         
  35.         // 保持日志数量不超过最大值
  36.         if (this.logs.length > this.maxLogs) {
  37.             this.logs = this.logs.slice(-this.maxLogs);
  38.         }
  39.         
  40.         this._saveLogs();
  41.     }
  42.     getLogs(level) {
  43.         if (level) {
  44.             return this.logs.filter(log => log.level === level);
  45.         }
  46.         return this.logs;
  47.     }
  48.     clearLogs() {
  49.         this.logs = [];
  50.         this._saveLogs();
  51.     }
  52. }
  53. // 使用示例
  54. const persistentLogger = new PersistentLogger('debug');
  55. persistentLogger.info('Application started');
  56. persistentLogger.warn('Low disk space');
  57. persistentLogger.error('Database connection failed');
  58. // 获取所有日志
  59. console.log(persistentLogger.getLogs());
  60. // 仅获取错误日志
  61. console.log(persistentLogger.getLogs('error'));
复制代码

远程日志记录

在生产环境中,将日志发送到远程服务器进行集中管理和分析是非常有用的:
  1. class RemoteLogger extends PersistentLogger {
  2.     constructor(level = 'info', options = {}) {
  3.         super(level, options);
  4.         this.remoteUrl = options.remoteUrl;
  5.         this.batchSize = options.batchSize || 10;
  6.         this.flushInterval = options.flushInterval || 10000; // 10秒
  7.         this.pendingLogs = [];
  8.         
  9.         if (this.remoteUrl) {
  10.             setInterval(() => this.flush(), this.flushInterval);
  11.         }
  12.     }
  13.     _log(level, ...args) {
  14.         super._log(level, ...args);
  15.         
  16.         if (this.remoteUrl) {
  17.             const logEntry = {
  18.                 timestamp: new Date().toISOString(),
  19.                 level: level,
  20.                 message: args.join(' '),
  21.                 userAgent: navigator.userAgent,
  22.                 url: window.location.href
  23.             };
  24.             
  25.             this.pendingLogs.push(logEntry);
  26.             
  27.             if (this.pendingLogs.length >= this.batchSize) {
  28.                 this.flush();
  29.             }
  30.         }
  31.     }
  32.     async flush() {
  33.         if (this.pendingLogs.length === 0) return;
  34.         
  35.         const logsToSend = [...this.pendingLogs];
  36.         this.pendingLogs = [];
  37.         
  38.         try {
  39.             const response = await fetch(this.remoteUrl, {
  40.                 method: 'POST',
  41.                 headers: {
  42.                     'Content-Type': 'application/json'
  43.                 },
  44.                 body: JSON.stringify({ logs: logsToSend })
  45.             });
  46.             
  47.             if (!response.ok) {
  48.                 throw new Error(`HTTP error! status: ${response.status}`);
  49.             }
  50.         } catch (e) {
  51.             console.error('Failed to send logs to remote server:', e);
  52.             // 如果发送失败,将日志重新放回待发送队列
  53.             this.pendingLogs = [...logsToSend, ...this.pendingLogs];
  54.         }
  55.     }
  56. }
  57. // 使用示例
  58. const remoteLogger = new RemoteLogger('debug', {
  59.     remoteUrl: 'https://api.example.com/logs',
  60.     batchSize: 5,
  61.     flushInterval: 5000
  62. });
  63. remoteLogger.info('Application started');
  64. remoteLogger.warn('Low disk space');
  65. remoteLogger.error('Database connection failed');
复制代码

调试技巧

条件日志记录

条件日志记录允许我们只在特定条件下输出日志,这在调试复杂问题时非常有用:
  1. function debugLog(condition, ...args) {
  2.     if (condition) {
  3.         console.log(...args);
  4.     }
  5. }
  6. // 使用示例
  7. const debugMode = true;
  8. const user = { name: 'Alice', role: 'admin' };
  9. debugLog(debugMode, 'Current user:', user);
  10. debugLog(user.role === 'admin', 'Admin action performed');
复制代码

命名空间日志

使用命名空间可以帮助我们更好地组织和过滤日志:
  1. const createNamespaceLogger = (namespace) => {
  2.     return {
  3.         log: (...args) => console.log(`[${namespace}]`, ...args),
  4.         info: (...args) => console.info(`[${namespace}]`, ...args),
  5.         warn: (...args) => console.warn(`[${namespace}]`, ...args),
  6.         error: (...args) => console.error(`[${namespace}]`, ...args),
  7.         debug: (...args) => console.debug(`[${namespace}]`, ...args)
  8.     };
  9. };
  10. // 使用示例
  11. const authLogger = createNamespaceLogger('AUTH');
  12. const dbLogger = createNamespaceLogger('DATABASE');
  13. const uiLogger = createNamespaceLogger('UI');
  14. authLogger.info('User logged in');
  15. dbLogger.warn('Slow query detected');
  16. uiLogger.error('Failed to render component');
复制代码

性能分析日志

使用日志来分析代码性能:
  1. function analyzePerformance(fn, ...args) {
  2.     console.time(`Performance analysis: ${fn.name}`);
  3.     const result = fn(...args);
  4.     console.timeEnd(`Performance analysis: ${fn.name}`);
  5.     return result;
  6. }
  7. // 使用示例
  8. function fibonacci(n) {
  9.     if (n <= 1) return n;
  10.     return fibonacci(n - 1) + fibonacci(n - 2);
  11. }
  12. const result = analyzePerformance(fibonacci, 30);
  13. console.log('Result:', result);
复制代码

状态变化日志

记录应用程序状态的变化,有助于理解数据流:
  1. class StateLogger {
  2.     constructor(initialState) {
  3.         this.state = initialState;
  4.         this.logState('Initial state');
  5.     }
  6.     logState(action) {
  7.         console.log(`[${action}] State:`, JSON.parse(JSON.stringify(this.state)));
  8.     }
  9.     updateState(updates, action = 'State update') {
  10.         this.state = { ...this.state, ...updates };
  11.         this.logState(action);
  12.         return this.state;
  13.     }
  14. }
  15. // 使用示例
  16. const appState = new StateLogger({ user: null, isLoading: false });
  17. appState.updateState({ isLoading: true }, 'Fetching user data');
  18. appState.updateState({ user: { name: 'Alice' }, isLoading: false }, 'User data fetched');
复制代码

事件流日志

记录事件流可以帮助我们理解应用程序的交互流程:
  1. function createEventLogger() {
  2.     const events = [];
  3.    
  4.     return {
  5.         log: (event, data) => {
  6.             const timestamp = new Date().toISOString();
  7.             events.push({ event, data, timestamp });
  8.             console.log(`[EVENT] ${event}:`, data);
  9.         },
  10.         getEvents: () => events,
  11.         clear: () => { events.length = 0; }
  12.     };
  13. }
  14. // 使用示例
  15. const eventLogger = createEventLogger();
  16. // 模拟用户操作
  17. eventLogger.log('page_load', { page: 'home' });
  18. eventLogger.log('button_click', { button: 'login' });
  19. eventLogger.log('form_submit', { form: 'login', data: { username: 'alice' } });
  20. eventLogger.log('api_request', { endpoint: '/login', method: 'POST' });
  21. eventLogger.log('api_response', { status: 200, data: { token: 'abc123' } });
  22. eventLogger.log('navigation', { from: 'login', to: 'dashboard' });
  23. // 获取所有事件
  24. console.log(eventLogger.getEvents());
复制代码

错误追踪

错误日志记录

正确记录错误信息对于调试和修复问题至关重要:
  1. function logError(error, context = {}) {
  2.     console.error('Error occurred:', {
  3.         message: error.message,
  4.         stack: error.stack,
  5.         context: context,
  6.         timestamp: new Date().toISOString(),
  7.         userAgent: navigator.userAgent,
  8.         url: window.location.href
  9.     });
  10. }
  11. // 使用示例
  12. try {
  13.     // 可能出错的代码
  14.     JSON.parse('{invalid json}');
  15. } catch (error) {
  16.     logError(error, {
  17.         action: 'parseJSON',
  18.         input: '{invalid json}'
  19.     });
  20. }
复制代码

全局错误处理

设置全局错误处理器来捕获未处理的错误:
  1. // 全局JavaScript错误处理
  2. window.addEventListener('error', (event) => {
  3.     console.error('Global error caught:', {
  4.         message: event.message,
  5.         filename: event.filename,
  6.         lineno: event.lineno,
  7.         colno: event.colno,
  8.         error: event.error,
  9.         timestamp: new Date().toISOString()
  10.     });
  11.    
  12.     // 可以在这里添加将错误发送到服务器的代码
  13. });
  14. // 未处理的Promise拒绝处理
  15. window.addEventListener('unhandledrejection', (event) => {
  16.     console.error('Unhandled promise rejection:', {
  17.         reason: event.reason,
  18.         timestamp: new Date().toISOString()
  19.     });
  20.    
  21.     // 可以在这里添加将错误发送到服务器的代码
  22. });
  23. // 使用示例
  24. // 触发全局错误处理
  25. setTimeout(() => {
  26.     undefinedFunction(); // 未定义的函数
  27. }, 1000);
  28. // 触发未处理的Promise拒绝
  29. setTimeout(() => {
  30.     Promise.reject(new Error('Intentional rejection'));
  31. }, 2000);
复制代码

错误边界(React)

在React应用中,使用错误边界来捕获组件树中的JavaScript错误:
  1. import React from 'react';
  2. class ErrorBoundary extends React.Component {
  3.     constructor(props) {
  4.         super(props);
  5.         this.state = { hasError: false, error: null, errorInfo: null };
  6.     }
  7.     static getDerivedStateFromError(error) {
  8.         // 更新state以触发下一次渲染显示降级UI
  9.         return { hasError: true };
  10.     }
  11.     componentDidCatch(error, errorInfo) {
  12.         // 记录错误信息
  13.         this.setState({
  14.             error: error,
  15.             errorInfo: errorInfo
  16.         });
  17.         
  18.         // 可以在这里将错误日志发送到服务器
  19.         console.error('Error caught by boundary:', error, errorInfo);
  20.     }
  21.     render() {
  22.         if (this.state.hasError) {
  23.             // 自定义降级UI
  24.             return (
  25.                 <div>
  26.                     <h2>Something went wrong.</h2>
  27.                     <details style={{ whiteSpace: 'pre-wrap' }}>
  28.                         {this.state.error && this.state.error.toString()}
  29.                         <br />
  30.                         {this.state.errorInfo.componentStack}
  31.                     </details>
  32.                 </div>
  33.             );
  34.         }
  35.         return this.props.children;
  36.     }
  37. }
  38. // 使用示例
  39. function BuggyComponent() {
  40.     // 这将触发错误
  41.     return <>{undefinedVariable}</>;
  42. }
  43. function App() {
  44.     return (
  45.         <ErrorBoundary>
  46.             <h1>My App</h1>
  47.             <BuggyComponent />
  48.         </ErrorBoundary>
  49.     );
  50. }
复制代码

错误报告系统

创建一个错误报告系统,收集和发送错误信息:
  1. class ErrorReporter {
  2.     constructor(options = {}) {
  3.         this.url = options.url;
  4.         this.appVersion = options.appVersion || 'unknown';
  5.         this.environment = options.environment || 'development';
  6.         this.setupGlobalHandlers();
  7.     }
  8.     setupGlobalHandlers() {
  9.         window.addEventListener('error', (event) => {
  10.             this.reportError(event.error, {
  11.                 type: 'global_error',
  12.                 filename: event.filename,
  13.                 lineno: event.lineno,
  14.                 colno: event.colno
  15.             });
  16.         });
  17.         window.addEventListener('unhandledrejection', (event) => {
  18.             this.reportError(event.reason, {
  19.                 type: 'unhandled_promise_rejection'
  20.             });
  21.         });
  22.     }
  23.     reportError(error, context = {}) {
  24.         const errorReport = {
  25.             message: error.message || String(error),
  26.             stack: error.stack,
  27.             context: {
  28.                 ...context,
  29.                 appVersion: this.appVersion,
  30.                 environment: this.environment,
  31.                 userAgent: navigator.userAgent,
  32.                 url: window.location.href,
  33.                 timestamp: new Date().toISOString()
  34.             }
  35.         };
  36.         console.error('Error reported:', errorReport);
  37.         if (this.url) {
  38.             this.sendToServer(errorReport);
  39.         }
  40.     }
  41.     async sendToServer(errorReport) {
  42.         try {
  43.             const response = await fetch(this.url, {
  44.                 method: 'POST',
  45.                 headers: {
  46.                     'Content-Type': 'application/json'
  47.                 },
  48.                 body: JSON.stringify(errorReport)
  49.             });
  50.             if (!response.ok) {
  51.                 throw new Error(`HTTP error! status: ${response.status}`);
  52.             }
  53.         } catch (e) {
  54.             console.error('Failed to send error report:', e);
  55.         }
  56.     }
  57. }
  58. // 使用示例
  59. const errorReporter = new ErrorReporter({
  60.     url: 'https://api.example.com/errors',
  61.     appVersion: '1.0.0',
  62.     environment: 'production'
  63. });
  64. // 手动报告错误
  65. try {
  66.     // 可能出错的代码
  67.     JSON.parse('{invalid json}');
  68. } catch (error) {
  69.     errorReporter.reportError(error, {
  70.         action: 'parseJSON',
  71.         input: '{invalid json}'
  72.     });
  73. }
复制代码

性能优化

性能监控日志

使用日志来监控应用程序的性能:
  1. class PerformanceMonitor {
  2.     constructor() {
  3.         this.metrics = {};
  4.     }
  5.     startTimer(name) {
  6.         this.metrics[name] = {
  7.             startTime: performance.now(),
  8.             endTime: null,
  9.             duration: null
  10.         };
  11.     }
  12.     endTimer(name) {
  13.         if (this.metrics[name] && !this.metrics[name].endTime) {
  14.             this.metrics[name].endTime = performance.now();
  15.             this.metrics[name].duration =
  16.                 this.metrics[name].endTime - this.metrics[name].startTime;
  17.             
  18.             console.log(`[PERFORMANCE] ${name}: ${this.metrics[name].duration.toFixed(2)}ms`);
  19.             
  20.             return this.metrics[name].duration;
  21.         }
  22.         return null;
  23.     }
  24.     getMetric(name) {
  25.         return this.metrics[name];
  26.     }
  27.     getAllMetrics() {
  28.         return this.metrics;
  29.     }
  30. }
  31. // 使用示例
  32. const perfMonitor = new PerformanceMonitor();
  33. // 监控函数执行时间
  34. function fetchData() {
  35.     perfMonitor.startTimer('fetchData');
  36.    
  37.     // 模拟API调用
  38.     return new Promise(resolve => {
  39.         setTimeout(() => {
  40.             perfMonitor.endTimer('fetchData');
  41.             resolve({ data: 'example data' });
  42.         }, 500);
  43.     });
  44. }
  45. fetchData().then(data => {
  46.     console.log('Data received:', data);
  47.     console.log('All metrics:', perfMonitor.getAllMetrics());
  48. });
复制代码

资源加载监控

监控资源加载时间:
  1. function monitorResourceLoading() {
  2.     // 使用Performance API获取资源加载时间
  3.     window.addEventListener('load', () => {
  4.         const resources = performance.getEntriesByType('resource');
  5.         
  6.         console.group('Resource Loading Performance');
  7.         resources.forEach(resource => {
  8.             const duration = resource.duration.toFixed(2);
  9.             const size = (resource.transferSize / 1024).toFixed(2);
  10.             
  11.             if (duration > 100 || size > 100) { // 只显示加载时间超过100ms或大小超过100KB的资源
  12.                 console.warn(`Slow or large resource: ${resource.name}`, {
  13.                     type: resource.initiatorType,
  14.                     duration: `${duration}ms`,
  15.                     size: `${size}KB`
  16.                 });
  17.             }
  18.         });
  19.         console.groupEnd();
  20.     });
  21. }
  22. // 使用示例
  23. monitorResourceLoading();
复制代码

用户交互性能

监控用户交互的性能:
  1. function measureInteractionPerformance() {
  2.     // 使用Event Timing API
  3.     const observer = new PerformanceObserver((list) => {
  4.         for (const entry of list.getEntries()) {
  5.             const delay = entry.processingStart - entry.startTime;
  6.             const duration = entry.duration;
  7.             
  8.             console.log(`[INTERACTION] ${entry.name}:`, {
  9.                 delay: `${delay.toFixed(2)}ms`,
  10.                 duration: `${duration.toFixed(2)}ms`
  11.             });
  12.             
  13.             // 如果交互延迟超过100ms,记录警告
  14.             if (delay > 100) {
  15.                 console.warn(`Slow interaction detected: ${entry.name}`, {
  16.                     delay: `${delay.toFixed(2)}ms`,
  17.                     target: entry.target
  18.                 });
  19.             }
  20.         }
  21.     });
  22.    
  23.     observer.observe({ entryTypes: ['event'] });
  24. }
  25. // 使用示例
  26. measureInteractionPerformance();
  27. // 添加一些交互元素来测试
  28. document.addEventListener('DOMContentLoaded', () => {
  29.     const button = document.createElement('button');
  30.     button.textContent = 'Click me';
  31.     button.addEventListener('click', () => {
  32.         // 模拟一些处理
  33.         const start = performance.now();
  34.         while (performance.now() - start < 50) {
  35.             // 空循环,模拟处理时间
  36.         }
  37.         console.log('Button clicked');
  38.     });
  39.     document.body.appendChild(button);
  40. });
复制代码

内存使用监控

监控内存使用情况:
  1. function monitorMemoryUsage() {
  2.     if (performance.memory) {
  3.         const memoryData = {
  4.             used: `${(performance.memory.usedJSHeapSize / 1048576).toFixed(2)} MB`,
  5.             total: `${(performance.memory.totalJSHeapSize / 1048576).toFixed(2)} MB`,
  6.             limit: `${(performance.memory.jsHeapSizeLimit / 1048576).toFixed(2)} MB`,
  7.             usage: `${((performance.memory.usedJSHeapSize / performance.memory.jsHeapSizeLimit) * 100).toFixed(2)}%`
  8.         };
  9.         
  10.         console.log('[MEMORY USAGE]', memoryData);
  11.         
  12.         // 如果内存使用超过80%,发出警告
  13.         if (parseFloat(memoryData.usage) > 80) {
  14.             console.warn('High memory usage detected:', memoryData);
  15.         }
  16.         
  17.         return memoryData;
  18.     } else {
  19.         console.log('Memory monitoring not supported in this browser');
  20.         return null;
  21.     }
  22. }
  23. // 使用示例
  24. // 每隔5秒监控一次内存使用
  25. setInterval(monitorMemoryUsage, 5000);
复制代码

生产环境应用

生产环境日志最佳实践

在生产环境中,日志记录需要遵循一些最佳实践:

1. 适当的日志级别:在生产环境中,通常只记录WARN和ERROR级别的日志,以减少性能影响。
2. 避免敏感信息:确保日志中不包含敏感信息,如密码、令牌、个人身份信息等。
3. 日志轮转:实现日志轮转机制,防止日志文件过大。
4. 结构化日志:使用结构化格式(如JSON)记录日志,便于后续分析。
5. 异步日志:使用异步方式记录日志,减少对主线程性能的影响。

适当的日志级别:在生产环境中,通常只记录WARN和ERROR级别的日志,以减少性能影响。

避免敏感信息:确保日志中不包含敏感信息,如密码、令牌、个人身份信息等。

日志轮转:实现日志轮转机制,防止日志文件过大。

结构化日志:使用结构化格式(如JSON)记录日志,便于后续分析。

异步日志:使用异步方式记录日志,减少对主线程性能的影响。
  1. class ProductionLogger {
  2.     constructor(options = {}) {
  3.         this.level = options.level || 'warn';
  4.         this.levels = { error: 0, warn: 1, info: 2, debug: 3 };
  5.         this.remoteUrl = options.remoteUrl;
  6.         this.sanitizeFields = options.sanitizeFields || ['password', 'token', 'creditCard'];
  7.         this.batchSize = options.batchSize || 10;
  8.         this.pendingLogs = [];
  9.         this.flushInterval = options.flushInterval || 10000;
  10.         
  11.         if (this.remoteUrl) {
  12.             setInterval(() => this.flush(), this.flushInterval);
  13.         }
  14.     }
  15.     sanitize(data) {
  16.         if (!data || typeof data !== 'object') return data;
  17.         
  18.         const sanitized = { ...data };
  19.         
  20.         for (const field in sanitized) {
  21.             if (this.sanitizeFields.includes(field)) {
  22.                 sanitized[field] = '[REDACTED]';
  23.             } else if (typeof sanitized[field] === 'object' && sanitized[field] !== null) {
  24.                 sanitized[field] = this.sanitize(sanitized[field]);
  25.             }
  26.         }
  27.         
  28.         return sanitized;
  29.     }
  30.     _log(level, message, data = {}) {
  31.         if (this.levels[level] > this.levels[this.level]) return;
  32.         
  33.         const logEntry = {
  34.             timestamp: new Date().toISOString(),
  35.             level: level,
  36.             message: message,
  37.             data: this.sanitize(data),
  38.             userAgent: navigator.userAgent,
  39.             url: window.location.href
  40.         };
  41.         
  42.         // 在开发环境中,也输出到控制台
  43.         if (process.env.NODE_ENV !== 'production') {
  44.             console[level](`[${level.toUpperCase()}] ${message}`, logEntry.data);
  45.         }
  46.         
  47.         // 添加到待发送队列
  48.         this.pendingLogs.push(logEntry);
  49.         
  50.         if (this.pendingLogs.length >= this.batchSize) {
  51.             this.flush();
  52.         }
  53.     }
  54.     async flush() {
  55.         if (this.pendingLogs.length === 0 || !this.remoteUrl) return;
  56.         
  57.         const logsToSend = [...this.pendingLogs];
  58.         this.pendingLogs = [];
  59.         
  60.         try {
  61.             // 使用navigator.sendBeacon进行非阻塞请求
  62.             const data = JSON.stringify({ logs: logsToSend });
  63.             const blob = new Blob([data], { type: 'application/json' });
  64.             
  65.             if (navigator.sendBeacon(this.remoteUrl, blob)) {
  66.                 console.log(`${logsToSend.length} logs sent successfully`);
  67.             } else {
  68.                 // 如果sendBeacon不可用,回退到fetch
  69.                 const response = await fetch(this.remoteUrl, {
  70.                     method: 'POST',
  71.                     headers: { 'Content-Type': 'application/json' },
  72.                     body: data,
  73.                     keepalive: true
  74.                 });
  75.                
  76.                 if (!response.ok) {
  77.                     throw new Error(`HTTP error! status: ${response.status}`);
  78.                 }
  79.             }
  80.         } catch (e) {
  81.             console.error('Failed to send logs to remote server:', e);
  82.             // 如果发送失败,将日志重新放回待发送队列
  83.             this.pendingLogs = [...logsToSend, ...this.pendingLogs];
  84.         }
  85.     }
  86.     error(message, data) {
  87.         this._log('error', message, data);
  88.     }
  89.     warn(message, data) {
  90.         this._log('warn', message, data);
  91.     }
  92.     info(message, data) {
  93.         this._log('info', message, data);
  94.     }
  95.     debug(message, data) {
  96.         this._log('debug', message, data);
  97.     }
  98. }
  99. // 使用示例
  100. const prodLogger = new ProductionLogger({
  101.     level: 'warn',
  102.     remoteUrl: 'https://api.example.com/logs',
  103.     sanitizeFields: ['password', 'token', 'creditCard', 'ssn'],
  104.     batchSize: 5,
  105.     flushInterval: 5000
  106. });
  107. prodLogger.info('Application started', { version: '1.0.0' });
  108. prodLogger.warn('Feature flag disabled', { flag: 'newFeature' });
  109. prodLogger.error('API request failed', {
  110.     endpoint: '/users',
  111.     status: 500,
  112.     error: 'Internal server error',
  113.     user: { id: 123, name: 'Alice', password: 'secret123' } // 密码将被自动过滤
  114. });
复制代码

客户端错误监控

在生产环境中,监控客户端错误非常重要:
  1. class ClientErrorMonitor {
  2.     constructor(options = {}) {
  3.         this.url = options.url;
  4.         this.appVersion = options.appVersion || 'unknown';
  5.         this.environment = options.environment || 'production';
  6.         this.sampleRate = options.sampleRate || 1.0; // 错误采样率
  7.         this.setupGlobalHandlers();
  8.     }
  9.     setupGlobalHandlers() {
  10.         // 全局JavaScript错误处理
  11.         window.addEventListener('error', (event) => {
  12.             if (Math.random() < this.sampleRate) {
  13.                 this.reportError({
  14.                     type: 'javascript_error',
  15.                     message: event.message,
  16.                     filename: event.filename,
  17.                     lineno: event.lineno,
  18.                     colno: event.colno,
  19.                     stack: event.error?.stack
  20.                 });
  21.             }
  22.         });
  23.         // 未处理的Promise拒绝处理
  24.         window.addEventListener('unhandledrejection', (event) => {
  25.             if (Math.random() < this.sampleRate) {
  26.                 this.reportError({
  27.                     type: 'unhandled_promise_rejection',
  28.                     message: event.reason?.message || String(event.reason),
  29.                     stack: event.reason?.stack
  30.                 });
  31.             }
  32.         });
  33.         // 控制台错误拦截
  34.         const originalError = console.error;
  35.         console.error = (...args) => {
  36.             originalError.apply(console, args);
  37.             
  38.             if (Math.random() < this.sampleRate) {
  39.                 this.reportError({
  40.                     type: 'console_error',
  41.                     message: args.map(arg =>
  42.                         typeof arg === 'object' ? JSON.stringify(arg) : String(arg)
  43.                     ).join(' ')
  44.                 });
  45.             }
  46.         };
  47.     }
  48.     reportError(errorData) {
  49.         const errorReport = {
  50.             ...errorData,
  51.             appVersion: this.appVersion,
  52.             environment: this.environment,
  53.             userAgent: navigator.userAgent,
  54.             url: window.location.href,
  55.             timestamp: new Date().toISOString(),
  56.             screenSize: `${window.screen.width}x${window.screen.height}`,
  57.             viewportSize: `${window.innerWidth}x${window.innerHeight}`
  58.         };
  59.         if (this.url) {
  60.             this.sendToServer(errorReport);
  61.         }
  62.     }
  63.     sendToServer(errorReport) {
  64.         // 使用navigator.sendBeacon进行非阻塞请求
  65.         const data = JSON.stringify(errorReport);
  66.         const blob = new Blob([data], { type: 'application/json' });
  67.         
  68.         if (navigator.sendBeacon) {
  69.             navigator.sendBeacon(this.url, blob);
  70.         } else {
  71.             // 回退方案
  72.             fetch(this.url, {
  73.                 method: 'POST',
  74.                 headers: { 'Content-Type': 'application/json' },
  75.                 body: data,
  76.                 keepalive: true
  77.             }).catch(e => console.error('Failed to send error report:', e));
  78.         }
  79.     }
  80.     // 手动报告错误
  81.     captureException(error, context = {}) {
  82.         this.reportError({
  83.             type: 'manual_report',
  84.             message: error.message,
  85.             stack: error.stack,
  86.             context: context
  87.         });
  88.     }
  89.     // 手动报告消息
  90.     captureMessage(message, level = 'info') {
  91.         this.reportError({
  92.             type: 'manual_message',
  93.             message: message,
  94.             level: level
  95.         });
  96.     }
  97. }
  98. // 使用示例
  99. const errorMonitor = new ClientErrorMonitor({
  100.     url: 'https://api.example.com/errors',
  101.     appVersion: '1.0.0',
  102.     environment: 'production',
  103.     sampleRate: 0.5 // 只采样50%的错误
  104. });
  105. // 手动报告错误
  106. try {
  107.     // 可能出错的代码
  108.     JSON.parse('{invalid json}');
  109. } catch (error) {
  110.     errorMonitor.captureException(error, {
  111.         action: 'parseJSON',
  112.         input: '{invalid json}'
  113.     });
  114. }
  115. // 手动报告消息
  116. errorMonitor.captureMessage('User completed onboarding', 'info');
复制代码

性能监控

在生产环境中监控应用性能:
  1. class PerformanceMonitor {
  2.     constructor(options = {}) {
  3.         this.url = options.url;
  4.         this.appVersion = options.appVersion || 'unknown';
  5.         this.environment = options.environment || 'production';
  6.         this.sampleRate = options.sampleRate || 0.1; // 性能数据采样率
  7.         this.metrics = {};
  8.         this.init();
  9.     }
  10.     init() {
  11.         // 监控页面加载性能
  12.         window.addEventListener('load', () => {
  13.             if (Math.random() < this.sampleRate) {
  14.                 this.collectPageLoadMetrics();
  15.             }
  16.         });
  17.         // 监控资源加载性能
  18.         if ('PerformanceObserver' in window) {
  19.             const resourceObserver = new PerformanceObserver((list) => {
  20.                 if (Math.random() < this.sampleRate) {
  21.                     this.collectResourceMetrics(list.getEntries());
  22.                 }
  23.             });
  24.             resourceObserver.observe({ entryTypes: ['resource'] });
  25.         }
  26.         // 监控用户交互性能
  27.         if ('PerformanceObserver' in window) {
  28.             const interactionObserver = new PerformanceObserver((list) => {
  29.                 if (Math.random() < this.sampleRate) {
  30.                     this.collectInteractionMetrics(list.getEntries());
  31.                 }
  32.             });
  33.             interactionObserver.observe({ entryTypes: ['event'] });
  34.         }
  35.         // 监控内存使用情况
  36.         if (performance.memory) {
  37.             setInterval(() => {
  38.                 if (Math.random() < this.sampleRate) {
  39.                     this.collectMemoryMetrics();
  40.                 }
  41.             }, 60000); // 每分钟收集一次
  42.         }
  43.     }
  44.     collectPageLoadMetrics() {
  45.         const navigation = performance.getEntriesByType('navigation')[0];
  46.         
  47.         if (navigation) {
  48.             const metrics = {
  49.                 type: 'page_load',
  50.                 domContentLoaded: navigation.domContentLoadedEventEnd - navigation.domContentLoadedEventStart,
  51.                 loadComplete: navigation.loadEventEnd - navigation.loadEventStart,
  52.                 firstPaint: this.getFirstPaint(),
  53.                 firstContentfulPaint: this.getFirstContentfulPaint(),
  54.                 timeToInteractive: this.getTimeToInteractive(),
  55.                 timestamp: new Date().toISOString()
  56.             };
  57.             
  58.             this.sendMetrics(metrics);
  59.         }
  60.     }
  61.     collectResourceMetrics(resources) {
  62.         const slowResources = resources.filter(resource => resource.duration > 100);
  63.         
  64.         if (slowResources.length > 0) {
  65.             const metrics = {
  66.                 type: 'slow_resources',
  67.                 resources: slowResources.map(resource => ({
  68.                     name: resource.name,
  69.                     type: resource.initiatorType,
  70.                     duration: resource.duration,
  71.                     size: resource.transferSize
  72.                 })),
  73.                 timestamp: new Date().toISOString()
  74.             };
  75.             
  76.             this.sendMetrics(metrics);
  77.         }
  78.     }
  79.     collectInteractionMetrics(interactions) {
  80.         const slowInteractions = interactions.filter(interaction => {
  81.             const delay = interaction.processingStart - interaction.startTime;
  82.             return delay > 100; // 延迟超过100ms的交互
  83.         });
  84.         
  85.         if (slowInteractions.length > 0) {
  86.             const metrics = {
  87.                 type: 'slow_interactions',
  88.                 interactions: slowInteractions.map(interaction => ({
  89.                     name: interaction.name,
  90.                     delay: interaction.processingStart - interaction.startTime,
  91.                     duration: interaction.duration
  92.                 })),
  93.                 timestamp: new Date().toISOString()
  94.             };
  95.             
  96.             this.sendMetrics(metrics);
  97.         }
  98.     }
  99.     collectMemoryMetrics() {
  100.         const metrics = {
  101.             type: 'memory_usage',
  102.             used: performance.memory.usedJSHeapSize,
  103.             total: performance.memory.totalJSHeapSize,
  104.             limit: performance.memory.jsHeapSizeLimit,
  105.             timestamp: new Date().toISOString()
  106.         };
  107.         
  108.         this.sendMetrics(metrics);
  109.     }
  110.     getFirstPaint() {
  111.         const paintEntries = performance.getEntriesByType('paint');
  112.         const fpEntry = paintEntries.find(entry => entry.name === 'first-paint');
  113.         return fpEntry ? fpEntry.startTime : null;
  114.     }
  115.     getFirstContentfulPaint() {
  116.         const paintEntries = performance.getEntriesByType('paint');
  117.         const fcpEntry = paintEntries.find(entry => entry.name === 'first-contentful-paint');
  118.         return fcpEntry ? fcpEntry.startTime : null;
  119.     }
  120.     getTimeToInteractive() {
  121.         // 这是一个简化的TTI计算,实际应用中可能需要更复杂的算法
  122.         const navigation = performance.getEntriesByType('navigation')[0];
  123.         if (!navigation) return null;
  124.         
  125.         const fcp = this.getFirstContentfulPaint();
  126.         if (!fcp) return null;
  127.         
  128.         // 查找FCP之后5秒内没有长任务的窗口
  129.         const longTasks = performance.getEntriesByType('longtask');
  130.         const lastLongTask = longTasks
  131.             .filter(task => task.startTime > fcp)
  132.             .sort((a, b) => b.startTime - a.startTime)[0];
  133.         
  134.         if (!lastLongTask) return fcp + 5000; // 如果没有长任务,假设TTI是FCP后5秒
  135.         
  136.         return Math.max(lastLongTask.startTime + lastLongTask.duration, fcp + 5000);
  137.     }
  138.     sendMetrics(metrics) {
  139.         const fullMetrics = {
  140.             ...metrics,
  141.             appVersion: this.appVersion,
  142.             environment: this.environment,
  143.             userAgent: navigator.userAgent,
  144.             url: window.location.href
  145.         };
  146.         
  147.         if (this.url) {
  148.             // 使用navigator.sendBeacon进行非阻塞请求
  149.             const data = JSON.stringify(fullMetrics);
  150.             const blob = new Blob([data], { type: 'application/json' });
  151.             
  152.             if (navigator.sendBeacon) {
  153.                 navigator.sendBeacon(this.url, blob);
  154.             } else {
  155.                 // 回退方案
  156.                 fetch(this.url, {
  157.                     method: 'POST',
  158.                     headers: { 'Content-Type': 'application/json' },
  159.                     body: data,
  160.                     keepalive: true
  161.                 }).catch(e => console.error('Failed to send metrics:', e));
  162.             }
  163.         }
  164.     }
  165.     // 手动记录自定义指标
  166.     measureCustomMetric(name, value, tags = {}) {
  167.         const metrics = {
  168.             type: 'custom_metric',
  169.             name: name,
  170.             value: value,
  171.             tags: tags,
  172.             timestamp: new Date().toISOString()
  173.         };
  174.         
  175.         this.sendMetrics(metrics);
  176.     }
  177. }
  178. // 使用示例
  179. const perfMonitor = new PerformanceMonitor({
  180.     url: 'https://api.example.com/metrics',
  181.     appVersion: '1.0.0',
  182.     environment: 'production',
  183.     sampleRate: 0.1
  184. });
  185. // 手动记录自定义指标
  186. function checkout() {
  187.     const startTime = performance.now();
  188.    
  189.     // 执行结账流程
  190.     // ...
  191.    
  192.     const duration = performance.now() - startTime;
  193.     perfMonitor.measureCustomMetric('checkout_duration', duration, {
  194.         payment_method: 'credit_card'
  195.     });
  196. }
复制代码

日志分析工具

浏览器开发者工具

现代浏览器提供了强大的开发者工具,用于分析日志和调试应用程序:

1. Console面板:查看和过滤日志消息使用console对象的方法进行日志记录执行JavaScript代码
2. 查看和过滤日志消息
3. 使用console对象的方法进行日志记录
4. 执行JavaScript代码
5. Network面板:监控网络请求查看请求和响应详情分析加载性能
6. 监控网络请求
7. 查看请求和响应详情
8. 分析加载性能
9. Performance面板:记录和分析运行时性能识别性能瓶颈分析CPU使用情况
10. 记录和分析运行时性能
11. 识别性能瓶颈
12. 分析CPU使用情况
13. Memory面板:分析内存使用情况识别内存泄漏比较内存快照
14. 分析内存使用情况
15. 识别内存泄漏
16. 比较内存快照

Console面板:

• 查看和过滤日志消息
• 使用console对象的方法进行日志记录
• 执行JavaScript代码

Network面板:

• 监控网络请求
• 查看请求和响应详情
• 分析加载性能

Performance面板:

• 记录和分析运行时性能
• 识别性能瓶颈
• 分析CPU使用情况

Memory面板:

• 分析内存使用情况
• 识别内存泄漏
• 比较内存快照

ELK Stack (Elasticsearch, Logstash, Kibana)

ELK Stack是一个强大的日志分析平台:

1. Elasticsearch:用于存储和索引日志数据
2. Logstash:用于收集、处理和转发日志数据
3. Kibana:用于可视化和分析日志数据

以下是如何将JavaScript应用与ELK Stack集成的示例:
  1. class ELKLogger {
  2.     constructor(options = {}) {
  3.         this.elasticsearchUrl = options.elasticsearchUrl || 'http://localhost:9200';
  4.         this.indexName = options.indexName || 'js-logs';
  5.         this.appName = options.appName || 'javascript-app';
  6.         this.environment = options.environment || 'development';
  7.         this.level = options.level || 'info';
  8.         this.levels = { error: 0, warn: 1, info: 2, debug: 3 };
  9.         this.batchSize = options.batchSize || 10;
  10.         this.flushInterval = options.flushInterval || 10000;
  11.         this.pendingLogs = [];
  12.         
  13.         if (this.elasticsearchUrl) {
  14.             setInterval(() => this.flush(), this.flushInterval);
  15.         }
  16.     }
  17.     _log(level, message, data = {}) {
  18.         if (this.levels[level] > this.levels[this.level]) return;
  19.         
  20.         const logEntry = {
  21.             '@timestamp': new Date().toISOString(),
  22.             level: level,
  23.             message: message,
  24.             application: this.appName,
  25.             environment: this.environment,
  26.             data: data,
  27.             userAgent: navigator.userAgent,
  28.             url: window.location.href,
  29.             screenSize: `${window.screen.width}x${window.screen.height}`,
  30.             viewportSize: `${window.innerWidth}x${window.innerHeight}`
  31.         };
  32.         
  33.         // 在开发环境中,也输出到控制台
  34.         if (this.environment === 'development') {
  35.             console[level](`[${level.toUpperCase()}] ${message}`, data);
  36.         }
  37.         
  38.         // 添加到待发送队列
  39.         this.pendingLogs.push(logEntry);
  40.         
  41.         if (this.pendingLogs.length >= this.batchSize) {
  42.             this.flush();
  43.         }
  44.     }
  45.     async flush() {
  46.         if (this.pendingLogs.length === 0 || !this.elasticsearchUrl) return;
  47.         
  48.         const logsToSend = [...this.pendingLogs];
  49.         this.pendingLogs = [];
  50.         
  51.         try {
  52.             // 批量索引格式
  53.             const bulkBody = logsToSend.flatMap(log => [
  54.                 { index: { _index: this.indexName } },
  55.                 log
  56.             ]);
  57.             
  58.             const response = await fetch(`${this.elasticsearchUrl}/_bulk`, {
  59.                 method: 'POST',
  60.                 headers: {
  61.                     'Content-Type': 'application/x-ndjson'
  62.                 },
  63.                 body: bulkBody.map(JSON.stringify).join('\n') + '\n'
  64.             });
  65.             
  66.             if (!response.ok) {
  67.                 throw new Error(`HTTP error! status: ${response.status}`);
  68.             }
  69.             
  70.             const result = await response.json();
  71.             
  72.             if (result.errors) {
  73.                 console.error('Some logs failed to index:', result);
  74.             }
  75.         } catch (e) {
  76.             console.error('Failed to send logs to Elasticsearch:', e);
  77.             // 如果发送失败,将日志重新放回待发送队列
  78.             this.pendingLogs = [...logsToSend, ...this.pendingLogs];
  79.         }
  80.     }
  81.     error(message, data) {
  82.         this._log('error', message, data);
  83.     }
  84.     warn(message, data) {
  85.         this._log('warn', message, data);
  86.     }
  87.     info(message, data) {
  88.         this._log('info', message, data);
  89.     }
  90.     debug(message, data) {
  91.         this._log('debug', message, data);
  92.     }
  93. }
  94. // 使用示例
  95. const elkLogger = new ELKLogger({
  96.     elasticsearchUrl: 'http://localhost:9200',
  97.     indexName: 'my-app-logs',
  98.     appName: 'My JavaScript App',
  99.     environment: 'production',
  100.     level: 'info',
  101.     batchSize: 5,
  102.     flushInterval: 5000
  103. });
  104. elkLogger.info('Application started', { version: '1.0.0' });
  105. elkLogger.warn('Feature flag disabled', { flag: 'newFeature' });
  106. elkLogger.error('API request failed', {
  107.     endpoint: '/users',
  108.     status: 500,
  109.     error: 'Internal server error'
  110. });
复制代码

Sentry

Sentry是一个流行的错误监控平台,特别适合前端应用:
  1. // 集成Sentry的示例
  2. import * as Sentry from "@sentry/browser";
  3. import { BrowserTracing } from "@sentry/tracing";
  4. // 初始化Sentry
  5. Sentry.init({
  6.   dsn: "https://examplePublicKey@o0.ingest.sentry.io/0",
  7.   integrations: [new BrowserTracing()],
  8.   
  9.   // 设置采样率,在生产环境中建议设置为0.2
  10.   tracesSampleRate: 1.0,
  11.   
  12.   // 设置环境
  13.   environment: process.env.NODE_ENV || 'development',
  14.   
  15.   // 设置发布版本
  16.   release: "my-app@1.0.0",
  17.   
  18.   // 在发送前可以修改或丢弃事件
  19.   beforeSend(event) {
  20.     if (event.request) {
  21.       event.request.headers = {
  22.         ...event.request.headers,
  23.         'Authorization': '[REDACTED]'
  24.       };
  25.     }
  26.     return event;
  27.   }
  28. });
  29. // 手动捕获异常
  30. try {
  31.   // 可能出错的代码
  32.   myUndefinedFunction();
  33. } catch (error) {
  34.   Sentry.captureException(error);
  35. }
  36. // 手动捕获消息
  37. Sentry.captureMessage("Something went wrong");
  38. // 添加用户上下文
  39. Sentry.withScope((scope) => {
  40.   scope.setUser({ id: "123", email: "user@example.com" });
  41.   scope.setTag("page", "checkout");
  42.   scope.setExtra("data", {
  43.     value1: "some data",
  44.     value2: "another data"
  45.   });
  46.   Sentry.captureException(new Error("Custom error with context"));
  47. });
  48. // 事务跟踪
  49. const transaction = Sentry.startTransaction({
  50.   name: "checkout-process",
  51.   op: "transaction"
  52. });
  53. const span = transaction.startChild({ op: "task", description: "processing payment" });
  54. // 执行一些操作
  55. processPayment();
  56. span.finish();
  57. transaction.finish();
复制代码

LogRocket

LogRocket是一个前端监控和调试工具,可以记录用户会话、控制台日志、网络请求等:
  1. // 集成LogRocket的示例
  2. import LogRocket from 'logrocket';
  3. import setupLogRocketReact from 'logrocket-react';
  4. // 初始化LogRocket
  5. LogRocket.init('your-app-id');
  6. // 对于React应用,设置LogRocket React插件
  7. setupLogRocketReact(LogRocket);
  8. // 添加用户信息
  9. LogRocket.identify('123', {
  10.   name: 'John Doe',
  11.   email: 'john@example.com',
  12.   
  13.   // 添加自定义属性
  14.   subscriptionType: 'pro',
  15.   favoriteColor: 'blue'
  16. });
  17. // 记录自定义事件
  18. LogRocket.track('checkout_started', {
  19.   items: 3,
  20.   total: 99.99,
  21.   currency: 'USD'
  22. });
  23. // 记录会话URL以便于查找
  24. const sessionURL = LogRocket.sessionURL;
  25. console.log('LogRocket session URL:', sessionURL);
  26. // 将LogRocket会话URL与其他错误报告系统集成
  27. Sentry.configureScope((scope) => {
  28.   scope.setExtra("sessionURL", LogRocket.sessionURL);
  29. });
复制代码

Grafana Loki

Grafana Loki是一个日志聚合系统,与Grafana集成用于可视化:
  1. class LokiLogger {
  2.     constructor(options = {}) {
  3.         this.lokiUrl = options.lokiUrl || 'http://localhost:3100/loki/api/v1/push';
  4.         this.username = options.username;
  5.         this.password = options.password;
  6.         this.labels = {
  7.             ...options.labels,
  8.             app: options.appName || 'javascript-app',
  9.             env: options.environment || 'development'
  10.         };
  11.         this.level = options.level || 'info';
  12.         this.levels = { error: 0, warn: 1, info: 2, debug: 3 };
  13.         this.batchSize = options.batchSize || 10;
  14.         this.flushInterval = options.flushInterval || 10000;
  15.         this.pendingLogs = [];
  16.         
  17.         if (this.lokiUrl) {
  18.             setInterval(() => this.flush(), this.flushInterval);
  19.         }
  20.     }
  21.     _log(level, message, data = {}) {
  22.         if (this.levels[level] > this.levels[this.level]) return;
  23.         
  24.         const logEntry = {
  25.             timestamp: new Date().toISOString(),
  26.             level: level,
  27.             message: message,
  28.             data: data,
  29.             userAgent: navigator.userAgent,
  30.             url: window.location.href
  31.         };
  32.         
  33.         // 在开发环境中,也输出到控制台
  34.         if (this.labels.env === 'development') {
  35.             console[level](`[${level.toUpperCase()}] ${message}`, data);
  36.         }
  37.         
  38.         // 添加到待发送队列
  39.         this.pendingLogs.push(logEntry);
  40.         
  41.         if (this.pendingLogs.length >= this.batchSize) {
  42.             this.flush();
  43.         }
  44.     }
  45.     async flush() {
  46.         if (this.pendingLogs.length === 0 || !this.lokiUrl) return;
  47.         
  48.         const logsToSend = [...this.pendingLogs];
  49.         this.pendingLogs = [];
  50.         
  51.         try {
  52.             // 准备Loki格式的日志
  53.             const streams = [{
  54.                 stream: {
  55.                     ...this.labels,
  56.                     level: logsToSend[0].level
  57.                 },
  58.                 values: logsToSend.map(log => [
  59.                     `${Date.now()}000000`, // 纳秒时间戳
  60.                     JSON.stringify({
  61.                         message: log.message,
  62.                         data: log.data,
  63.                         userAgent: log.userAgent,
  64.                         url: log.url
  65.                     })
  66.                 ])
  67.             }];
  68.             
  69.             const headers = {
  70.                 'Content-Type': 'application/json'
  71.             };
  72.             
  73.             // 添加基本认证(如果提供了用户名和密码)
  74.             if (this.username && this.password) {
  75.                 headers['Authorization'] = `Basic ${btoa(`${this.username}:${this.password}`)}`;
  76.             }
  77.             
  78.             const response = await fetch(this.lokiUrl, {
  79.                 method: 'POST',
  80.                 headers: headers,
  81.                 body: JSON.stringify({ streams })
  82.             });
  83.             
  84.             if (!response.ok) {
  85.                 throw new Error(`HTTP error! status: ${response.status}`);
  86.             }
  87.         } catch (e) {
  88.             console.error('Failed to send logs to Loki:', e);
  89.             // 如果发送失败,将日志重新放回待发送队列
  90.             this.pendingLogs = [...logsToSend, ...this.pendingLogs];
  91.         }
  92.     }
  93.     error(message, data) {
  94.         this._log('error', message, data);
  95.     }
  96.     warn(message, data) {
  97.         this._log('warn', message, data);
  98.     }
  99.     info(message, data) {
  100.         this._log('info', message, data);
  101.     }
  102.     debug(message, data) {
  103.         this._log('debug', message, data);
  104.     }
  105. }
  106. // 使用示例
  107. const lokiLogger = new LokiLogger({
  108.     lokiUrl: 'http://localhost:3100/loki/api/v1/push',
  109.     username: 'loki',
  110.     password: 'password',
  111.     labels: {
  112.         app: 'my-app',
  113.         env: 'production',
  114.         version: '1.0.0'
  115.     },
  116.     level: 'info',
  117.     batchSize: 5,
  118.     flushInterval: 5000
  119. });
  120. lokiLogger.info('Application started');
  121. lokiLogger.warn('Feature flag disabled', { flag: 'newFeature' });
  122. lokiLogger.error('API request failed', {
  123.     endpoint: '/users',
  124.     status: 500,
  125.     error: 'Internal server error'
  126. });
复制代码

总结

有效的日志记录是JavaScript开发中不可或缺的一部分。从基础的console.log到高级的日志管理技术,良好的日志实践可以帮助我们:

1. 提高调试效率:通过详细的日志信息,快速定位和解决问题。
2. 增强错误追踪:捕获和记录错误,包括堆栈跟踪和上下文信息,以便于分析。
3. 优化性能:通过性能监控日志,识别和解决性能瓶颈。
4. 改善生产环境稳定性:在生产环境中实施适当的日志策略,及时发现和解决问题。
5. 提升用户体验:通过分析用户交互日志,了解用户行为并改进应用。

通过本文介绍的各种技术和工具,开发者可以构建一个全面的日志系统,从开发到生产环境,有效提升开发效率和代码质量。记住,好的日志不仅仅是记录信息,更是提供洞察力和解决问题的能力。

在实际应用中,根据项目需求和团队情况选择合适的日志策略和工具,并随着项目的发展不断调整和优化日志系统,使其成为开发流程中的有力助手。
「七転び八起き(ななころびやおき)」
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则