ReactObservabilitySkill react-observability

React 可观测性提供了日志记录、错误消息和调试模式,用于提高React应用的代码可观测性,帮助开发者在生产环境中更有效地诊断问题。

前端开发 0 次安装 0 次浏览 更新于 3/3/2026

React 可观测性

问题陈述

无声故障是调试的噩梦。代码提前返回而没有日志记录,错误消息缺乏上下文,以及缺失的可观测性使得生产问题无法诊断。编写代码时,就好像你会在凌晨3点仅凭日志进行调试一样。


模式:无静默提前返回

问题: 提前返回而没有日志记录会创建看不见的失败路径。

// 错误 - 静默死亡
const saveData = (id: string, value: number) => {
  if (!validIds.has(id)) {
    return;  // ❌ 为什么我们返回?没人知道。
  }
  // ... 保存逻辑
};

// 正确 - 可观测
const saveData = (id: string, value: number) => {
  if (!validIds.has(id)) {
    logger.warn('[saveData] 丢弃保存 - 无效ID', {
      id,
      value,
      validIds: Array.from(validIds),
    });
    return;
  }
  // ... 保存逻辑
};

规则: 每个提前返回都应该记录为什么返回,有足够的上下文来诊断。


模式:错误消息设计

问题: 错误消息无助于诊断问题。

// 坏 - 没有上下文
throw new Error('数据未找到');

// 坏 - 稍微好一点,但在凌晨3点仍然无用
throw new Error('数据未找到。请再试一次。');

// 好 - 包含诊断上下文
throw new Error(
  `数据未找到。ID: ${id}, ` +
  `可用: ${Object.keys(data).length} 项, ` +
  `上次获取: ${lastFetchTime}。这可能表明缓存问题。`
);

错误消息模板:

throw new Error(
  `[${functionName}] ${whatFailed}. ` +
  `上下文: ${relevantState}. ` +
  `可能的原因: ${hypothesis}.`
);

包含内容:

元素 为什么
函数/位置 错误发生的位置
什么失败了 未满足的具体条件
相关状态 有助于诊断的值
可能的原因 你最好的猜测修复方法

模式:结构化日志记录

问题: 控制台.log语句难以解析和搜索。

// 坏 - 非结构化
console.log('保存数据', id, value);
console.log('当前状态', data);

// 好 - 结构化,带上下文对象
logger.info('[saveData] 保存数据', {
  id,
  value,
  existingCount: Object.keys(data).length,
});

日志级别:

级别 用途
error 异常,需要立即关注的失败
warn 未失败但可能表明问题的意外条件
info 重要的业务事件(用户操作,流程里程碑)
debug 详细的诊断信息(状态转储,计时)

一致日志记录的包装器:

// utils/logger.ts
const LOG_LEVELS = ['debug', 'info', 'warn', 'error'] as const;
type LogLevel = typeof LOG_LEVELS[number];

const currentLevel: LogLevel = process.env.NODE_ENV === 'development' ? 'debug' : 'warn';

function shouldLog(level: LogLevel): boolean {
  return LOG_LEVELS.indexOf(level) >= LOG_LEVELS.indexOf(currentLevel);
}

export const logger = {
  debug: (message: string, context?: object) => {
    if (shouldLog('debug')) {
      console.log(`[DEBUG] ${message}`, context ?? '');
    }
  },
  info: (message: string, context?: object) => {
    if (shouldLog('info')) {
      console.log(`[INFO] ${message}`, context ?? '');
    }
  },
  warn: (message: string, context?: object) => {
    if (shouldLog('warn')) {
      console.warn(`[WARN] ${message}`, context ?? '');
    }
  },
  error: (message: string, context?: object) => {
    if (shouldLog('error')) {
      console.error(`[ERROR] ${message}`, context ?? '');
    }
  },
};

模式:敏感数据处理

问题: 在控制台或错误报告中记录敏感数据。

// utils/secureLogger.ts
const SENSITIVE_KEYS = ['password', 'token', 'ssn', 'creditCard', 'apiKey', 'secret'];

function redactSensitive(obj: object): object {
  const redacted = { ...obj };
  for (const key of Object.keys(redacted)) {
    if (SENSITIVE_KEYS.some(s => key.toLowerCase().includes(s))) {
      redacted[key] = '[REDACTED]';
    } else if (typeof redacted[key] === 'object' && redacted[key] !== null) {
      redacted[key] = redactSensitive(redacted[key]);
    }
  }
  return redacted;
}

export const secureLogger = {
  info: (message: string, context?: object) => {
    const safeContext = context ? redactSensitive(context) : undefined;
    logger.info(message, safeContext);
  },
  // ... 其他级别
};

模式:流程追踪

问题: 多步骤操作中不清楚执行到了哪一步。

