本文目录导读:

- 通用核心原则
- 后端(Java/Python/Go)实现方案
- 前端(JavaScript/TypeScript)实现方案
- 移动端(iOS/Android)实现方案
- 高级留存策略:快照(Snapshot)技术
- 最佳实践总结
实现“留存异常上下文信息”的核心目标是:当程序发生异常(如崩溃、错误、超时)时,能够捕获并保存导致异常发生时的环境状态、数据快照、调用链路等关键信息,以便事后排查根因。
根据不同的技术场景(后端、前端、移动端、嵌入式),实现方式差异较大,以下是几种主流场景的详细实现方案:
通用核心原则
无论哪种语言或平台,留存上下文通常包含以下3类信息:
- 静态上下文:版本号、环境(生产/测试)、用户ID、请求ID、设备信息。
- 动态上下文:当前线程堆栈、调用链(TraceID/SpanID)、函数入参、关键变量值。
- 资源上下文:内存使用率、CPU负载、数据库连接池状态、最近一次网络请求的URL和耗时。
后端(Java/Python/Go)实现方案
Java(最常用:日志框架 + MDC + 异常拦截器)
核心工具:SLF4J + Logback/Log4j2 + ThreadLocal(通过MDC实现)。
实现步骤:
-
在入口处(Filter/Interceptor)注入上下文:
import org.slf4j.MDC; public class LoggingFilter implements Filter { public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) { try { // 1. 生成TraceID(全链路唯一) String traceId = UUID.randomUUID().toString(); MDC.put("traceId", traceId); MDC.put("userId", extractUserId(request)); MDC.put("requestURI", ((HttpServletRequest) request).getRequestURI()); // 2. 执行业务逻辑 chain.doFilter(request, response); } finally { // 3. 清除MDC(防止内存泄漏) MDC.clear(); } } } -
在异常拦截器中捕获详细数据:
@ControllerAdvice public class GlobalExceptionHandler { @ExceptionHandler(Exception.class) @ResponseBody public ErrorResponse handleException(Exception e, HttpServletRequest request) { // 1. 提取上下文 Map<String, String> context = new HashMap<>(); context.put("threadName", Thread.currentThread().getName()); context.put("stackTrace", ExceptionUtils.getStackTrace(e)); // 完整堆栈 context.put("method", request.getMethod()); context.put("queryString", request.getQueryString()); // 2. 如果是业务异常,获取请求体(注意只消费一次,需缓存) // context.put("body", getCachedBody(request)); // 3. 留存:写入专门的错误日志文件 log.error("Business Exception with context: {}", JSON.toJSONString(context), e); return new ErrorResponse(e.getMessage()); } } -
日志配置(logback.xml):
<appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"> <file>logs/error-context.log</file> <filter class="ch.qos.logback.classic.filter.LevelFilter"> <level>ERROR</level> <onMatch>ACCEPT</onMatch> <onMismatch>DENY</onMismatch> </filter> <encoder> <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern> </encoder> </appender>
Python(使用 logging + 自定义适配器 + Sentry)
核心工具:logging.LoggerAdapter 或 structlog。
实现步骤:
-
自定义Adapter传递上下文:
import logging from logging import LoggerAdapter class ContextAdapter(LoggerAdapter): def process(self, msg, kwargs): # 将extra参数自动附加到日志 extra = self.extra.copy() if 'extra' in kwargs: extra.update(kwargs['extra']) kwargs['extra'] = extra return msg, kwargs # 初始化 logger = ContextAdapter(logging.getLogger(__name__), {'trace_id': trace_id, 'user': user_id}) logger.error("数据库查询失败", extra={'sql': sql_query, 'params': params}) -
使用Sentry(推荐):
import sentry_sdk from sentry_sdk import configure_scope with configure_scope() as scope: scope.set_user({"id": user_id, "email": email}) scope.set_tag("service", "payment-api") scope.set_context("request", { "method": request.method, "url": request.url, "body": request.json # 小心敏感数据脱敏 }) # 触发异常时自动携带上述上下文
Go(使用 context.Context + 自定义中间件)
核心思想:将上下文信息通过 context.Context 传递。
package main
import (
"context"
"log"
"net/http"
"runtime/debug"
)
type ContextKey string
const TraceIDKey ContextKey = "trace_id"
func middleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// 1. 注入上下文
ctx := context.WithValue(r.Context(), TraceIDKey, r.Header.Get("X-Trace-ID"))
ctx = context.WithValue(ctx, "user_id", extractUser(r))
// 2. 异常恢复中间件(捕获panic)
defer func() {
if rec := recover(); rec != nil {
// 3. 提取堆栈
stack := debug.Stack()
// 4. 留存上下文
log.Printf("[CRASH] TraceID: %v, UserID: %v, Error: %v, Stack: %s",
ctx.Value(TraceIDKey), ctx.Value("user_id"), rec, stack)
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
}
}()
next.ServeHTTP(w, r.WithContext(ctx))
})
}
前端(JavaScript/TypeScript)实现方案
全局Error Handler + 自定义上下文
// 1. 定义一个全局上下文对象(实时更新)
const globalContext = {
userId: null,
pageUrl: window.location.href,
userAgent: navigator.userAgent,
lastApiCall: null,
};
// 2. 重写XMLHttpRequest或fetch,捕获请求上下文
const originalFetch = window.fetch;
window.fetch = async (...args) => {
globalContext.lastApiCall = args[0];
try {
return await originalFetch(...args);
} catch (error) {
// 这里会触发全局错误
throw error;
}
};
// 3. 全局异常捕获
window.onerror = function(message, source, lineno, colno, error) {
const errorContext = {
...globalContext,
timestamp: new Date().toISOString(),
errorMessage: message,
stackTrace: error?.stack,
componentStack: error?.componentStack // React等框架提供
};
// 4. 发送到后端日志服务
navigator.sendBeacon('/api/log/error', JSON.stringify(errorContext));
return true; // 阻止默认浏览器错误提示
};
React Error Boundary(组件级)
class ErrorBoundary extends React.Component {
componentDidCatch(error, errorInfo) {
// 留存组件栈
const context = {
error,
componentStack: errorInfo.componentStack,
reduxState: store.getState(), // 如果用了Redux
props: this.props
};
logToServer(context);
}
render() {
return this.props.children;
}
}
移动端(iOS/Android)实现方案
iOS (Swift) - NSSetUncaughtExceptionHandler + 自定义上下文
// 1. 设置未捕获异常处理器
func setupExceptionHandler() {
NSSetUncaughtExceptionHandler { exception in
let context: [String: Any] = [
"name": exception.name.rawValue,
"reason": exception.reason ?? "",
"callStackSymbols": exception.callStackSymbols,
"userInfo": exception.userInfo ?? [:],
"currentViewController": getTopViewController()?.description ?? "unknown",
"threadInfo": Thread.current.description
]
// 写入本地文件
saveCrashReport(context)
}
}
// 2. 捕获信号量崩溃(如EXC_BAD_ACCESS)
signal(SIGABRT) { _ in /* 处理 */ }
Android (Java/Kotlin) - Thread.setDefaultUncaughtExceptionHandler
class CustomExceptionHandler : Thread.UncaughtExceptionHandler {
override fun uncaughtException(t: Thread, e: Throwable) {
val context = mutableMapOf<String, Any>()
context["thread_name"] = t.name
context["stack_trace"] = Log.getStackTraceString(e)
context["activity"] = currentActivity?.localClassName
context["time"] = System.currentTimeMillis()
context["memory"] = getMemoryInfo()
// 保存到SharedPreferences或本地文件
saveToFile(context)
// 交给系统默认处理器(保证应用能退出)
defaultHandler?.uncaughtException(t, e)
}
}
// 在Application中设置
Thread.setDefaultUncaughtExceptionHandler(CustomExceptionHandler())
高级留存策略:快照(Snapshot)技术
对于非常复杂的异常(如内存溢出前的状态),可以在异常发生前做增量快照:
- Java:使用
jcmd或JMX在监控到内存阈值时自动 dump 堆(-XX:+HeapDumpOnOutOfMemoryError)。 - 数据库:在事务失败时,留存该事务的 Undo Log 或 Redo Log 快照。
- 分布式系统:使用 OpenTelemetry 记录 Span Events,在异常Span上追加“异常上下文属性”。
最佳实践总结
- 自动注入 > 手动注入:使用中间件/切面自动收集(URL、用户、请求ID),避免业务代码手动传递。
- 敏感信息脱敏:永远不要记录密码、Token、银行卡号,使用
replace("password", "***")过滤。 - 限制上下文大小:入参超过1KB可截断;堆栈超过20层可压缩。
- 异步留存:使用 消息队列 或
sendBeacon/async writer将上下文发送到日志系统,不要阻塞主业务线程。 - 结构化存储:使用 JSON 或 Protobuf 格式存储,方便后续搜索
traceId或userId。
如果你有具体的语言或框架(如 Spring Boot, Express, Flutter),我可以提供更针对性的代码示例。
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播