async function checkoutFlow(cartId: string) {
  const flowId = `checkout-${Date.now()}`;

  logger.info(`[checkoutFlow:${flowId}] 开始`, { cartId });

  try {
    logger.debug(`[checkoutFlow:${flowId}] 第1步:验证购物车`);
    await validateCart(cartId);

    logger.debug(`[checkoutFlow:${flowId}] 第2步:处理付款`);
    await processPayment(cartId);

    logger.debug(`[checkoutFlow:${flowId}] 第3步:确认订单`);
    await confirmOrder(cartId);

    logger.info(`[checkoutFlow:${flowId}] 成功完成`);
  } catch (error) {
    logger.error(`[checkoutFlow:${flowId}] 失败`, {
      error: error.message,
      stack: error.stack,
      cartId,
    });
    throw error;
  }
}

好处:

  • 可以通过flowId搜索日志查看整个流程
  • 确切知道哪一步失败了
  • 通过时间戳可见的时机

模式:调试状态快照

问题: 在复杂流程中需要理解特定点的状态。

function snapshotState(label: string) {
  const state = useStore.getState();
  logger.debug(`[StateSnapshot] ${label}`, {
    itemCount: Object.keys(state.items).length,
    activeFeatures: Array.from(state.features),
    loading: state.loading,
  });
}

// 流程中的使用
async function complexFlow() {
  snapshotState('加载前');
  await loadData(id);
  snapshotState('加载后');
  await processData();
  snapshotState('处理后');
}

模式:断言助手

问题: 条件是“永远不应该发生”的,但当它们确实发生时需要可见性。

// utils/assertions.ts
export function assertDefined<T>(
  value: T | null | undefined,
  context: string
): asserts value is T {
  if (value === null || value === undefined) {
    const message = `[Assertion Failed] 预期定义值:${context}`;
    logger.error(message, { value });
    throw new Error(message);
  }
}

export function assertCondition(
  condition: boolean,
  context: string,
  debugInfo?: object
): asserts condition {
  if (!condition) {
    const message = `[Assertion Failed] ${context}`;
    logger.error(message, debugInfo);
    throw new Error(message);
  }
}

// 使用
assertDefined(user, `未找到用户:${userId}`);
assertCondition(
  items.length > 0,
  `未找到项目`,
  { searchQuery, filters }
);

模式:生产错误报告

问题: 生产中的错误没有可见性。

// 与错误报告服务集成(Sentry示例)
import * as Sentry from '@sentry/react';

export function captureError(
  error: Error,
  context?: Record<string, unknown>
) {
  logger.error(error.message, { ...context, stack: error.stack });

  if (process.env.NODE_ENV === 'production') {
    Sentry.captureException(error, {
      extra: context,
    });
  }
}

// 使用
try {
  await riskyOperation();
} catch (error) {
  captureError(error, {
    userId,
    action: 'checkout',
    cartItems: cart.items.length,
  });
  throw error;
}

模式:React错误边界

问题: 未处理的错误会崩溃整个应用程序。

import { Component, ErrorInfo, ReactNode } from 'react';

interface Props {
  children: ReactNode;
  fallback?: ReactNode;
}

interface State {
  hasError: boolean;
  error?: Error;
}

class ErrorBoundary extends Component<Props, State> {
  state: State = { hasError: false };

  static getDerivedStateFromError(error: Error): State {
    return { hasError: true, error };
  }

  componentDidCatch(error: Error, errorInfo: ErrorInfo) {
    logger.error('[ErrorBoundary] 捕获错误', {
      error: error.message,
      stack: error.stack,
      componentStack: errorInfo.componentStack,
    });

    captureError(error, { componentStack: errorInfo.componentStack });
  }

  render() {
    if (this.state.hasError) {
      return this.props.fallback ?? <DefaultErrorFallback error={this.state.error} />;
    }
    return this.props.children;
  }
}

检查表:添加可观测性

编写新代码时:

  • [ ] 所有提前返回都有带有上下文的日志记录
  • [ ] 错误消息包含诊断信息
  • [ ] 多步骤操作有流程追踪
  • [ ] 敏感数据在记录之前被删除
  • [ ] 复杂流程的状态快照可用于调试
  • [ ] 生产错误被捕获并带有上下文

调试现有代码时:

  • [ ] 在可疑的提前返回处添加日志记录
  • [ ] 在异步操作前后添加状态快照
  • [ ] 检查静默捕获是否吞噬错误
  • [ ] 验证错误消息是否有足够的上下文

快速调试模板

在调试异步/状态问题时临时添加:

const DEBUG = true;

function debugLog(label: string, data?: object) {
  if (DEBUG) {
    console.log(`[DEBUG ${Date.now()}] ${label}`, data ?? '');
  }
}

// 在你的流程中
debugLog('流程开始', { inputs });
debugLog('第1步后', { state: getState() });
debugLog('第2步后', { state: getState() });
debugLog('流程结束', { result });

在提交前删除,或通过标志控制。