From 85200ab2055dcc67c1e2c1b7e1f809239ffaaa6f Mon Sep 17 00:00:00 2001 From: funcodingdev Date: Tue, 27 May 2025 20:01:03 +0800 Subject: [PATCH 1/4] feat: add docs --- cursor/README.md | 438 ++++++++++ cursor/sentry-crash-monitoring.md | 688 ++++++++++++++++ cursor/sentry-init-quick-reference.md | 155 ++++ cursor/sentry-initialization-details.md | 352 ++++++++ cursor/sentry-initialization-flow.md | 366 +++++++++ cursor/sentry-network-monitoring.md | 1004 +++++++++++++++++++++++ cursor/sentry-profiling-analysis.md | 1002 ++++++++++++++++++++++ cursor/sentry-replay-analysis.md | 775 +++++++++++++++++ cursor/sentry-session-management.md | 773 +++++++++++++++++ cursor/sentry-startup-monitoring.md | 681 +++++++++++++++ cursor/sentry-ui-jank-monitoring.md | 689 ++++++++++++++++ 11 files changed, 6923 insertions(+) create mode 100644 cursor/README.md create mode 100644 cursor/sentry-crash-monitoring.md create mode 100644 cursor/sentry-init-quick-reference.md create mode 100644 cursor/sentry-initialization-details.md create mode 100644 cursor/sentry-initialization-flow.md create mode 100644 cursor/sentry-network-monitoring.md create mode 100644 cursor/sentry-profiling-analysis.md create mode 100644 cursor/sentry-replay-analysis.md create mode 100644 cursor/sentry-session-management.md create mode 100644 cursor/sentry-startup-monitoring.md create mode 100644 cursor/sentry-ui-jank-monitoring.md diff --git a/cursor/README.md b/cursor/README.md new file mode 100644 index 0000000000..69400bc6fa --- /dev/null +++ b/cursor/README.md @@ -0,0 +1,438 @@ +# Sentry Java SDK 分析文档 + +本文件夹包含了对 Sentry Java SDK 初始化流程的深入分析和可视化文档。 + +## 📁 文档结构 + +### 🎯 [快速参考](./sentry-init-quick-reference.md) +- **适用对象**: 开发者快速查阅 +- **内容**: 核心初始化步骤、关键组件、最佳实践 +- **特点**: 简洁明了,包含常见问题解答 + +### 📊 [初始化流程时序图](./sentry-initialization-flow.md) +- **适用对象**: 架构师、高级开发者 +- **内容**: 完整的初始化流程可视化 +- **包含**: + - 核心初始化流程时序图 + - Android 特定初始化流程 + - 集成注册流程 + - 客户端创建流程 + - 配置加载流程 + +### 🔍 [详细实现说明](./sentry-initialization-details.md) +- **适用对象**: SDK 贡献者、深度定制需求 +- **内容**: 设计决策和实现细节 +- **包含**: + - 线程安全机制 + - 配置优先级策略 + - 平台检测和适配 + - Scopes 架构设计 + - 集成系统设计 + - 传输层设计 + - 错误处理策略 + - 性能优化 + - 内存管理 + - 扩展点设计 + +### 💥 [崩溃监控机制](./sentry-crash-monitoring.md) +- **适用对象**: 所有开发者、运维人员 +- **内容**: 全面的崩溃监控机制分析 +- **包含**: + - Java 未捕获异常监控 + - Android ANR 检测机制 + - Native 崩溃处理 + - 启动崩溃检测 + - 事件处理流程 + - 传输和持久化 + - 性能优化策略 + - 配置和最佳实践 + - 故障排查指南 + +### 🚀 [启动监控机制](./sentry-startup-monitoring.md) +- **适用对象**: Android 开发者、性能工程师 +- **内容**: 冷启动和热启动监控的完整分析 +- **包含**: + - 启动类型检测机制 + - 时间跨度测量体系 + - 冷启动详细监控 + - 热启动监控机制 + - TTID/TTFD 性能指标 + - 性能数据收集上报 + - 启动性能分析 + - 配置和最佳实践 + - 故障排查指南 + +### 🎨 [UI 卡顿监控机制](./sentry-ui-jank-monitoring.md) +- **适用对象**: Android 开发者、UI/UX 工程师 +- **内容**: UI 卡顿和帧率监控的深度分析 +- **包含**: + - 双重帧率监控策略 + - 慢帧和冻结帧检测 + - Performance V1 vs V2 对比 + - 实时帧数据收集 + - Span 级别帧分析 + - 帧插值和补偿机制 + - 性能指标和阈值 + - 配置和最佳实践 + - 故障排查指南 + +### 📱 [会话管理机制](./sentry-session-management.md) +- **适用对象**: 所有开发者、产品经理 +- **内容**: 用户会话生命周期和状态管理的完整分析 +- **包含**: + - 会话数据结构和状态枚举 + - 会话生命周期管理 + - 前台后台切换处理 + - 会话持久化机制 + - 异常状态更新 + - 性能优化策略 + - 配置和最佳实践 + - 故障排查指南 + +### 🎬 [Replay 功能分析](./sentry-replay-analysis.md) +- **适用对象**: Android 开发者、QA 工程师 +- **内容**: Session Replay 屏幕录制和回放功能的深度分析 +- **包含**: + - 录制策略架构(全会话 vs 错误缓冲) + - 屏幕截图和视频编码 + - 缓存机制和存储优化 + - 事件捕获和同步 + - MP4 生成和压缩 + - 采样决策和性能影响 + - 配置和最佳实践 + - 故障排查指南 + +### 📊 [Profiling 性能分析](./sentry-profiling-analysis.md) +- **适用对象**: 性能工程师、高级开发者 +- **内容**: 方法调用跟踪和性能瓶颈识别的完整分析 +- **包含**: + - 事务性能分析 vs 连续性能分析 + - 方法调用跟踪(基于 Android Debug API) + - CPU、内存、帧率等多维度性能数据 + - 性能数据编码和传输 + - 智能采样策略 + - 设备信息集成 + - 配置和最佳实践 + - 故障排查指南 + +### 🌐 [网络监控机制](./sentry-network-monitoring.md) +- **适用对象**: 网络工程师、后端开发者 +- **内容**: HTTP 请求监控和网络性能分析的深度解析 +- **包含**: + - OkHttp 拦截器和事件监听器 + - 网络性能指标收集(DNS、连接、SSL等) + - HTTP 错误捕获和分析 + - 分布式追踪集成 + - Apollo GraphQL 支持 + - 面包屑记录和错误事件 + - 配置和最佳实践 + - 故障排查指南 + +## 🎨 时序图说明 + +文档中使用了 Mermaid 时序图来可视化复杂的初始化流程: + +```mermaid +graph LR + A[用户] --> B[快速参考] + A --> C[时序图] + A --> D[详细说明] + + B --> E[基本使用] + C --> F[流程理解] + D --> G[深度定制] + + style B fill:#e1f5fe + style C fill:#f3e5f5 + style D fill:#fff3e0 +``` + +## 🔧 使用建议 + +### 对于应用开发者 +1. 先阅读 [快速参考](./sentry-init-quick-reference.md) +2. 了解 [崩溃监控机制](./sentry-crash-monitoring.md) 确保正确配置 +3. 学习 [启动监控机制](./sentry-startup-monitoring.md) 优化启动性能 +4. 掌握 [会话管理机制](./sentry-session-management.md) 理解用户行为追踪 +5. 遇到问题时查看常见问题部分 +6. 需要深入理解时参考时序图 + +### 对于Android开发者 +1. 重点关注 [启动监控机制](./sentry-startup-monitoring.md) +2. 学习 [UI 卡顿监控机制](./sentry-ui-jank-monitoring.md) 优化界面流畅度 +3. 了解 [Replay 功能分析](./sentry-replay-analysis.md) 实现用户操作回放 +4. 掌握 [Profiling 性能分析](./sentry-profiling-analysis.md) 进行深度性能优化 +5. 理解冷启动和热启动的区别和优化策略 +6. 掌握TTID/TTFD性能指标和帧率指标的含义 +7. 配置合适的性能监控参数 + +### 对于UI/UX工程师 +1. 重点阅读 [UI 卡顿监控机制](./sentry-ui-jank-monitoring.md) +2. 理解慢帧和冻结帧对用户体验的影响 +3. 学习如何通过指标数据优化界面性能 +4. 掌握不同刷新率设备的性能标准 + +### 对于运维和测试人员 +1. 重点阅读 [崩溃监控机制](./sentry-crash-monitoring.md) +2. 了解 [启动监控机制](./sentry-startup-monitoring.md) 中的性能指标 +3. 关注故障排查和最佳实践部分 +4. 了解不同类型崩溃的检测原理 + +### 对于框架集成开发者 +1. 重点关注 [集成注册流程](./sentry-initialization-flow.md#集成注册流程) +2. 参考 [集成系统设计](./sentry-initialization-details.md#5-集成系统设计) +3. 查看现有集成的实现模式 +4. 了解崩溃监控的扩展点 + +### 对于性能工程师 +1. 重点阅读 [Profiling 性能分析](./sentry-profiling-analysis.md) +2. 学习 [启动监控机制](./sentry-startup-monitoring.md) 和 [UI 卡顿监控机制](./sentry-ui-jank-monitoring.md) +3. 了解 [网络监控机制](./sentry-network-monitoring.md) 分析网络性能 +4. 掌握各种性能指标的含义和优化策略 +5. 配置合适的采样率和监控参数 + +### 对于网络工程师 +1. 重点阅读 [网络监控机制](./sentry-network-monitoring.md) +2. 了解分布式追踪和HTTP错误捕获 +3. 学习网络性能指标的收集和分析 +4. 掌握OkHttp集成和Apollo GraphQL支持 +5. 配置合适的网络监控策略 + +### 对于QA工程师 +1. 重点阅读 [Replay 功能分析](./sentry-replay-analysis.md) +2. 了解 [崩溃监控机制](./sentry-crash-monitoring.md) 和 [会话管理机制](./sentry-session-management.md) +3. 学习如何利用屏幕录制功能复现问题 +4. 掌握各种监控数据的解读和分析 +5. 配置合适的录制策略和采样率 + +### 对于 SDK 贡献者 +1. 完整阅读所有文档 +2. 重点理解线程安全和错误处理机制 +3. 关注性能优化和内存管理策略 +4. 深入研究各种监控机制的实现细节 +5. 了解扩展点和集成接口设计 + +## 📋 关键发现 + +通过对 Sentry Java SDK 的深入分析,我们发现了以下关键设计特点: + +### 🛡️ 健壮性设计 +- **多层锁机制**: 确保并发安全 +- **优雅降级**: 初始化失败不影响应用 +- **异常隔离**: 单个组件异常不影响整体 +- **多重崩溃检测**: Java异常、ANR、Native崩溃全覆盖 + +### ⚡ 性能优化 +- **懒加载**: 非关键组件延迟初始化 +- **异步处理**: 避免阻塞主线程 +- **资源管理**: 完善的清理机制 +- **智能去重**: 避免重复的多线程崩溃报告 + +### 🔌 扩展性 +- **插件化集成**: 统一的集成接口 +- **条件加载**: 根据环境动态加载功能 +- **自定义支持**: 丰富的扩展点 +- **平台特定优化**: Android、JVM、Native 分层处理 + +### 🎯 易用性 +- **合理默认值**: 开箱即用 +- **配置层次**: 灵活的配置优先级 +- **平台适配**: 自动检测和适配 +- **离线缓存**: 网络异常时的数据保护 + +### 💾 数据可靠性 +- **阻塞刷新**: 崩溃时确保数据写入磁盘 +- **标记文件**: 通过文件标记检测崩溃状态 +- **重试机制**: 网络失败时的自动重试 +- **启动崩溃特殊处理**: 阻塞发送确保关键数据不丢失 + +### 📱 启动性能监控 +- **智能类型检测**: 自动区分冷启动和热启动 +- **精确时间测量**: 使用系统级API获取准确启动时间 +- **细粒度分析**: 分解启动过程的各个阶段 +- **TTID/TTFD指标**: 关键用户体验指标监控 +- **性能分析集成**: CPU和内存分析支持 + +### 🎨 UI性能监控 +- **双重监控策略**: Performance V1和V2满足不同监控需求 +- **实时帧分析**: 基于Choreographer的精确帧时间测量 +- **智能帧分类**: 自动区分正常帧、慢帧和冻结帧 +- **刷新率适配**: 支持不同刷新率设备的准确监控 +- **内存优化**: 只存储异常帧数据,减少内存占用 +- **Span级别监控**: 精确到具体操作的帧性能分析 + +### 📱 会话管理 +- **生命周期跟踪**: 完整的会话状态管理(Ok、Crashed、Abnormal、Exited) +- **前后台感知**: 基于LifecycleWatcher的智能状态切换 +- **持久化机制**: 会话数据的可靠存储和恢复 +- **异常状态更新**: 实时更新会话状态反映应用健康度 +- **性能优化**: 高效的会话数据管理和内存控制 + +### 🎬 Replay功能 +- **双重录制策略**: 全会话录制 vs 错误缓冲录制 +- **智能采样**: 基于错误发生的动态策略转换 +- **视频编码**: 高效的MP4生成和压缩算法 +- **缓存机制**: 智能的帧存储和清理策略 +- **事件同步**: 屏幕录制与用户操作的精确同步 + +### 📊 Profiling性能分析 +- **双重分析模式**: 事务性能分析和连续性能分析 +- **方法级跟踪**: 基于Android Debug API的精确方法调用跟踪 +- **全面性能指标**: CPU、内存、帧率等多维度数据收集 +- **智能采样**: 可配置的采样策略平衡数据价值和性能影响 +- **设备信息集成**: 完整的设备和环境信息关联 + +### 🌐 网络监控 +- **双重监控架构**: 拦截器处理业务逻辑,事件监听器提供性能细节 +- **分布式追踪**: 自动注入追踪头,支持跨服务调用链追踪 +- **智能错误捕获**: 可配置的错误状态码和目标URL匹配 +- **详细性能指标**: DNS、连接、SSL等各阶段的精确时间测量 +- **多框架支持**: OkHttp、Apollo GraphQL等主流网络库集成 + +## ✅ 已完成的深度分析 + +### 核心监控机制 +- [x] **[初始化流程](./sentry-initialization-flow.md)** ✅ 完整的SDK启动和配置流程 +- [x] **[崩溃监控](./sentry-crash-monitoring.md)** ✅ Java异常、ANR、Native崩溃全覆盖 +- [x] **[启动监控](./sentry-startup-monitoring.md)** ✅ 冷启动、热启动性能监控和TTID/TTFD指标 +- [x] **[UI卡顿监控](./sentry-ui-jank-monitoring.md)** ✅ 帧率监控、慢帧检测、冻结帧分析 + +### 高级功能分析 +- [x] **[会话管理](./sentry-session-management.md)** ✅ Session生命周期、状态管理、持久化机制 +- [x] **[Replay功能](./sentry-replay-analysis.md)** ✅ 屏幕录制、事件回放、视频编码、存储优化 +- [x] **[Profiling性能分析](./sentry-profiling-analysis.md)** ✅ 方法调用跟踪、性能瓶颈识别、CPU/内存分析 +- [x] **[网络监控](./sentry-network-monitoring.md)** ✅ HTTP请求监控、性能指标收集、分布式追踪 + +### 快速参考文档 +- [x] **[初始化快速参考](./sentry-init-quick-reference.md)** ✅ 常用配置和代码示例 +- [x] **[初始化详细说明](./sentry-initialization-details.md)** ✅ 深度技术实现细节 + +## 🎯 核心技术发现 + +通过对 Sentry Java SDK 的全面分析,我们揭示了以下关键技术架构: + +### 🏗️ **架构设计哲学** +- **模块化设计**: 50+ 子模块,职责清晰,便于维护和扩展 +- **平台适配**: 统一的核心API,平台特定的实现(Android、JVM、Native) +- **插件化集成**: 通过Integration接口实现功能的条件加载 +- **线程安全**: 全面的锁机制和原子操作保证并发安全 + +### 🔧 **核心技术栈** +- **智能初始化**: 多重锁保护、平台检测、优雅降级 +- **全面监控**: 崩溃、性能、网络、用户行为的端到端监控 +- **高效传输**: Envelope封装、数据压缩、批量上报 +- **数据可靠性**: 本地缓存、重试机制、离线支持 + +### 📊 **性能优化策略** +- **懒加载**: 按需初始化组件,减少启动开销 +- **异步处理**: 后台线程处理数据收集和上报 +- **智能采样**: 可配置的采样率平衡数据价值和性能影响 +- **内存管理**: 自动清理、缓存限制、内存泄漏防护 + +## 🚀 后续扩展方向 + +虽然已经完成了核心功能的深度分析,但仍有一些高级主题值得进一步探索: + +### 🔄 数据传输层深度分析 +- [ ] **Envelope封装机制**: 事件、会话、性能数据的统一封装格式 +- [ ] **序列化优化**: JSON序列化性能优化和数据压缩策略 +- [ ] **批量上报机制**: 数据聚合、去重、优先级排序的实现 +- [ ] **离线缓存策略**: 网络异常时的数据持久化和恢复机制 +- [ ] **传输重试逻辑**: 指数退避、速率限制、故障转移的实现 + +### 🔌 集成生态系统分析 +- [ ] **Spring Boot深度集成**: 自动配置、健康检查、指标暴露的实现 +- [ ] **React Native集成**: 跨平台事件桥接和性能监控 +- [ ] **Kotlin Multiplatform**: 共享代码和平台特定实现的架构 +- [ ] **微服务集成模式**: 服务网格、API网关的监控集成 +- [ ] **CI/CD集成**: 构建时符号上传、发布监控的自动化 + +### 🛠️ 自定义监控扩展 +- [ ] **自定义Integration开发**: 插件化架构的扩展指南 +- [ ] **自定义Transport实现**: 私有化部署的数据传输定制 +- [ ] **自定义Scope和Context**: 业务特定的上下文信息管理 +- [ ] **自定义性能指标**: 业务KPI的监控和分析 +- [ ] **自定义采样策略**: 智能采样算法的实现和优化 + +### 📊 性能基准测试 +- [ ] **内存使用分析**: 不同配置下的内存占用对比 +- [ ] **CPU开销测量**: 监控功能对应用性能的量化影响 +- [ ] **网络流量分析**: 数据上报的带宽使用优化 +- [ ] **启动时间影响**: SDK初始化对应用启动的性能影响 +- [ ] **电池消耗评估**: 移动设备上的电量使用分析 + +### 🏭 生产环境最佳实践 +- [ ] **大规模部署策略**: 企业级部署的配置管理和监控 +- [ ] **多环境配置管理**: 开发、测试、生产环境的差异化配置 +- [ ] **监控数据治理**: 数据质量、存储成本、保留策略的管理 +- [ ] **告警和通知系统**: 智能告警规则和通知渠道的配置 +- [ ] **团队协作工具**: 问题分配、处理流程、知识库的建设 + +### 🔒 安全和隐私深度分析 +- [ ] **数据脱敏机制**: PII识别、过滤、匿名化的实现 +- [ ] **传输安全**: TLS配置、证书验证、中间人攻击防护 +- [ ] **存储安全**: 本地数据加密、密钥管理、安全删除 +- [ ] **合规性支持**: GDPR、CCPA等法规的技术实现 +- [ ] **审计日志**: 数据处理活动的完整记录和追踪 + +### 🚨 故障恢复机制 +- [ ] **网络异常处理**: 连接超时、DNS故障、代理问题的处理 +- [ ] **存储故障恢复**: 磁盘空间不足、权限问题的优雅处理 +- [ ] **内存压力管理**: OOM情况下的数据保护和恢复 +- [ ] **系统资源竞争**: 高负载下的资源调度和优先级管理 +- [ ] **灾难恢复**: 极端情况下的数据完整性保证 + +### 🔬 高级调试和诊断 +- [ ] **SDK内部监控**: SDK自身的性能和健康状态监控 +- [ ] **调试工具开发**: 开发者友好的调试界面和工具 +- [ ] **问题诊断自动化**: 常见问题的自动检测和修复建议 +- [ ] **性能瓶颈分析**: SDK内部热点代码的识别和优化 +- [ ] **兼容性测试**: 不同Android版本、设备的兼容性验证 + +### 🌐 云原生和容器化 +- [ ] **Kubernetes集成**: Pod监控、服务发现、配置管理 +- [ ] **Docker容器监控**: 容器生命周期、资源使用的监控 +- [ ] **服务网格集成**: Istio、Linkerd等的深度集成 +- [ ] **Serverless监控**: AWS Lambda、Azure Functions的监控适配 +- [ ] **边缘计算支持**: 边缘节点的监控和数据同步 + +### 🤖 AI和机器学习集成 +- [ ] **异常检测算法**: 基于ML的异常模式识别 +- [ ] **智能采样优化**: 动态调整采样率的AI算法 +- [ ] **根因分析**: 自动化的问题根因定位 +- [ ] **预测性维护**: 基于历史数据的故障预测 +- [ ] **用户行为分析**: 深度学习驱动的用户体验洞察 + +### 📱 移动端特定优化 +- [ ] **低功耗模式**: 电池优化的监控策略 +- [ ] **网络适配**: 2G/3G/4G/5G不同网络的优化 +- [ ] **存储优化**: 移动设备存储空间的高效利用 +- [ ] **后台任务管理**: 系统后台限制下的数据处理 +- [ ] **设备兼容性**: 不同厂商ROM的适配和优化 + +### 🔄 实时监控和流处理 +- [ ] **实时数据流**: 事件流的实时处理和分析 +- [ ] **流式聚合**: 实时指标计算和趋势分析 +- [ ] **实时告警**: 毫秒级的异常检测和通知 +- [ ] **热点数据**: 实时热点问题的识别和处理 +- [ ] **流量控制**: 实时流量整形和限流机制 + +### 📈 高级分析和可视化 +- [ ] **多维度分析**: 时间、地域、版本等维度的交叉分析 +- [ ] **趋势预测**: 基于历史数据的趋势预测模型 +- [ ] **对比分析**: A/B测试、版本对比的深度分析 +- [ ] **用户旅程**: 完整用户行为路径的可视化 +- [ ] **业务影响分析**: 技术指标与业务KPI的关联分析 + +这些扩展方向涵盖了从底层技术实现到上层业务应用的各个层面,为深入理解和优化 Sentry SDK 提供了全面的研究路径。每个方向都可以独立深入研究,也可以结合现有的核心分析文档进行综合应用。 + +## 📞 联系方式 + +如有问题或建议,请通过以下方式联系: +- 创建 Issue 讨论 +- 提交 Pull Request 改进 +- 参与社区讨论 + +--- + +💡 **提示**: 这些文档基于 Sentry Java SDK 的源码分析,旨在帮助开发者更好地理解和使用 SDK。 \ No newline at end of file diff --git a/cursor/sentry-crash-monitoring.md b/cursor/sentry-crash-monitoring.md new file mode 100644 index 0000000000..bbfa6129e7 --- /dev/null +++ b/cursor/sentry-crash-monitoring.md @@ -0,0 +1,688 @@ +# Sentry 崩溃监控机制深度分析 + +本文档详细分析了 Sentry Java SDK 如何监控和处理各种类型的崩溃,包括 Java 异常、Android ANR、Native 崩溃等。 + +## 🎯 崩溃监控概览 + +Sentry 通过多层监控机制来捕获不同类型的崩溃: + +```mermaid +graph TD + A[应用运行] --> B{崩溃类型} + B --> C[Java 未捕获异常] + B --> D[Android ANR] + B --> E[Native 崩溃] + B --> F[启动崩溃] + + C --> G[UncaughtExceptionHandler] + D --> H[ANRWatchDog + AnrV2Integration] + E --> I[NDK Signal Handler] + F --> J[启动时间检测] + + G --> K[事件处理流程] + H --> K + I --> K + J --> K + + K --> L[事件处理器链] + L --> M[传输层] + M --> N[Sentry 服务器] +``` + +## 1. Java 未捕获异常监控 + +### 1.1 核心机制:UncaughtExceptionHandlerIntegration + +```java +public final class UncaughtExceptionHandlerIntegration + implements Integration, Thread.UncaughtExceptionHandler, Closeable { + + private @Nullable Thread.UncaughtExceptionHandler defaultExceptionHandler; + + @Override + public void register(final @NotNull IScopes scopes, final @NotNull SentryOptions options) { + // 保存原有的异常处理器 + final Thread.UncaughtExceptionHandler currentHandler = + threadAdapter.getDefaultUncaughtExceptionHandler(); + + if (currentHandler != null) { + defaultExceptionHandler = currentHandler; + } + + // 设置 Sentry 的异常处理器 + threadAdapter.setDefaultUncaughtExceptionHandler(this); + } +} +``` + +### 1.2 异常捕获流程 + +```mermaid +sequenceDiagram + participant App as 应用线程 + participant Handler as UncaughtExceptionHandler + participant Sentry as Sentry SDK + participant Transport as 传输层 + participant Original as 原始处理器 + + App->>Handler: 未捕获异常发生 + Handler->>Sentry: 创建 SentryEvent + Note over Handler: 设置 level = FATAL + Note over Handler: 标记 handled = false + + Handler->>Sentry: captureEvent(event, hint) + Sentry->>Transport: 发送事件 + + Note over Handler: 等待事件刷新到磁盘 + Handler->>Handler: waitFlush() + + alt 有原始处理器 + Handler->>Original: 调用原始处理器 + else 无原始处理器 + Handler->>App: printStackTrace() + end +``` + +### 1.3 关键实现细节 + +#### 异常包装机制 +```java +static Throwable getUnhandledThrowable( + final @NotNull Thread thread, final @NotNull Throwable thrown) { + final Mechanism mechanism = new Mechanism(); + mechanism.setHandled(false); // 标记为未处理 + mechanism.setType("UncaughtExceptionHandler"); + + return new ExceptionMechanismException(mechanism, thrown, thread); +} +``` + +#### 阻塞刷新机制 +```java +public void uncaughtException(Thread thread, Throwable thrown) { + final UncaughtExceptionHint exceptionHint = + new UncaughtExceptionHint(options.getFlushTimeoutMillis(), options.getLogger()); + + final SentryEvent event = new SentryEvent(throwable); + event.setLevel(SentryLevel.FATAL); + + final @NotNull SentryId sentryId = scopes.captureEvent(event, hint); + + // 阻塞等待事件刷新到磁盘 + if (!exceptionHint.waitFlush()) { + options.getLogger().log(SentryLevel.WARNING, + "Timed out waiting to flush event to disk before crashing."); + } +} +``` + +## 2. Android ANR 监控 + +### 2.1 双重 ANR 检测机制 + +Sentry Android 提供两种 ANR 检测方式: + +#### 方式一:ANRWatchDog (实时检测) +```java +final class ANRWatchDog extends Thread { + private final Runnable ticker = () -> { + lastKnownActiveUiTimestampMs = timeProvider.getCurrentTimeMillis(); + reported.set(false); + }; + + @Override + public void run() { + while (!isInterrupted()) { + uiHandler.post(ticker); // 向主线程发送心跳 + Thread.sleep(pollingIntervalMs); + + final long unresponsiveDurationMs = + timeProvider.getCurrentTimeMillis() - lastKnownActiveUiTimestampMs; + + if (unresponsiveDurationMs > timeoutIntervalMillis) { + if (isProcessNotResponding() && reported.compareAndSet(false, true)) { + final ApplicationNotResponding error = + new ApplicationNotResponding(message, uiHandler.getThread()); + anrListener.onAppNotResponding(error); + } + } + } + } +} +``` + +#### 方式二:AnrV2Integration (系统级检测) +```java +public final class AnrV2Integration implements Integration { + private void reportAsSentryEvent(final @NotNull ApplicationExitInfo exitInfo) { + // 解析系统提供的线程转储 + final ParseResult result = parseThreadDump(exitInfo, isBackground); + + final SentryEvent event = new SentryEvent(); + if (result.type == ParseResult.Type.DUMP) { + event.setThreads(result.threads); // 设置线程信息 + } + + event.setLevel(SentryLevel.FATAL); + event.setTimestamp(DateUtils.getDateTime(exitInfo.getTimestamp())); + + scopes.captureEvent(event, hint); + } +} +``` + +### 2.2 ANR 检测流程 + +```mermaid +sequenceDiagram + participant MainThread as 主线程 + participant WatchDog as ANRWatchDog + participant System as Android系统 + participant AnrV2 as AnrV2Integration + participant Sentry as Sentry SDK + + Note over WatchDog: 实时检测方式 + loop 每500ms + WatchDog->>MainThread: post(ticker) + alt 主线程响应 + MainThread->>WatchDog: 更新心跳时间 + else 主线程无响应 > 5s + WatchDog->>Sentry: 报告ANR事件 + end + end + + Note over System: 系统级检测方式 + System->>System: 检测到ANR + System->>System: 生成ApplicationExitInfo + + Note over AnrV2: 应用重启后 + AnrV2->>System: 查询ApplicationExitInfo + AnrV2->>AnrV2: 解析线程转储 + AnrV2->>Sentry: 报告历史ANR事件 +``` + +### 2.3 线程转储解析 + +```java +public class ThreadDumpParser { + public @NotNull List parse(final @NotNull Lines lines) { + final List threads = new ArrayList<>(); + + while (lines.hasNext()) { + final String line = lines.next(); + + if (THREAD_STATE_RE.matcher(line).matches()) { + final SentryThread thread = parseThread(lines, line, isBackground); + if (thread != null) { + threads.add(thread); + } + } + } + + return threads; + } + + private @Nullable SentryThread parseThread(Lines lines, String threadLine, boolean isBackground) { + // 解析线程名称、状态、ID等信息 + final String threadName = extractThreadName(threadLine); + final String threadState = extractThreadState(threadLine); + + final SentryThread sentryThread = new SentryThread(); + sentryThread.setName(threadName); + sentryThread.setState(threadState); + + if (threadName != null && threadName.equals("main")) { + sentryThread.setMain(true); + sentryThread.setCrashed(true); // ANR中主线程被标记为崩溃 + } + + // 解析堆栈跟踪 + final SentryStackTrace stackTrace = parseStacktrace(lines, sentryThread); + sentryThread.setStacktrace(stackTrace); + + return sentryThread; + } +} +``` + +## 3. Native 崩溃监控 + +### 3.1 NDK 集成架构 + +```java +public final class SentryNdk { + public static void init(@NotNull final SentryAndroidOptions options) { + final @NotNull NdkOptions ndkOptions = new NdkOptions( + options.getDsn(), + options.isDebug(), + options.getOutboxPath(), + options.getRelease(), + options.getEnvironment() + ); + + // 初始化 Native SDK + io.sentry.ndk.SentryNdk.init(ndkOptions); + + // 启用作用域同步 + if (options.isEnableScopeSync()) { + options.addScopeObserver(new NdkScopeObserver(options)); + } + } +} +``` + +### 3.2 Native 崩溃处理流程 + +```mermaid +sequenceDiagram + participant App as Native代码 + participant Signal as 信号处理器 + participant NDK as Sentry NDK + participant Java as Java层 + participant Disk as 磁盘缓存 + + App->>Signal: Native崩溃 (SIGSEGV等) + Signal->>NDK: 捕获信号 + NDK->>NDK: 收集崩溃信息 + Note over NDK: - 寄存器状态
- 堆栈跟踪
- 内存映射 + + NDK->>Disk: 写入崩溃文件 + Note over Disk: 避免在信号处理器中
进行复杂操作 + + Note over Java: 应用重启后 + Java->>Disk: 检查崩溃标记文件 + Java->>Java: 读取崩溃信息 + Java->>Java: 创建SentryEvent + Java->>Java: 发送到Sentry服务器 +``` + +### 3.3 作用域同步机制 + +```java +public class NdkScopeObserver implements IScopeObserver { + @Override + public void setUser(final @Nullable User user) { + try { + options.getExecutorService().submit(() -> { + if (user != null) { + nativeScope.setUser( + user.getId(), + user.getEmail(), + user.getIpAddress(), + user.getUsername() + ); + } else { + nativeScope.removeUser(); + } + }); + } catch (Throwable e) { + options.getLogger().log(SentryLevel.ERROR, e, "Scope sync setUser has an error."); + } + } +} +``` + +## 4. 启动崩溃检测 + +### 4.1 启动崩溃定义 + +启动崩溃是指在应用启动后的短时间内(默认2秒)发生的崩溃: + +```java +public class AndroidEnvelopeCache extends EnvelopeCache { + @Override + public void store(@NotNull SentryEnvelope envelope, @NotNull Hint hint) { + super.store(envelope, hint); + + final TimeSpan sdkInitTimeSpan = AppStartMetrics.getInstance().getSdkInitTimeSpan(); + + if (HintUtils.hasType(hint, UncaughtExceptionHandlerIntegration.UncaughtExceptionHint.class) + && sdkInitTimeSpan.hasStarted()) { + + long timeSinceSdkInit = + currentDateProvider.getCurrentTimeMillis() - sdkInitTimeSpan.getStartUptimeMs(); + + if (timeSinceSdkInit <= options.getStartupCrashDurationThresholdMillis()) { + options.getLogger().log(DEBUG, + "Startup Crash detected %d milliseconds after SDK init.", timeSinceSdkInit); + writeStartupCrashMarkerFile(); + } + } + } +} +``` + +### 4.2 启动崩溃处理 + +```mermaid +sequenceDiagram + participant App as 应用启动 + participant SDK as Sentry SDK + participant Cache as 缓存系统 + participant Sender as 发送器 + + App->>SDK: Sentry.init() + SDK->>SDK: 记录初始化时间 + + Note over App: 应用崩溃 (< 2s) + App->>SDK: UncaughtException + SDK->>Cache: 检查启动时间 + Cache->>Cache: 写入启动崩溃标记 + + Note over App: 应用重启 + App->>SDK: Sentry.init() + SDK->>Cache: 检查启动崩溃标记 + Cache->>Sender: 阻塞发送启动崩溃事件 + Note over Sender: 等待最多5秒确保发送成功 + Sender->>SDK: 发送完成 + SDK->>App: 初始化完成 +``` + +## 5. 事件处理流程 + +### 5.1 事件捕获统一入口 + +```java +public class SentryClient implements ISentryClient { + @Override + public @NotNull SentryId captureEvent( + @NotNull SentryEvent event, + @Nullable IScope scope, + @Nullable Hint hint) { + + // 1. 验证和预处理 + if (shouldApplyScopeData(event, hint)) { + event = applyScope(event, scope, hint); + } + + // 2. 事件处理器链 + event = processEvent(event, hint, options.getEventProcessors()); + if (scope != null) { + event = processEvent(event, hint, scope.getEventProcessors()); + } + + // 3. beforeSend 回调 + event = executeBeforeSend(event, hint); + + // 4. 构建信封并发送 + final SentryEnvelope envelope = buildEnvelope(event, attachments, session, traceContext); + return sendEnvelope(envelope, hint); + } +} +``` + +### 5.2 事件处理器链 + +```mermaid +graph LR + A[原始事件] --> B[MainEventProcessor] + B --> C[DuplicateEventDetectionEventProcessor] + C --> D[DeduplicateMultithreadedEventProcessor] + D --> E[SentryRuntimeEventProcessor] + E --> F[自定义EventProcessor] + F --> G[beforeSend回调] + G --> H[传输层] + + style B fill:#e1f5fe + style C fill:#f3e5f5 + style D fill:#fff3e0 + style E fill:#e8f5e8 +``` + +#### 关键处理器说明 + +**MainEventProcessor**: 添加设备信息、线程信息、上下文数据 +```java +public class MainEventProcessor implements EventProcessor { + @Override + public @NotNull SentryEvent process(@NotNull SentryEvent event, @NotNull Hint hint) { + // 设置设备信息 + setDevice(event); + // 设置操作系统信息 + setOperatingSystem(event); + // 设置运行时信息 + setRuntime(event); + // 设置应用信息 + setApp(event); + // 处理线程信息 + setThreads(event, hint); + + return event; + } +} +``` + +**DeduplicateMultithreadedEventProcessor**: 多线程崩溃去重 +```java +public class DeduplicateMultithreadedEventProcessor implements EventProcessor { + @Override + public @Nullable SentryEvent process(@NotNull SentryEvent event, @NotNull Hint hint) { + // 只处理来自 UncaughtExceptionHandler 的崩溃 + if (!HintUtils.hasType(hint, UncaughtExceptionHandlerIntegration.UncaughtExceptionHint.class)) { + return event; + } + + final SentryException exception = event.getUnhandledException(); + if (exception == null) return event; + + final String type = exception.getType(); + final Long currentEventTid = exception.getThreadId(); + + // 检查是否已经处理过相同类型的异常 + final Long tid = processedEvents.get(type); + if (tid != null && !tid.equals(currentEventTid)) { + // 丢弃重复的多线程崩溃 + HintUtils.setEventDropReason(hint, EventDropReason.MULTITHREADED_DEDUPLICATION); + return null; + } + + processedEvents.put(type, currentEventTid); + return event; + } +} +``` + +## 6. 传输和持久化 + +### 6.1 离线缓存机制 + +```java +public class EnvelopeCache implements IEnvelopeCache { + @Override + public void store(@NotNull SentryEnvelope envelope, @NotNull Hint hint) { + // 写入崩溃标记文件 + if (HintUtils.hasType(hint, UncaughtExceptionHandlerIntegration.UncaughtExceptionHint.class)) { + writeCrashMarkerFile(); + } + + // 将信封写入磁盘 + final File envelopeFile = getEnvelopeFile(envelope); + writeEnvelopeToDisk(envelopeFile, envelope); + } + + private void writeCrashMarkerFile() { + final File crashMarkerFile = new File(options.getCacheDirPath(), CRASH_MARKER_FILE); + try { + crashMarkerFile.createNewFile(); + } catch (IOException e) { + options.getLogger().log(SentryLevel.ERROR, "Failed to create crash marker file.", e); + } + } +} +``` + +### 6.2 崩溃恢复流程 + +```mermaid +sequenceDiagram + participant App as 应用重启 + participant Cache as 缓存系统 + participant Sender as 发送器 + participant Server as Sentry服务器 + + App->>Cache: 检查崩溃标记文件 + alt 发现崩溃标记 + Cache->>Cache: 设置 crashedLastRun = true + Cache->>Cache: 删除崩溃标记文件 + end + + Cache->>Sender: 扫描缓存目录 + Sender->>Sender: 读取未发送的信封 + + loop 每个缓存的信封 + Sender->>Server: 发送信封 + alt 发送成功 + Sender->>Cache: 删除缓存文件 + else 发送失败 + Sender->>Cache: 保留缓存文件 + end + end +``` + +## 7. 性能优化策略 + +### 7.1 异步处理 + +```java +public class UncaughtExceptionHandlerIntegration { + @Override + public void uncaughtException(Thread thread, Throwable thrown) { + try { + // 创建事件(同步,快速) + final SentryEvent event = new SentryEvent(throwable); + event.setLevel(SentryLevel.FATAL); + + // 捕获事件(可能异步) + final @NotNull SentryId sentryId = scopes.captureEvent(event, hint); + + // 阻塞等待刷新(确保数据不丢失) + if (!exceptionHint.waitFlush()) { + options.getLogger().log(SentryLevel.WARNING, + "Timed out waiting to flush event to disk before crashing."); + } + } catch (Throwable e) { + // 异常处理不能影响原始崩溃流程 + options.getLogger().log(SentryLevel.ERROR, + "Error sending uncaught exception to Sentry.", e); + } + + // 调用原始异常处理器 + if (defaultExceptionHandler != null) { + defaultExceptionHandler.uncaughtException(thread, thrown); + } + } +} +``` + +### 7.2 内存管理 + +```java +public class SentryClient { + private @NotNull SentryId sendEnvelope(@NotNull SentryEnvelope envelope, @Nullable Hint hint) { + try { + // 发送前清理 hint 中的大对象 + hint.clear(); + + if (hint == null) { + transport.send(envelope); + } else { + transport.send(envelope, hint); + } + } finally { + // 确保资源释放 + if (hint != null) { + hint.clear(); + } + } + } +} +``` + +## 8. 配置和最佳实践 + +### 8.1 关键配置选项 + +```java +// 基础配置 +options.setDsn("YOUR_DSN"); +options.setEnvironment("production"); +options.setRelease("1.0.0"); + +// 崩溃相关配置 +options.setEnableUncaughtExceptionHandler(true); // 启用未捕获异常处理 +options.setFlushTimeoutMillis(5000); // 崩溃时刷新超时 +options.setPrintUncaughtStackTrace(false); // 生产环境关闭堆栈打印 + +// Android 特定配置 +options.setAnrEnabled(true); // 启用ANR检测 +options.setAnrTimeoutIntervalMillis(5000); // ANR超时时间 +options.setAnrReportInDebug(false); // 调试时不报告ANR + +// Native 崩溃配置 +options.setEnableNdk(true); // 启用NDK崩溃捕获 +options.setEnableScopeSync(true); // 启用作用域同步 + +// 启动崩溃配置 +options.setStartupCrashDurationThresholdMillis(2000); // 启动崩溃时间阈值 +options.setStartupCrashFlushTimeoutMillis(5000); // 启动崩溃刷新超时 +``` + +### 8.2 最佳实践 + +#### ✅ 推荐做法 +- **尽早初始化**: 在 Application.onCreate() 中初始化 Sentry +- **合理配置超时**: 根据应用特点调整刷新超时时间 +- **启用所有监控**: 同时启用 Java、ANR、Native 崩溃监控 +- **测试崩溃恢复**: 验证崩溃后的数据恢复机制 + +#### ❌ 避免做法 +- **在崩溃处理器中执行复杂操作**: 可能导致二次崩溃 +- **忽略启动崩溃**: 启动崩溃往往影响更严重 +- **禁用缓存机制**: 可能导致崩溃数据丢失 +- **过短的超时时间**: 可能导致崩溃数据未完全写入 + +## 9. 故障排查 + +### 9.1 常见问题 + +**Q: 崩溃事件没有上报?** +A: 检查网络连接、DSN配置、是否启用了相应的集成 + +**Q: ANR 检测不准确?** +A: 调整 ANR 超时时间,检查是否在调试模式下运行 + +**Q: Native 崩溃信息不完整?** +A: 确保符号文件已上传,检查 NDK 版本兼容性 + +**Q: 启动崩溃处理缓慢?** +A: 调整启动崩溃刷新超时时间,优化网络配置 + +### 9.2 调试技巧 + +```java +// 启用详细日志 +options.setDebug(true); +options.setLogger(new SystemOutLogger()); + +// 监控崩溃处理状态 +options.setBeforeEnvelopeCallback((envelope, hint) -> { + System.out.println("Sending envelope: " + envelope.getHeader().getEventId()); +}); + +// 检查崩溃标记文件 +File crashMarker = new File(options.getCacheDirPath(), "sentry-java-crash-marker"); +if (crashMarker.exists()) { + System.out.println("Previous session crashed"); +} +``` + +## 总结 + +Sentry 的崩溃监控机制通过多层防护确保了各种类型崩溃的可靠捕获: + +1. **全面覆盖**: Java异常、Android ANR、Native崩溃、启动崩溃 +2. **可靠传输**: 离线缓存、重试机制、阻塞刷新 +3. **性能优化**: 异步处理、内存管理、去重机制 +4. **易于集成**: 自动注册、合理默认值、灵活配置 + +这套机制确保了在各种异常情况下,崩溃信息都能被准确捕获并可靠地发送到 Sentry 服务器,为开发者提供完整的崩溃分析数据。 \ No newline at end of file diff --git a/cursor/sentry-init-quick-reference.md b/cursor/sentry-init-quick-reference.md new file mode 100644 index 0000000000..4d8b559aaa --- /dev/null +++ b/cursor/sentry-init-quick-reference.md @@ -0,0 +1,155 @@ +# Sentry 初始化快速参考 + +## 🚀 核心初始化步骤 + +### 1. 基本 Java 初始化 +```java +Sentry.init(options -> { + options.setDsn("YOUR_DSN_HERE"); + options.setDebug(true); +}); +``` + +### 2. Android 初始化 +```java +SentryAndroid.init(this, options -> { + options.setDsn("YOUR_DSN_HERE"); + options.setDebug(true); +}); +``` + +## 📋 初始化流程概览 + +```mermaid +graph TD + A[用户调用 init] --> B[获取锁] + B --> C[平台检查] + C --> D[配置加载] + D --> E[创建 Scopes] + E --> F[初始化客户端] + F --> G[注册集成] + G --> H[启动服务] + H --> I[初始化完成] +``` + +## 🔧 关键组件 + +| 组件 | 作用 | 关键类 | +|------|------|--------| +| **Sentry** | 主入口类 | `Sentry.java` | +| **Options** | 配置管理 | `SentryOptions.java` | +| **Scopes** | 上下文管理 | `IScopes.java` | +| **Client** | 事件处理 | `SentryClient.java` | +| **Transport** | 网络传输 | `ITransport.java` | +| **Integration** | 框架集成 | `Integration.java` | + +## ⚙️ 配置优先级 + +1. 🔴 **用户代码** - `options.setXxx()` +2. 🟠 **配置文件** - `sentry.properties` +3. 🟡 **系统属性** - `-Dsentry.xxx` +4. 🟢 **环境变量** - `SENTRY_XXX` +5. 🔵 **Manifest** - `` (Android) +6. ⚪ **默认值** + +## 🔒 线程安全机制 + +```java +// 全局锁保护初始化 +private static final AutoClosableReentrantLock lock = new AutoClosableReentrantLock(); + +// volatile 变量确保可见性 +private static volatile IScopes rootScopes = NoOpScopes.getInstance(); +``` + +## 📱 Android 特定流程 + +```mermaid +sequenceDiagram + participant App as Application + participant SA as SentryAndroid + participant S as Sentry + + App->>SA: init(context, config) + SA->>SA: 检查集成可用性 + SA->>SA: 加载 Manifest 配置 + SA->>SA: 安装默认集成 + SA->>S: 调用核心初始化 + S->>SA: 初始化完成 + SA->>SA: 启动会话跟踪 +``` + +## 🔌 常用集成 + +### Java 集成 +- `UncaughtExceptionHandlerIntegration` - 全局异常捕获 +- `ShutdownHookIntegration` - 应用关闭处理 + +### Android 集成 +- `ActivityLifecycleIntegration` - Activity 生命周期 +- `AnrIntegration` - ANR 检测 +- `NetworkBreadcrumbsIntegration` - 网络监控 +- `FragmentLifecycleIntegration` - Fragment 生命周期 + +### Spring 集成 +- `SentrySpringIntegration` - Spring 框架集成 +- `SentryWebMvcIntegration` - Web MVC 集成 + +## 🎯 最佳实践 + +### ✅ 推荐做法 +- 在应用启动时尽早初始化 +- 使用环境变量管理不同环境的 DSN +- 启用调试模式进行问题排查 +- 合理设置采样率控制数据量 + +### ❌ 避免做法 +- 重复调用 `init()` 方法 +- 在代码中硬编码敏感信息 +- 忽略初始化异常 +- 在生产环境启用调试模式 + +## 🐛 常见问题 + +### Q: 初始化失败怎么办? +A: 检查 DSN 格式、网络连接、权限配置 + +### Q: Android 上集成不生效? +A: 确认使用 `SentryAndroid.init()` 而非 `Sentry.init()` + +### Q: 如何自定义集成? +A: 实现 `Integration` 接口并在配置中添加 + +### Q: 性能影响如何? +A: SDK 使用异步处理,对应用性能影响极小 + +## 📊 监控指标 + +初始化完成后可监控: +- 错误捕获率 +- 性能指标 +- 用户会话 +- 发布健康度 + +## 🔍 调试技巧 + +```java +// 启用详细日志 +options.setDebug(true); +options.setLogger(new SystemOutLogger()); + +// 检查初始化状态 +if (Sentry.isEnabled()) { + System.out.println("Sentry 初始化成功"); +} +``` + +## 📚 相关文档 + +- [完整初始化流程图](./sentry-initialization-flow.md) +- [详细实现说明](./sentry-initialization-details.md) +- [官方文档](https://docs.sentry.io/platforms/java/) + +--- + +💡 **提示**: 这个快速参考涵盖了 Sentry 初始化的核心要点,详细信息请参考完整文档。 \ No newline at end of file diff --git a/cursor/sentry-initialization-details.md b/cursor/sentry-initialization-details.md new file mode 100644 index 0000000000..1cb74250b4 --- /dev/null +++ b/cursor/sentry-initialization-details.md @@ -0,0 +1,352 @@ +# Sentry 初始化流程详细说明 + +本文档详细解释了 Sentry Java SDK 初始化流程中的关键细节、设计决策和实现原理。 + +## 1. 线程安全和锁机制 + +### AutoClosableReentrantLock 的使用 + +```java +private static final AutoClosableReentrantLock lock = new AutoClosableReentrantLock(); + +private static void init(final @NotNull SentryOptions options, final boolean globalHubMode) { + try (final @NotNull ISentryLifecycleToken ignored = lock.acquire()) { + // 初始化逻辑 + } +} +``` + +**设计原因:** +- 确保多线程环境下的初始化安全 +- 防止并发初始化导致的状态不一致 +- 使用 try-with-resources 确保锁的正确释放 + +### 静态变量的线程安全 + +```java +private static volatile @NotNull IScopesStorage scopesStorage = NoOpScopesStorage.getInstance(); +private static volatile @NotNull IScopes rootScopes = NoOpScopes.getInstance(); +private static volatile boolean globalHubMode = GLOBAL_HUB_DEFAULT_MODE; +``` + +**设计原因:** +- `volatile` 关键字确保可见性 +- 静态变量提供全局访问点 +- 默认使用 NoOp 实现避免空指针异常 + +## 2. 配置优先级和合并策略 + +### 配置来源优先级(从高到低) + +1. **用户代码配置** - `OptionsConfiguration.configure()` +2. **外部配置文件** - `sentry.properties` +3. **系统属性** - `-Dsentry.xxx` +4. **环境变量** - `SENTRY_XXX` +5. **Android Manifest** - `` +6. **默认值** + +### 配置合并逻辑 + +```java +if (options.isEnableExternalConfiguration()) { + options.merge(ExternalOptions.from(PropertiesProviderFactory.create(), options.getLogger())); +} +``` + +**实现细节:** +- 只有非空值才会覆盖现有配置 +- 布尔值有特殊处理逻辑 +- 列表类型采用追加而非替换 + +## 3. 平台检测和适配 + +### Android 平台检测 + +```java +if (!options.getClass().getName().equals("io.sentry.android.core.SentryAndroidOptions") + && Platform.isAndroid()) { + throw new IllegalArgumentException( + "You are running Android. Please, use SentryAndroid.init. " + options.getClass().getName()); +} +``` + +**设计原因:** +- 确保 Android 环境使用正确的初始化方法 +- 避免功能缺失或配置错误 +- 提供清晰的错误信息 + +### 平台特定功能 + +```java +// Platform.java +public static boolean isAndroid() { + return "The Android Project".equals(System.getProperty("java.vendor")); +} + +public static boolean isJvm() { + return !isAndroid(); +} +``` + +## 4. Scopes 架构设计 + +### 三层 Scope 结构 + +```java +final IScope rootScope = new Scope(options); // 根作用域 +final IScope rootIsolationScope = new Scope(options); // 隔离作用域 +rootScopes = new Scopes(rootScope, rootIsolationScope, globalScope, "Sentry.init"); +``` + +**架构说明:** +- **Global Scope**: 全局共享,存储用户信息、标签等 +- **Isolation Scope**: 请求级别隔离,避免并发污染 +- **Current Scope**: 当前操作上下文,支持嵌套 + +### Scope 存储策略 + +```java +if (SentryOpenTelemetryMode.OFF == options.getOpenTelemetryMode()) { + scopesStorage = new DefaultScopesStorage(); // ThreadLocal 存储 +} else { + scopesStorage = ScopesStorageFactory.create(new LoadClass(), NoOpLogger.getInstance()); +} +``` + +**存储选择:** +- **DefaultScopesStorage**: 使用 ThreadLocal,适合传统应用 +- **OpenTelemetry 模式**: 使用 Context 传播,适合异步环境 + +## 5. 集成系统设计 + +### 集成接口定义 + +```java +public interface Integration { + void register(@NotNull IScopes scopes, @NotNull SentryOptions options); +} +``` + +### 集成注册时机 + +```java +// 在 Scopes 创建之后,客户端绑定之后注册 +for (final Integration integration : options.getIntegrations()) { + integration.register(ScopesAdapter.getInstance(), options); +} +``` + +**设计考虑:** +- 延迟注册确保依赖组件已就绪 +- 统一的注册接口简化扩展 +- 异常隔离避免单个集成影响整体 + +### Android 集成的条件加载 + +```java +final boolean isFragmentAvailable = + (isFragmentUpstreamAvailable && + classLoader.isClassAvailable(SENTRY_FRAGMENT_INTEGRATION_CLASS_NAME, options)); + +if (isFragmentAvailable) { + options.addIntegration(new FragmentLifecycleIntegration(application)); +} +``` + +**动态加载原因:** +- 避免可选依赖导致的 ClassNotFoundException +- 支持模块化应用架构 +- 减少不必要的资源消耗 + +## 6. 传输层设计 + +### 传输工厂模式 + +```java +ITransportFactory transportFactory = options.getTransportFactory(); +if (transportFactory instanceof NoOpTransportFactory) { + transportFactory = new AsyncHttpTransportFactory(); + options.setTransportFactory(transportFactory); +} +transport = transportFactory.create(options, requestDetailsResolver.resolve()); +``` + +**设计优势:** +- 支持自定义传输实现 +- 默认提供异步 HTTP 传输 +- 便于测试和模拟 + +### 请求详情解析 + +```java +public class RequestDetailsResolver { + public RequestDetails resolve() { + return new RequestDetails( + options.getDsn(), + options.getSdkVersion(), + options.getAuthToken() + ); + } +} +``` + +## 7. 错误处理和降级策略 + +### 初始化失败处理 + +```java +final boolean shouldInit = InitUtil.shouldInit(globalScope.getOptions(), options, isEnabled()); +if (!shouldInit) { + options.getLogger().log(SentryLevel.WARNING, + "This init call has been ignored due to priority being too low."); + return; +} +``` + +**降级策略:** +- 优先级检查避免重复初始化 +- 配置错误时使用 NoOp 实现 +- 异常不会阻止应用启动 + +### 集成注册异常处理 + +```java +try { + configuration.configure(options); +} catch (Throwable t) { + options.getLogger().log(SentryLevel.ERROR, + "Error in the 'OptionsConfiguration.configure' callback.", t); +} +``` + +## 8. 性能优化策略 + +### 懒加载机制 + +```java +try { + options.getExecutorService().submit(() -> options.loadLazyFields()); +} catch (RejectedExecutionException e) { + // 降级处理 +} +``` + +**优化点:** +- 非关键字段延迟加载 +- 异步执行避免阻塞主线程 +- 执行器关闭时的优雅降级 + +### 缓存和预热 + +```java +// 应用启动性能分析配置预热 +handleAppStartProfilingConfig(options, options.getExecutorService()); + +// 清理旧的性能分析文件 +if (f.lastModified() < classCreationTimestamp - TimeUnit.MINUTES.toMillis(5)) { + FileUtils.deleteRecursively(f); +} +``` + +## 9. 内存管理 + +### 资源清理 + +```java +public static void close() { + try (final @NotNull ISentryLifecycleToken ignored = lock.acquire()) { + final IScopes scopes = getCurrentScopes(); + rootScopes = NoOpScopes.getInstance(); + getScopesStorage().close(); // 清理 ThreadLocal + scopes.close(false); + } +} +``` + +**清理策略:** +- 显式清理 ThreadLocal 避免内存泄漏 +- 关闭所有集成释放资源 +- 重置为 NoOp 实现 + +### 弱引用和生命周期管理 + +```java +// 在 Android 中使用 Application 生命周期 +if (context.getApplicationContext() instanceof Application) { + appStartMetrics.registerApplicationForegroundCheck( + (Application) context.getApplicationContext()); +} +``` + +## 10. 调试和诊断 + +### 日志级别控制 + +```java +private static void initLogger(final @NotNull SentryOptions options) { + if (options.isDebug() && options.getLogger() instanceof NoOpLogger) { + options.setLogger(new SystemOutLogger()); + } +} +``` + +### 初始化状态跟踪 + +```java +options.getLogger().log(SentryLevel.INFO, "Initializing SDK with DSN: '%s'", options.getDsn()); +options.getLogger().log(SentryLevel.DEBUG, "GlobalHubMode: '%s'", String.valueOf(globalHubModeToUse)); +options.getLogger().log(SentryLevel.DEBUG, "Using openTelemetryMode %s", options.getOpenTelemetryMode()); +``` + +## 11. 扩展点和自定义 + +### 自定义集成 + +```java +public class CustomIntegration implements Integration { + @Override + public void register(@NotNull IScopes scopes, @NotNull SentryOptions options) { + // 自定义逻辑 + addIntegrationToSdkVersion("Custom"); + } +} + +// 使用 +Sentry.init(options -> { + options.addIntegration(new CustomIntegration()); +}); +``` + +### 自定义传输 + +```java +public class CustomTransportFactory implements ITransportFactory { + @Override + public ITransport create(SentryOptions options, RequestDetails requestDetails) { + return new CustomTransport(); + } +} +``` + +## 12. 最佳实践建议 + +### 初始化时机 +- **应用启动时**: 在 Application.onCreate() 中初始化 +- **避免延迟**: 尽早初始化以捕获启动期间的错误 +- **单次初始化**: 避免重复调用 init 方法 + +### 配置管理 +- **环境分离**: 不同环境使用不同的 DSN +- **敏感信息**: 避免在代码中硬编码 DSN +- **性能调优**: 根据应用特点调整采样率 + +### 错误处理 +- **优雅降级**: 初始化失败不应影响应用功能 +- **日志记录**: 启用调试日志便于问题排查 +- **监控指标**: 关注初始化成功率和耗时 + +这个设计确保了 Sentry SDK 的: +- **可靠性**: 完善的错误处理和降级策略 +- **性能**: 异步处理和懒加载优化 +- **扩展性**: 灵活的集成和自定义机制 +- **易用性**: 简单的 API 和合理的默认配置 \ No newline at end of file diff --git a/cursor/sentry-initialization-flow.md b/cursor/sentry-initialization-flow.md new file mode 100644 index 0000000000..e462bbfedb --- /dev/null +++ b/cursor/sentry-initialization-flow.md @@ -0,0 +1,366 @@ +# Sentry Java SDK 初始化流程时序图 + +本文档通过时序图的形式详细展示了 Sentry Java SDK 的初始化流程,包括核心组件的创建、配置加载、集成注册等关键步骤。 + +## 核心初始化流程 + +```mermaid +sequenceDiagram + participant User as 用户代码 + participant Sentry as Sentry + participant Lock as AutoClosableReentrantLock + participant Options as SentryOptions + participant Scopes as IScopes + participant Client as SentryClient + participant Transport as ITransport + participant Integrations as Integration[] + participant Storage as IScopesStorage + + User->>Sentry: init(OptionsConfiguration) + Sentry->>Lock: acquire() + Lock-->>Sentry: token + + Note over Sentry: 1. 平台检查 + alt Android平台 && 非SentryAndroidOptions + Sentry-->>User: throw IllegalArgumentException + end + + Note over Sentry: 2. 预初始化配置 + Sentry->>Sentry: preInitConfigurations(options) + Sentry->>Options: isEnableExternalConfiguration() + alt 启用外部配置 + Sentry->>Options: merge(ExternalOptions) + end + Sentry->>Options: getDsn() + alt DSN为空或无效 + Sentry->>Sentry: close() + Sentry-->>User: return false + end + Sentry->>Options: retrieveParsedDsn() + + Note over Sentry: 3. 检查是否应该初始化 + Sentry->>Sentry: shouldInit(options) + alt 不应该初始化 + Sentry-->>User: 记录警告并返回 + end + + Note over Sentry: 4. 关闭现有Scopes + Sentry->>Scopes: getCurrentScopes() + Sentry->>Scopes: close(true) + + Note over Sentry: 5. 创建新的Scopes结构 + Sentry->>Sentry: globalScope.replaceOptions(options) + Sentry->>Scopes: new Scope(options) [rootScope] + Sentry->>Scopes: new Scope(options) [rootIsolationScope] + Sentry->>Scopes: new Scopes(rootScope, rootIsolationScope, globalScope) + + Note over Sentry: 6. 初始化核心组件 + Sentry->>Sentry: initLogger(options) + Sentry->>Sentry: initForOpenTelemetryMaybe(options) + Sentry->>Sentry: initScopesStorage(options) + Sentry->>Storage: close() + alt OpenTelemetry模式关闭 + Sentry->>Storage: new DefaultScopesStorage() + else OpenTelemetry模式开启 + Sentry->>Storage: ScopesStorageFactory.create() + end + + Note over Sentry: 7. 设置Scopes存储 + Sentry->>Storage: set(rootScopes) + + Note over Sentry: 8. 初始化配置 + Sentry->>Sentry: initConfigurations(options) + Sentry->>Options: getOutboxPath() + alt outboxPath存在 + Sentry->>Sentry: 创建outbox目录 + end + Sentry->>Options: getCacheDirPath() + alt cacheDirPath存在 + Sentry->>Sentry: 创建cache目录 + Sentry->>Options: setEnvelopeDiskCache(EnvelopeCache) + end + Sentry->>Options: getProfilingTracesDirPath() + alt 性能分析启用 + Sentry->>Sentry: 创建profiling目录 + Sentry->>Sentry: 清理旧的trace文件 + end + + Note over Sentry: 9. 配置模块和调试元数据 + Sentry->>Options: setModulesLoader() + Sentry->>Options: setDebugMetaLoader() + Sentry->>Options: setThreadChecker() + Sentry->>Options: addPerformanceCollector() + + Note over Sentry: 10. 创建并绑定客户端 + Sentry->>Client: new SentryClient(options) + Client->>Transport: transportFactory.create(options) + Transport-->>Client: transport实例 + Client-->>Sentry: client实例 + Sentry->>Scopes: bindClient(client) + + Note over Sentry: 11. 注册集成 + loop 遍历所有集成 + Sentry->>Integrations: register(ScopesAdapter, options) + Integrations->>Options: 获取配置 + Integrations->>Scopes: 注册回调和监听器 + Integrations-->>Sentry: 注册完成 + end + + Note over Sentry: 12. 后续处理 + Sentry->>Sentry: notifyOptionsObservers(options) + Sentry->>Sentry: finalizePreviousSession(options, scopes) + Sentry->>Sentry: handleAppStartProfilingConfig(options) + + Sentry->>Lock: release() + Sentry-->>User: 初始化完成 +``` + +## Android 特定初始化流程 + +```mermaid +sequenceDiagram + participant User as 用户代码 + participant SentryAndroid as SentryAndroid + participant Context as Android Context + participant AndroidOptions as SentryAndroidOptions + participant AndroidInit as AndroidOptionsInitializer + participant AppStart as AppStartMetrics + participant Integrations as Android Integrations + + User->>SentryAndroid: init(context, configuration) + SentryAndroid->>SentryAndroid: staticLock.acquire() + + Note over SentryAndroid: 1. 检查可用的集成 + SentryAndroid->>SentryAndroid: 检查Timber可用性 + SentryAndroid->>SentryAndroid: 检查Fragment可用性 + SentryAndroid->>SentryAndroid: 检查Replay可用性 + + Note over SentryAndroid: 2. 创建Android特定组件 + SentryAndroid->>SentryAndroid: new BuildInfoProvider() + SentryAndroid->>SentryAndroid: new ActivityFramesTracker() + + Note over SentryAndroid: 3. 加载默认配置和元数据 + SentryAndroid->>AndroidInit: loadDefaultAndMetadataOptions(options, context) + AndroidInit->>Context: 读取AndroidManifest.xml + AndroidInit->>AndroidOptions: 设置默认值 + AndroidInit->>AndroidOptions: 应用元数据配置 + AndroidInit-->>SentryAndroid: 配置完成 + + Note over SentryAndroid: 4. 安装默认集成 + SentryAndroid->>AndroidInit: installDefaultIntegrations() + AndroidInit->>AndroidOptions: 添加ActivityLifecycleIntegration + AndroidInit->>AndroidOptions: 添加AnrIntegration + AndroidInit->>AndroidOptions: 添加NetworkBreadcrumbsIntegration + AndroidInit->>AndroidOptions: 添加SystemEventsBreadcrumbsIntegration + alt Fragment可用 + AndroidInit->>AndroidOptions: 添加FragmentLifecycleIntegration + end + alt Timber可用 + AndroidInit->>AndroidOptions: 添加SentryTimberIntegration + end + alt Replay可用 + AndroidInit->>AndroidOptions: 添加ReplayIntegration + end + + Note over SentryAndroid: 5. 执行用户配置回调 + SentryAndroid->>User: configuration.configure(options) + User-->>SentryAndroid: 配置完成 + + Note over SentryAndroid: 6. 设置应用启动指标 + SentryAndroid->>AppStart: getInstance() + alt 性能V2启用 + SentryAndroid->>AppStart: setStartedAt(Process.getStartUptimeMillis()) + end + SentryAndroid->>AppStart: registerApplicationForegroundCheck() + SentryAndroid->>AppStart: setSdkInitStartedAt(sdkInitMillis) + + Note over SentryAndroid: 7. 初始化集成和处理器 + SentryAndroid->>AndroidInit: initializeIntegrationsAndProcessors() + AndroidInit->>AndroidOptions: 添加DefaultAndroidEventProcessor + AndroidInit->>AndroidOptions: 添加PerformanceAndroidEventProcessor + AndroidInit->>AndroidOptions: 添加ScreenshotEventProcessor + AndroidInit->>AndroidOptions: 添加ViewHierarchyEventProcessor + AndroidInit->>AndroidOptions: 添加AnrV2EventProcessor + + Note over SentryAndroid: 8. 去重集成 + SentryAndroid->>SentryAndroid: deduplicateIntegrations() + + Note over SentryAndroid: 9. 调用核心Sentry初始化 + SentryAndroid->>Sentry: init(OptionsContainer, configuration, globalHubMode=true) + Note over Sentry: 执行核心初始化流程... + + Note over SentryAndroid: 10. 启动会话和Replay + SentryAndroid->>SentryAndroid: 检查前台重要性 + alt 应用在前台 + alt 自动会话跟踪启用 + SentryAndroid->>Scopes: startSession() + end + SentryAndroid->>Options: getReplayController().start() + end + + SentryAndroid-->>User: 初始化完成 +``` + +## 集成注册流程 + +```mermaid +sequenceDiagram + participant Sentry as Sentry + participant Integration as Integration + participant Scopes as IScopes + participant Options as SentryOptions + participant Platform as 平台组件 + + Note over Sentry: 遍历所有集成进行注册 + loop 每个集成 + Sentry->>Integration: register(scopes, options) + + Note over Integration: 集成特定的注册逻辑 + alt ActivityLifecycleIntegration + Integration->>Platform: application.registerActivityLifecycleCallbacks() + Integration->>Options: 配置性能监控 + Integration->>Options: 配置帧率跟踪 + else AnrIntegration + Integration->>Platform: 启动ANR监控线程 + Integration->>Platform: 设置ANR检测器 + else NetworkBreadcrumbsIntegration + Integration->>Platform: 注册网络状态监听器 + Integration->>Platform: 设置网络拦截器 + else SystemEventsBreadcrumbsIntegration + Integration->>Platform: 注册系统事件广播接收器 + Integration->>Platform: 设置Intent过滤器 + else UncaughtExceptionHandlerIntegration + Integration->>Platform: 设置全局异常处理器 + Integration->>Platform: 保存原有异常处理器 + else ShutdownHookIntegration + Integration->>Platform: 注册JVM关闭钩子 + end + + Note over Integration: 更新SDK版本信息 + Integration->>Options: addIntegrationToSdkVersion() + Integration->>Options: addPackageInfo() + + Integration-->>Sentry: 注册完成 + end +``` + +## 客户端创建流程 + +```mermaid +sequenceDiagram + participant Sentry as Sentry + participant Client as SentryClient + participant Options as SentryOptions + participant TransportFactory as ITransportFactory + participant Transport as ITransport + participant RequestResolver as RequestDetailsResolver + + Sentry->>Client: new SentryClient(options) + Client->>Options: 验证options非空 + Client->>Client: enabled = true + + Note over Client: 创建传输层 + Client->>Options: getTransportFactory() + alt TransportFactory是NoOp + Client->>TransportFactory: new AsyncHttpTransportFactory() + Client->>Options: setTransportFactory(transportFactory) + end + + Client->>RequestResolver: new RequestDetailsResolver(options) + Client->>RequestResolver: resolve() + RequestResolver-->>Client: RequestDetails + + Client->>TransportFactory: create(options, requestDetails) + TransportFactory->>Transport: 创建具体传输实现 + Transport-->>TransportFactory: transport实例 + TransportFactory-->>Client: transport实例 + + Client-->>Sentry: SentryClient实例 +``` + +## 配置加载流程 + +```mermaid +sequenceDiagram + participant Sentry as Sentry + participant Options as SentryOptions + participant External as ExternalOptions + participant Properties as PropertiesProvider + participant Manifest as AndroidManifest + participant Files as 配置文件 + + Note over Sentry: 预初始化配置检查 + Sentry->>Options: isEnableExternalConfiguration() + alt 启用外部配置 + Sentry->>Properties: PropertiesProviderFactory.create() + Properties->>Files: 读取sentry.properties + Properties->>Files: 读取系统属性 + Properties->>Files: 读取环境变量 + Properties-->>External: 配置数据 + Sentry->>External: from(properties, logger) + External-->>Options: 外部配置 + Sentry->>Options: merge(externalOptions) + end + + Note over Sentry: Android特定配置加载 + alt Android平台 + Sentry->>Manifest: 读取meta-data + Manifest->>Options: 设置DSN + Manifest->>Options: 设置debug模式 + Manifest->>Options: 设置采样率 + Manifest->>Options: 设置环境信息 + Manifest->>Options: 设置发布版本 + end + + Note over Sentry: DSN验证 + Sentry->>Options: getDsn() + alt DSN为空或禁用 + Sentry->>Sentry: close() + Sentry-->>Sentry: return false + else DSN无效 + Sentry-->>Sentry: throw IllegalArgumentException + end + + Sentry->>Options: retrieveParsedDsn() + Options->>Options: 解析和验证DSN格式 + Options-->>Sentry: 解析完成 +``` + +## 关键组件说明 + +### 1. **Sentry 主类** +- 提供静态API入口 +- 管理全局状态和锁 +- 协调整个初始化流程 + +### 2. **SentryOptions** +- 存储所有配置选项 +- 支持外部配置合并 +- 提供默认值和验证 + +### 3. **IScopes** +- 管理作用域层次结构 +- 提供线程安全的上下文管理 +- 支持作用域继承和隔离 + +### 4. **SentryClient** +- 核心事件处理客户端 +- 管理传输层连接 +- 处理事件序列化和发送 + +### 5. **Integration** +- 提供框架和平台集成 +- 自动注册监听器和钩子 +- 扩展SDK功能 + +### 6. **Transport** +- 处理网络传输 +- 实现重试和限流 +- 支持离线缓存 + +这个初始化流程确保了Sentry SDK能够: +- 正确加载配置 +- 安全地初始化组件 +- 注册必要的集成 +- 建立可靠的传输连接 +- 提供完整的错误监控功能 \ No newline at end of file diff --git a/cursor/sentry-network-monitoring.md b/cursor/sentry-network-monitoring.md new file mode 100644 index 0000000000..e048137e46 --- /dev/null +++ b/cursor/sentry-network-monitoring.md @@ -0,0 +1,1004 @@ +# Sentry 网络监控机制深度分析 + +本文档详细分析了 Sentry Java SDK 的网络监控功能,包括 OkHttp 集成、网络性能监控、HTTP 错误捕获、分布式追踪等核心实现。 + +## 🎯 网络监控概览 + +Sentry 通过拦截器和事件监听器,为 HTTP 请求提供全面的监控和追踪: + +```mermaid +graph TD + A[HTTP请求] --> B{监控类型} + B --> C[SentryOkHttpInterceptor] + B --> D[SentryOkHttpEventListener] + + C --> E[请求拦截] + D --> F[事件监听] + + E --> G[Span创建] + F --> H[性能事件] + + G --> I[分布式追踪] + H --> J[网络性能指标] + + I --> K[Trace Header注入] + J --> L[DNS解析时间] + J --> M[连接建立时间] + J --> N[请求响应时间] + + K --> O[请求执行] + L --> O + M --> O + N --> O + + O --> P{响应状态} + P --> Q[成功响应] + P --> R[错误响应] + + Q --> S[Breadcrumb记录] + R --> T[错误事件捕获] + + S --> U[数据上报] + T --> U + + style C fill:#e8f5e8 + style D fill:#fff3cd + style G fill:#e8f5e8 + style H fill:#fff3cd +``` + +## 1. SentryOkHttpInterceptor - 请求拦截器 + +### 1.1 核心功能架构 + +```kotlin +public open class SentryOkHttpInterceptor( + private val scopes: IScopes = ScopesAdapter.getInstance(), + private val beforeSpan: BeforeSpanCallback? = null, + private val captureFailedRequests: Boolean = true, + private val failedRequestStatusCodes: List = listOf( + HttpStatusCodeRange(HttpStatusCodeRange.DEFAULT_MIN, HttpStatusCodeRange.DEFAULT_MAX) + ), + private val failedRequestTargets: List = listOf(DEFAULT_PROPAGATION_TARGETS) +) : Interceptor { + + companion object { + private const val TRACE_ORIGIN = "auto.http.okhttp" + } +} +``` + +### 1.2 请求拦截处理 + +```kotlin +override fun intercept(chain: Interceptor.Chain): Response { + var request = chain.request() + + val urlDetails = UrlUtils.parse(request.url.toString()) + val url = urlDetails.urlOrFallback + val method = request.method + + val span: ISpan? + val okHttpEvent: SentryOkHttpEvent? + + // 检查是否有事件监听器创建的span + if (SentryOkHttpEventListener.eventMap.containsKey(chain.call())) { + // 从事件监听器读取span + okHttpEvent = SentryOkHttpEventListener.eventMap[chain.call()] + span = okHttpEvent?.callSpan + } else { + // 从当前scope读取span + okHttpEvent = null + val parentSpan = if (Platform.isAndroid()) scopes.transaction else scopes.span + span = parentSpan?.startChild("http.client", "$method $url") + } + + val startTimestamp = CurrentDateProvider.getInstance().currentTimeMillis + span?.spanContext?.origin = TRACE_ORIGIN + + // 应用URL详情到span + urlDetails.applyToSpan(span) + + val isFromEventListener = okHttpEvent != null + var response: Response? = null + var code: Int? = null + + try { + val requestBuilder = request.newBuilder() + + // 添加分布式追踪头 + if (!isIgnored()) { + TracingUtils.traceIfAllowed( + scopes, + request.url.toString(), + request.headers(BaggageHeader.BAGGAGE_HEADER), + span + )?.let { tracingHeaders -> + requestBuilder.addHeader( + tracingHeaders.sentryTraceHeader.name, + tracingHeaders.sentryTraceHeader.value + ) + tracingHeaders.baggageHeader?.let { + requestBuilder.removeHeader(BaggageHeader.BAGGAGE_HEADER) + requestBuilder.addHeader(it.name, it.value) + } + } + } + + request = requestBuilder.build() + response = chain.proceed(request) + code = response.code + + // 设置响应状态 + span?.setData(SpanDataConvention.HTTP_STATUS_CODE_KEY, code) + span?.status = SpanStatus.fromHttpStatusCode(code) + + // 捕获客户端错误 + if (shouldCaptureClientError(request, response)) { + if (isFromEventListener && okHttpEvent != null) { + okHttpEvent.setClientErrorResponse(response) + } else { + SentryOkHttpUtils.captureClientError(scopes, request, response) + } + } + + return response + } catch (e: IOException) { + span?.apply { + this.throwable = e + this.status = SpanStatus.INTERNAL_ERROR + } + throw e + } finally { + // 更新请求详情(拦截器可能会修改请求) + okHttpEvent?.setRequest(request) + + finishSpan(span, request, response, isFromEventListener, okHttpEvent) + + // 发送面包屑(如果不是从事件监听器来的) + if (!isFromEventListener) { + sendBreadcrumb(request, code, response, startTimestamp) + } + } +} +``` + +### 1.3 错误捕获机制 + +```kotlin +private fun shouldCaptureClientError(request: Request, response: Response): Boolean { + if (!captureFailedRequests) { + return false + } + + // 检查状态码范围 + val statusCode = response.code + val isFailedStatusCode = failedRequestStatusCodes.any { range -> + statusCode >= range.min && statusCode <= range.max + } + + if (!isFailedStatusCode) { + return false + } + + // 检查目标URL匹配 + val url = request.url.toString() + return PropagationTargetsUtils.contain(failedRequestTargets, url) +} + +// 在 SentryOkHttpUtils 中处理错误捕获 +internal object SentryOkHttpUtils { + internal fun captureClientError(scopes: IScopes, request: Request, response: Response) { + val urlDetails = UrlUtils.parse(request.url.toString()) + + val mechanism = Mechanism().apply { + type = "SentryOkHttpInterceptor" + } + + val exception = SentryHttpClientException( + "HTTP Client Error with status code: ${response.code}" + ) + + val mechanismException = ExceptionMechanismException( + mechanism, + exception, + Thread.currentThread(), + true + ) + + val event = SentryEvent(mechanismException) + + val hint = Hint() + hint.set(TypeCheckHint.OKHTTP_REQUEST, request) + hint.set(TypeCheckHint.OKHTTP_RESPONSE, response) + + // 设置请求信息 + event.request = Request().apply { + this.url = urlDetails.urlOrFallback + this.method = request.method + this.headers = HttpUtils.filterOutSecurityHeaders(request.headers.toMultimap()) + + // 如果启用了PII发送,包含查询参数 + if (scopes.options.isSendDefaultPii) { + this.queryString = urlDetails.query + } + } + + // 设置响应信息 + event.contexts.setResponse( + Response().apply { + this.statusCode = response.code + this.headers = HttpUtils.filterOutSecurityHeaders(response.headers.toMultimap()) + } + ) + + scopes.captureEvent(event, hint) + } +} +``` + +### 1.4 面包屑记录 + +```kotlin +private fun sendBreadcrumb( + request: Request, + code: Int?, + response: Response?, + startTimestamp: Long +) { + val urlDetails = UrlUtils.parse(request.url.toString()) + val breadcrumb = Breadcrumb().apply { + type = "http" + category = "http" + setData("url", urlDetails.urlOrFallback) + setData("method", request.method.uppercase()) + setData(SpanDataConvention.HTTP_START_TIMESTAMP, startTimestamp) + + code?.let { setData("status_code", it) } + + if (scopes.options.isSendDefaultPii) { + urlDetails.query?.let { setData("http.query", it) } + urlDetails.fragment?.let { setData("http.fragment", it) } + } + + // 计算请求持续时间 + val endTimestamp = CurrentDateProvider.getInstance().currentTimeMillis + val duration = endTimestamp - startTimestamp + setData("duration", duration) + + // 设置级别 + level = when { + code == null -> SentryLevel.ERROR + code >= 400 -> SentryLevel.WARNING + else -> SentryLevel.INFO + } + } + + val hint = Hint() + hint.set(TypeCheckHint.OKHTTP_REQUEST, request) + response?.let { hint.set(TypeCheckHint.OKHTTP_RESPONSE, it) } + + scopes.addBreadcrumb(breadcrumb, hint) +} +``` + +## 2. SentryOkHttpEventListener - 性能事件监听 + +### 2.1 事件监听架构 + +```kotlin +public open class SentryOkHttpEventListener( + private val scopes: IScopes = ScopesAdapter.getInstance(), + private val originalEventListenerCreator: ((call: Call) -> EventListener)? = null +) : EventListener() { + + companion object { + // 性能事件常量 + internal const val PROXY_SELECT_EVENT = "http.client.proxy_select_ms" + internal const val DNS_EVENT = "http.client.resolve_dns_ms" + internal const val CONNECT_EVENT = "http.connect_ms" + internal const val SECURE_CONNECT_EVENT = "http.connect.secure_connect_ms" + internal const val CONNECTION_EVENT = "http.connection_ms" + internal const val REQUEST_HEADERS_EVENT = "http.connection.request_headers_ms" + internal const val REQUEST_BODY_EVENT = "http.connection.request_body_ms" + internal const val RESPONSE_HEADERS_EVENT = "http.connection.response_headers_ms" + internal const val RESPONSE_BODY_EVENT = "http.connection.response_body_ms" + + // 事件映射表 + internal val eventMap: MutableMap = ConcurrentHashMap() + } +} +``` + +### 2.2 网络性能事件跟踪 + +```kotlin +// DNS解析监控 +override fun dnsStart(call: Call, domainName: String) { + originalEventListener?.dnsStart(call, domainName) + if (!canCreateEventSpan()) return + + val okHttpEvent: SentryOkHttpEvent = eventMap[call] ?: return + okHttpEvent.onEventStart(DNS_EVENT) +} + +override fun dnsEnd(call: Call, domainName: String, inetAddressList: List) { + originalEventListener?.dnsEnd(call, domainName, inetAddressList) + if (!canCreateEventSpan()) return + + val okHttpEvent: SentryOkHttpEvent = eventMap[call] ?: return + okHttpEvent.onEventFinish(DNS_EVENT) { span -> + span.setData("domain_name", domainName) + if (inetAddressList.isNotEmpty()) { + span.setData("dns_addresses", inetAddressList.joinToString { it.toString() }) + } + } +} + +// 连接建立监控 +override fun connectStart(call: Call, inetSocketAddress: InetSocketAddress, proxy: Proxy) { + originalEventListener?.connectStart(call, inetSocketAddress, proxy) + if (!canCreateEventSpan()) return + + val okHttpEvent: SentryOkHttpEvent = eventMap[call] ?: return + okHttpEvent.onEventStart(CONNECT_EVENT) +} + +override fun connectEnd( + call: Call, + inetSocketAddress: InetSocketAddress, + proxy: Proxy, + protocol: Protocol? +) { + originalEventListener?.connectEnd(call, inetSocketAddress, proxy, protocol) + if (!canCreateEventSpan()) return + + val okHttpEvent: SentryOkHttpEvent = eventMap[call] ?: return + okHttpEvent.setProtocol(protocol?.name) + okHttpEvent.onEventFinish(CONNECT_EVENT) +} + +// 连接失败处理 +override fun connectFailed( + call: Call, + inetSocketAddress: InetSocketAddress, + proxy: Proxy, + protocol: Protocol?, + ioe: IOException +) { + originalEventListener?.connectFailed(call, inetSocketAddress, proxy, protocol, ioe) + if (!canCreateEventSpan()) return + + val okHttpEvent: SentryOkHttpEvent = eventMap[call] ?: return + okHttpEvent.setProtocol(protocol?.name) + okHttpEvent.setError(ioe.message) + okHttpEvent.onEventFinish(CONNECT_EVENT) { span -> + span.throwable = ioe + span.status = SpanStatus.INTERNAL_ERROR + } +} + +// SSL握手监控 +override fun secureConnectStart(call: Call) { + originalEventListener?.secureConnectStart(call) + if (!canCreateEventSpan()) return + + val okHttpEvent: SentryOkHttpEvent = eventMap[call] ?: return + okHttpEvent.onEventStart(SECURE_CONNECT_EVENT) +} + +override fun secureConnectEnd(call: Call, handshake: Handshake?) { + originalEventListener?.secureConnectEnd(call, handshake) + if (!canCreateEventSpan()) return + + val okHttpEvent: SentryOkHttpEvent = eventMap[call] ?: return + okHttpEvent.onEventFinish(SECURE_CONNECT_EVENT) +} + +// 请求体监控 +override fun requestBodyStart(call: Call) { + originalEventListener?.requestBodyStart(call) + if (!canCreateEventSpan()) return + + val okHttpEvent: SentryOkHttpEvent = eventMap[call] ?: return + okHttpEvent.onEventStart(REQUEST_BODY_EVENT) +} + +override fun requestBodyEnd(call: Call, byteCount: Long) { + originalEventListener?.requestBodyEnd(call, byteCount) + if (!canCreateEventSpan()) return + + val okHttpEvent: SentryOkHttpEvent = eventMap[call] ?: return + okHttpEvent.onEventFinish(REQUEST_BODY_EVENT) { span -> + if (byteCount > 0) { + span.setData("http.request_content_length", byteCount) + } + } + okHttpEvent.setRequestBodySize(byteCount) +} + +// 响应体监控 +override fun responseBodyStart(call: Call) { + originalEventListener?.responseBodyStart(call) + if (!canCreateEventSpan()) return + + val okHttpEvent: SentryOkHttpEvent = eventMap[call] ?: return + okHttpEvent.onEventStart(RESPONSE_BODY_EVENT) +} + +override fun responseBodyEnd(call: Call, byteCount: Long) { + originalEventListener?.responseBodyEnd(call, byteCount) + if (!canCreateEventSpan()) return + + val okHttpEvent: SentryOkHttpEvent = eventMap[call] ?: return + okHttpEvent.setResponseBodySize(byteCount) + okHttpEvent.onEventFinish(RESPONSE_BODY_EVENT) { span -> + if (byteCount > 0) { + span.setData(SpanDataConvention.HTTP_RESPONSE_CONTENT_LENGTH_KEY, byteCount) + } + } +} +``` + +### 2.3 请求失败处理 + +```kotlin +override fun requestFailed(call: Call, ioe: IOException) { + originalEventListener?.requestFailed(call, ioe) + if (!canCreateEventSpan()) return + + val okHttpEvent: SentryOkHttpEvent = eventMap[call] ?: return + okHttpEvent.setError(ioe.message) + + // requestFailed可能在requestHeaders或requestBody之后发生 + // 如果requestHeaders已经完成,我们不改变其状态 + okHttpEvent.onEventFinish(REQUEST_HEADERS_EVENT) { span -> + if (!span.isFinished) { + span.status = SpanStatus.INTERNAL_ERROR + span.throwable = ioe + } + } + + okHttpEvent.onEventFinish(REQUEST_BODY_EVENT) { span -> + span.status = SpanStatus.INTERNAL_ERROR + span.throwable = ioe + } +} + +override fun callFailed(call: Call, ioe: IOException) { + originalEventListener?.callFailed(call, ioe) + if (!canCreateEventSpan()) return + + val okHttpEvent: SentryOkHttpEvent = eventMap[call] ?: return + okHttpEvent.setError(ioe.message) + okHttpEvent.finishCall { span -> + span.status = SpanStatus.INTERNAL_ERROR + span.throwable = ioe + } +} +``` + +## 3. SentryOkHttpEvent - 事件数据管理 + +### 3.1 事件数据结构 + +```kotlin +internal class SentryOkHttpEvent(private val scopes: IScopes, private val request: Request) { + private val eventDates: MutableMap = ConcurrentHashMap() + private val breadcrumb: Breadcrumb + internal val callSpan: ISpan? + private var response: Response? = null + private var clientErrorResponse: Response? = null + private val isEventFinished = AtomicBoolean(false) + private var url: String + private var method: String + + init { + val urlDetails = UrlUtils.parse(request.url.toString()) + url = urlDetails.urlOrFallback + method = request.method + + // 创建调用span,包含所有其他span + val parentSpan = if (Platform.isAndroid()) scopes.transaction else scopes.span + callSpan = parentSpan?.startChild("http.client") + callSpan?.spanContext?.origin = TRACE_ORIGIN + + // 创建面包屑 + breadcrumb = Breadcrumb().apply { + type = "http" + category = "http" + setData( + SpanDataConvention.HTTP_START_TIMESTAMP, + CurrentDateProvider.getInstance().currentTimeMillis + ) + } + + setRequest(request) + } +} +``` + +### 3.2 事件时间跟踪 + +```kotlin +fun onEventStart(eventName: String) { + eventDates[eventName] = CurrentDateProvider.getInstance().now() +} + +fun onEventFinish(eventName: String, spanDataCallback: ((ISpan) -> Unit)? = null) { + val startDate = eventDates[eventName] ?: return + val endDate = CurrentDateProvider.getInstance().now() + + // 创建子span + val span = callSpan?.startChild(eventName) + span?.setStartDate(startDate) + span?.finish(endDate) + + // 应用回调 + span?.let { spanDataCallback?.invoke(it) } + + // 移除事件记录 + eventDates.remove(eventName) +} + +fun finishCall(spanDataCallback: ((ISpan) -> Unit)? = null) { + if (isEventFinished.compareAndSet(false, true)) { + callSpan?.let { span -> + spanDataCallback?.invoke(span) + span.finish() + } + + // 发送面包屑 + sendBreadcrumb() + + // 处理客户端错误 + clientErrorResponse?.let { response -> + SentryOkHttpUtils.captureClientError(scopes, request, response) + } + } +} +``` + +### 3.3 请求响应数据设置 + +```kotlin +fun setRequest(request: Request) { + val urlDetails = UrlUtils.parse(request.url.toString()) + url = urlDetails.urlOrFallback + + val host: String = request.url.host + val encodedPath: String = request.url.encodedPath + method = request.method + + // 更新span描述和数据 + callSpan?.description = "$method $url" + urlDetails.applyToSpan(callSpan) + + // 更新面包屑数据 + breadcrumb.setData("host", host) + breadcrumb.setData("path", encodedPath) + if (urlDetails.url != null) { + breadcrumb.setData("url", urlDetails.url!!) + } + breadcrumb.setData("method", method.uppercase()) + + if (scopes.options.isSendDefaultPii) { + urlDetails.query?.let { breadcrumb.setData("http.query", it) } + urlDetails.fragment?.let { breadcrumb.setData("http.fragment", it) } + } + + // 设置span数据 + callSpan?.setData("url", url) + callSpan?.setData("host", host) + callSpan?.setData("path", encodedPath) + callSpan?.setData(SpanDataConvention.HTTP_METHOD_KEY, method.uppercase()) +} + +fun setResponse(response: Response) { + this.response = response + breadcrumb.setData("protocol", response.protocol.name) + breadcrumb.setData("status_code", response.code) + callSpan?.setData("protocol", response.protocol.name) + callSpan?.setData(SpanDataConvention.HTTP_STATUS_CODE_KEY, response.code) +} + +fun setRequestBodySize(byteCount: Long) { + if (byteCount > 0) { + breadcrumb.setData("request_body_size", byteCount) + callSpan?.setData("http.request_content_length", byteCount) + } +} + +fun setResponseBodySize(byteCount: Long) { + if (byteCount > 0) { + breadcrumb.setData("response_body_size", byteCount) + callSpan?.setData(SpanDataConvention.HTTP_RESPONSE_CONTENT_LENGTH_KEY, byteCount) + } +} +``` + +## 4. 分布式追踪集成 + +### 4.1 Trace Header 注入 + +```kotlin +// 在 TracingUtils 中处理追踪头注入 +object TracingUtils { + fun traceIfAllowed( + scopes: IScopes, + url: String, + baggageHeaders: List?, + span: ISpan? + ): TracingHeaders? { + + // 检查是否应该传播追踪 + if (!PropagationTargetsUtils.contain( + scopes.options.tracePropagationTargets, + url + )) { + return null + } + + val sentryTraceHeader = span?.toSentryTrace() + if (sentryTraceHeader == null) { + return null + } + + // 创建baggage头 + val baggageHeader = scopes.options.baggageManager?.toBaggageHeader( + scopes.baggage, + baggageHeaders + ) + + return TracingHeaders(sentryTraceHeader, baggageHeader) + } +} + +data class TracingHeaders( + val sentryTraceHeader: SentryTraceHeader, + val baggageHeader: BaggageHeader? +) +``` + +### 4.2 传播目标配置 + +```kotlin +// 配置追踪传播目标 +options.setTracePropagationTargets(listOf( + "api.example.com", + "*.internal.com", + "localhost" +)) + +// 默认传播目标 +public static final String DEFAULT_PROPAGATION_TARGETS = ".*"; + +// 检查URL是否匹配传播目标 +object PropagationTargetsUtils { + fun contain(targets: List, url: String): Boolean { + if (targets.isEmpty()) { + return false + } + + return targets.any { target -> + when { + target == DEFAULT_PROPAGATION_TARGETS -> true + target.contains("*") -> { + val regex = target.replace("*", ".*").toRegex() + regex.matches(url) + } + else -> url.contains(target, ignoreCase = true) + } + } + } +} +``` + +## 5. Apollo GraphQL 集成 + +### 5.1 Apollo 拦截器 + +```kotlin +class SentryApollo4HttpInterceptor @JvmOverloads constructor( + private val scopes: IScopes = ScopesAdapter.getInstance(), + private val beforeSpan: BeforeSpanCallback? = null, + private val captureFailedRequests: Boolean = DEFAULT_CAPTURE_FAILED_REQUESTS, + private val failedRequestTargets: List = listOf(DEFAULT_PROPAGATION_TARGETS) +) : HttpInterceptor { + + override suspend fun intercept( + request: HttpRequest, + chain: HttpInterceptorChain + ): HttpResponse { + + val url = request.url + val method = request.method.name + + // 创建span + val parentSpan = if (Platform.isAndroid()) scopes.transaction else scopes.span + val span = parentSpan?.startChild("http.client", "$method $url") + span?.spanContext?.origin = TRACE_ORIGIN + + // 设置span数据 + span?.setData(SpanDataConvention.HTTP_METHOD_KEY, method) + span?.setData("url", url) + + val startTimestamp = CurrentDateProvider.getInstance().currentTimeMillis + var response: HttpResponse? = null + + try { + // 注入追踪头 + val requestBuilder = request.newBuilder() + + TracingUtils.traceIfAllowed(scopes, url, null, span)?.let { tracingHeaders -> + requestBuilder.addHeader( + tracingHeaders.sentryTraceHeader.name, + tracingHeaders.sentryTraceHeader.value + ) + tracingHeaders.baggageHeader?.let { + requestBuilder.addHeader(it.name, it.value) + } + } + + response = chain.proceed(requestBuilder.build()) + + // 设置响应状态 + span?.setData(SpanDataConvention.HTTP_STATUS_CODE_KEY, response.statusCode) + span?.status = SpanStatus.fromHttpStatusCode(response.statusCode) + + // 检查GraphQL错误 + if (captureFailedRequests && shouldCaptureGraphQLError(response)) { + captureGraphQLError(request, response) + } + + return response + + } catch (e: Exception) { + span?.apply { + this.throwable = e + this.status = SpanStatus.INTERNAL_ERROR + } + throw e + } finally { + span?.finish() + sendBreadcrumb(request, response, startTimestamp) + } + } + + private suspend fun shouldCaptureGraphQLError(response: HttpResponse): Boolean { + // 检查响应体中是否包含GraphQL错误 + val body = response.body?.readUtf8() + return body?.contains("\"errors\"") == true + } +} +``` + +## 6. 配置和最佳实践 + +### 6.1 基本配置 + +```kotlin +// 创建OkHttp客户端 +val client = OkHttpClient.Builder() + .addInterceptor(SentryOkHttpInterceptor()) + .eventListener(SentryOkHttpEventListener()) + .build() + +// 高级配置 +val client = OkHttpClient.Builder() + .addInterceptor( + SentryOkHttpInterceptor( + captureFailedRequests = true, + failedRequestStatusCodes = listOf( + HttpStatusCodeRange(400, 599) // 捕获4xx和5xx错误 + ), + failedRequestTargets = listOf("api.example.com") + ) + ) + .eventListener(SentryOkHttpEventListener()) + .build() +``` + +### 6.2 Sentry 选项配置 + +```kotlin +// 配置追踪传播 +options.setTracePropagationTargets(listOf( + "api.example.com", + "*.internal.com" +)) + +// 启用PII发送(包含查询参数等敏感信息) +options.isSendDefaultPii = true + +// 配置HTTP状态码范围 +options.setFailedRequestStatusCodes(listOf( + HttpStatusCodeRange(400, 499), // 客户端错误 + HttpStatusCodeRange(500, 599) // 服务器错误 +)) + +// 配置失败请求目标 +options.setFailedRequestTargets(listOf( + "api.example.com", + "*.internal.com" +)) +``` + +### 6.3 自定义Span回调 + +```kotlin +val interceptor = SentryOkHttpInterceptor { span, request, response -> + // 自定义span数据 + span.setData("custom.user_agent", request.header("User-Agent")) + span.setData("custom.request_id", response?.header("X-Request-ID")) + + // 根据条件修改span + if (request.url.encodedPath.contains("/api/v1/")) { + span.setTag("api.version", "v1") + } + + // 可以返回null来丢弃span + span +} +``` + +### 6.4 性能优化建议 + +#### ✅ 推荐做法 + +1. **合理配置传播目标** + ```kotlin + // 只对内部API启用追踪传播 + options.setTracePropagationTargets(listOf( + "api.internal.com", + "*.internal.com" + )) + ``` + +2. **选择性错误捕获** + ```kotlin + // 只捕获服务器错误 + options.setFailedRequestStatusCodes(listOf( + HttpStatusCodeRange(500, 599) + )) + ``` + +3. **保护敏感信息** + ```kotlin + // 生产环境关闭PII发送 + options.isSendDefaultPii = false + ``` + +#### ❌ 避免做法 + +- **过度的追踪传播**:会增加请求头大小和处理开销 +- **捕获所有HTTP错误**:可能产生大量噪音事件 +- **在生产环境发送PII**:可能泄露敏感信息 + +### 6.5 网络性能指标 + +#### 关键指标说明 + +```kotlin +// DNS解析时间 +"http.client.resolve_dns_ms" + +// 连接建立时间 +"http.connect_ms" + +// SSL握手时间 +"http.connect.secure_connect_ms" + +// 请求头发送时间 +"http.connection.request_headers_ms" + +// 请求体发送时间 +"http.connection.request_body_ms" + +// 响应头接收时间 +"http.connection.response_headers_ms" + +// 响应体接收时间 +"http.connection.response_body_ms" +``` + +#### 性能分析示例 + +```kotlin +// 分析网络性能瓶颈 +fun analyzeNetworkPerformance(spans: List) { + spans.filter { it.operation == "http.client" }.forEach { httpSpan -> + val children = httpSpan.children + + val dnsTime = children.find { it.operation.contains("resolve_dns") }?.duration + val connectTime = children.find { it.operation.contains("connect_ms") }?.duration + val responseTime = children.find { it.operation.contains("response_headers") }?.duration + + println("DNS: ${dnsTime}ms, Connect: ${connectTime}ms, Response: ${responseTime}ms") + + // 识别性能瓶颈 + when { + dnsTime > 1000 -> println("DNS解析慢") + connectTime > 2000 -> println("连接建立慢") + responseTime > 5000 -> println("服务器响应慢") + } + } +} +``` + +## 7. 故障排查 + +### 7.1 常见问题 + +**Q: 网络请求没有被监控?** +A: 检查拦截器和事件监听器是否正确添加到OkHttp客户端 + +**Q: 分布式追踪不工作?** +A: 检查 `tracePropagationTargets` 配置,确保目标URL匹配 + +**Q: 产生太多HTTP错误事件?** +A: 调整 `failedRequestStatusCodes` 和 `failedRequestTargets` 配置 + +**Q: 性能事件数据不完整?** +A: 确保同时使用拦截器和事件监听器 + +### 7.2 调试技巧 + +```kotlin +// 启用网络监控调试 +options.setDebug(true) +options.setLogger(object : ILogger { + override fun log(level: SentryLevel, message: String, vararg args: Any?) { + if (message.contains("http") || message.contains("network")) { + println("[$level] $message") + } + } +}) + +// 检查span创建 +val transaction = Sentry.startTransaction("test", "http") +val httpSpan = transaction.startChild("http.client", "GET https://api.example.com") +println("HTTP Span created: ${httpSpan.spanId}") + +// 监控网络事件 +SentryOkHttpEventListener.eventMap.forEach { (call, event) -> + println("Call: ${call.request().url}, Event: ${event.callSpan?.description}") +} + +// 检查面包屑 +Sentry.configureScope { scope -> + scope.breadcrumbs.filter { it.type == "http" }.forEach { breadcrumb -> + println("HTTP Breadcrumb: ${breadcrumb.data}") + } +} +``` + +## 总结 + +Sentry 的网络监控机制通过拦截器和事件监听器的双重架构,为 HTTP 请求提供了全面的监控和分析能力: + +### 🎯 **核心优势** + +1. **双重监控架构**: 拦截器处理业务逻辑,事件监听器提供性能细节 +2. **分布式追踪**: 自动注入追踪头,支持跨服务调用链追踪 +3. **智能错误捕获**: 可配置的错误状态码和目标URL匹配 +4. **详细性能指标**: DNS、连接、SSL等各阶段的精确时间测量 +5. **多框架支持**: OkHttp、Apollo GraphQL等主流网络库集成 + +### 🔍 **技术特点** + +- **非侵入式集成**: 通过拦截器模式,无需修改业务代码 +- **性能友好**: 异步处理,最小化对请求性能的影响 +- **数据安全**: 可配置的PII过滤和敏感信息保护 +- **灵活配置**: 支持自定义错误捕获规则和传播目标 + +### 📊 **应用价值** + +通过这套网络监控系统,开发者可以: +- 识别网络性能瓶颈和优化点 +- 监控API错误率和响应时间 +- 追踪跨服务的请求调用链 +- 分析网络故障的根本原因 +- 优化用户体验和应用稳定性 + +这套机制为现代分布式应用的网络层监控提供了强有力的工具支撑,帮助开发者构建更可靠、更高性能的网络服务。 \ No newline at end of file diff --git a/cursor/sentry-profiling-analysis.md b/cursor/sentry-profiling-analysis.md new file mode 100644 index 0000000000..c004d90ff1 --- /dev/null +++ b/cursor/sentry-profiling-analysis.md @@ -0,0 +1,1002 @@ +# Sentry Profiling 性能分析深度解析 + +本文档详细分析了 Sentry Java SDK 的 Profiling 功能,包括事务性能分析、连续性能分析、性能数据收集、方法调用跟踪等核心实现。 + +## 🎯 Profiling 功能概览 + +Sentry Profiling 通过方法调用跟踪和性能指标收集,为开发者提供深度的性能洞察: + +```mermaid +graph TD + A[应用启动] --> B{Profiling类型} + B --> C[事务性能分析] + B --> D[连续性能分析] + + C --> E[AndroidTransactionProfiler] + D --> F[AndroidContinuousProfiler] + + E --> G[事务开始] + F --> H[手动/自动启动] + + G --> I[AndroidProfiler.start] + H --> I + + I --> J[Debug.startMethodTracingSampling] + J --> K[方法调用跟踪] + + K --> L[性能数据收集] + L --> M[帧率监控] + L --> N[CPU使用率] + L --> O[内存使用] + + M --> P[ProfileMeasurement] + N --> P + O --> P + + P --> Q[ProfilingTraceData] + Q --> R[Base64编码] + R --> S[数据上报] + + style C fill:#e8f5e8 + style D fill:#fff3cd + style E fill:#e8f5e8 + style F fill:#fff3cd +``` + +## 1. Profiling 架构设计 + +### 1.1 核心接口定义 + +```java +// 事务性能分析器接口 +public interface ITransactionProfiler { + boolean isRunning(); + void start(); + void bindTransaction(@NotNull ITransaction transaction); + + @Nullable ProfilingTraceData onTransactionFinish( + @NotNull ITransaction transaction, + @Nullable List performanceCollectionData, + @NotNull SentryOptions options + ); + + void close(); +} + +// 连续性能分析器接口 +public interface IContinuousProfiler { + void startProfiler(@NotNull ProfileLifecycle profileLifecycle, @Nullable TracesSampler tracesSampler); + void stopProfiler(@NotNull ProfileLifecycle profileLifecycle); + boolean isRunning(); + @NotNull SentryId getProfilerId(); + void close(); +} +``` + +### 1.2 ProfileLifecycle 枚举 + +```java +public enum ProfileLifecycle { + TRACE, // 跟随事务生命周期 + MANUAL // 手动控制 +} +``` + +## 2. AndroidProfiler - 核心性能分析器 + +### 2.1 核心数据结构 + +```java +public class AndroidProfiler { + // 性能分析开始数据 + public static class ProfileStartData { + public final long startNanos; // 开始时间戳(纳秒) + public final long startCpuMillis; // 开始CPU时间(毫秒) + public final @NotNull Date startTimestamp; // 开始时间 + } + + // 性能分析结束数据 + public static class ProfileEndData { + public final long endNanos; // 结束时间戳(纳秒) + public final long endCpuMillis; // 结束CPU时间(毫秒) + public final @NotNull File traceFile; // 跟踪文件 + public final @NotNull Map measurementsMap; // 性能指标 + public final boolean didTimeout; // 是否超时 + } +} +``` + +### 2.2 性能分析启动 + +```java +@SuppressLint("NewApi") +public @Nullable ProfileStartData start() { + try (final @NotNull ISentryLifecycleToken ignored = lock.acquire()) { + // 检查采样间隔 + if (intervalUs == 0) { + logger.log(SentryLevel.WARNING, "Disabling profiling because intervalUs is set to %d", intervalUs); + return null; + } + + if (isRunning) { + logger.log(SentryLevel.WARNING, "Profiling has already started..."); + return null; + } + + // 创建跟踪文件 + traceFile = new File(traceFilesDir, SentryUUID.generateSentryId() + ".trace"); + + // 清理之前的数据 + measurementsMap.clear(); + screenFrameRateMeasurements.clear(); + slowFrameRenderMeasurements.clear(); + frozenFrameRenderMeasurements.clear(); + + // 启动帧率监控 + frameMetricsCollectorId = frameMetricsCollector.startCollection( + (frameStartNanos, frameEndNanos, durationNanos, delayNanos, isSlow, isFrozen, refreshRate) -> { + final long frameTimestampRelativeNanos = frameStartNanos - profileStartNanos; + final Date timestamp = DateUtils.getDateTime(profileStartTimestamp.getTime() + frameTimestampRelativeNanos / 1_000_000L); + + // 收集慢帧数据 + if (isSlow) { + slowFrameRenderMeasurements.addLast( + new ProfileMeasurementValue(frameTimestampRelativeNanos, durationNanos, timestamp) + ); + } + + // 收集冻结帧数据 + if (isFrozen) { + frozenFrameRenderMeasurements.addLast( + new ProfileMeasurementValue(frameTimestampRelativeNanos, durationNanos, timestamp) + ); + } + + // 收集屏幕刷新率数据 + if (refreshRate != lastRefreshRate) { + lastRefreshRate = refreshRate; + screenFrameRateMeasurements.addLast( + new ProfileMeasurementValue(frameTimestampRelativeNanos, refreshRate, timestamp) + ); + } + } + ); + + // 设置超时机制(30秒) + if (timeoutExecutorService != null) { + scheduledFinish = timeoutExecutorService.schedule( + () -> endAndCollect(true, null), + PROFILING_TIMEOUT_MILLIS + ); + } + + // 记录开始时间 + profileStartNanos = SystemClock.elapsedRealtimeNanos(); + final @NotNull Date profileStartTimestamp = DateUtils.getCurrentDateTime(); + long profileStartCpuMillis = Process.getElapsedCpuTime(); + + try { + // 启动方法跟踪采样 + Debug.startMethodTracingSampling(traceFile.getPath(), BUFFER_SIZE_BYTES, intervalUs); + isRunning = true; + return new ProfileStartData(profileStartNanos, profileStartCpuMillis, profileStartTimestamp); + } catch (Throwable e) { + endAndCollect(false, null); + logger.log(SentryLevel.ERROR, "Unable to start a profile: ", e); + isRunning = false; + return null; + } + } +} +``` + +### 2.3 性能分析结束 + +```java +@SuppressLint("NewApi") +public @Nullable ProfileEndData endAndCollect( + final boolean isTimeout, + final @Nullable List performanceCollectionData) { + + try (final @NotNull ISentryLifecycleToken ignored = lock.acquire()) { + if (!isRunning) { + logger.log(SentryLevel.WARNING, "Profiler not running"); + return null; + } + + try { + // 停止方法跟踪 + Debug.stopMethodTracing(); + } catch (Throwable e) { + logger.log(SentryLevel.ERROR, "Error while stopping profiling: ", e); + } finally { + isRunning = false; + } + + // 停止帧率监控 + frameMetricsCollector.stopCollection(frameMetricsCollectorId); + + long transactionEndNanos = SystemClock.elapsedRealtimeNanos(); + long transactionEndCpuMillis = Process.getElapsedCpuTime(); + + if (traceFile == null) { + logger.log(SentryLevel.ERROR, "Trace file does not exists"); + return null; + } + + // 整理性能指标 + if (!slowFrameRenderMeasurements.isEmpty()) { + measurementsMap.put( + ProfileMeasurement.ID_SLOW_FRAME_RENDERS, + new ProfileMeasurement(ProfileMeasurement.UNIT_NANOSECONDS, slowFrameRenderMeasurements) + ); + } + + if (!frozenFrameRenderMeasurements.isEmpty()) { + measurementsMap.put( + ProfileMeasurement.ID_FROZEN_FRAME_RENDERS, + new ProfileMeasurement(ProfileMeasurement.UNIT_NANOSECONDS, frozenFrameRenderMeasurements) + ); + } + + if (!screenFrameRateMeasurements.isEmpty()) { + measurementsMap.put( + ProfileMeasurement.ID_SCREEN_FRAME_RATES, + new ProfileMeasurement(ProfileMeasurement.UNIT_HZ, screenFrameRateMeasurements) + ); + } + + // 处理性能收集数据 + putPerformanceCollectionDataInMeasurements(performanceCollectionData); + + // 取消超时任务 + if (scheduledFinish != null) { + scheduledFinish.cancel(true); + scheduledFinish = null; + } + + return new ProfileEndData( + transactionEndNanos, + transactionEndCpuMillis, + isTimeout, + traceFile, + measurementsMap + ); + } +} +``` + +### 2.4 性能数据处理 + +```java +private void putPerformanceCollectionDataInMeasurements( + final @Nullable List performanceCollectionData) { + + // 时间戳差异计算(System.currentTimeMillis() 转 SystemClock.elapsedRealtimeNanos()) + long timestampDiff = SystemClock.elapsedRealtimeNanos() - profileStartNanos + - TimeUnit.MILLISECONDS.toNanos(System.currentTimeMillis()); + + if (performanceCollectionData != null) { + final @NotNull ArrayDeque memoryUsageMeasurements = new ArrayDeque<>(); + final @NotNull ArrayDeque nativeMemoryUsageMeasurements = new ArrayDeque<>(); + final @NotNull ArrayDeque cpuUsageMeasurements = new ArrayDeque<>(); + + synchronized (performanceCollectionData) { + for (PerformanceCollectionData performanceData : performanceCollectionData) { + CpuCollectionData cpuData = performanceData.getCpuData(); + MemoryCollectionData memoryData = performanceData.getMemoryData(); + + // 处理CPU使用率数据 + if (cpuData != null) { + cpuUsageMeasurements.add( + new ProfileMeasurementValue( + cpuData.getTimestamp().nanoTimestamp() + timestampDiff, + cpuData.getCpuUsagePercentage(), + cpuData.getTimestamp() + ) + ); + } + + // 处理堆内存使用数据 + if (memoryData != null && memoryData.getUsedHeapMemory() > -1) { + memoryUsageMeasurements.add( + new ProfileMeasurementValue( + memoryData.getTimestamp().nanoTimestamp() + timestampDiff, + memoryData.getUsedHeapMemory(), + memoryData.getTimestamp() + ) + ); + } + + // 处理原生内存使用数据 + if (memoryData != null && memoryData.getUsedNativeMemory() > -1) { + nativeMemoryUsageMeasurements.add( + new ProfileMeasurementValue( + memoryData.getTimestamp().nanoTimestamp() + timestampDiff, + memoryData.getUsedNativeMemory(), + memoryData.getTimestamp() + ) + ); + } + } + } + + // 添加到性能指标映射 + if (!cpuUsageMeasurements.isEmpty()) { + measurementsMap.put( + ProfileMeasurement.ID_CPU_USAGE, + new ProfileMeasurement(ProfileMeasurement.UNIT_PERCENT, cpuUsageMeasurements) + ); + } + + if (!memoryUsageMeasurements.isEmpty()) { + measurementsMap.put( + ProfileMeasurement.ID_MEMORY_FOOTPRINT, + new ProfileMeasurement(ProfileMeasurement.UNIT_BYTES, memoryUsageMeasurements) + ); + } + + if (!nativeMemoryUsageMeasurements.isEmpty()) { + measurementsMap.put( + ProfileMeasurement.ID_MEMORY_NATIVE_FOOTPRINT, + new ProfileMeasurement(ProfileMeasurement.UNIT_BYTES, nativeMemoryUsageMeasurements) + ); + } + } +} +``` + +## 3. AndroidTransactionProfiler - 事务性能分析 + +### 3.1 事务绑定机制 + +```java +public class AndroidTransactionProfiler implements ITransactionProfiler { + private int transactionsCounter = 0; + private @Nullable ProfilingTransactionData currentProfilingTransactionData; + private @Nullable AndroidProfiler profiler; + + @Override + public void bindTransaction(final @NotNull ITransaction transaction) { + try (final @NotNull ISentryLifecycleToken ignored = lock.acquire()) { + // 如果性能分析器正在运行,但没有绑定事务数据,在此绑定 + if (transactionsCounter > 0 && currentProfilingTransactionData == null) { + currentProfilingTransactionData = new ProfilingTransactionData( + transaction, + profileStartNanos, + profileStartCpuMillis + ); + } + } + } + + @Override + public @Nullable ProfilingTraceData onTransactionFinish( + final @NotNull ITransaction transaction, + final @Nullable List performanceCollectionData, + final @NotNull SentryOptions options) { + + try (final @NotNull ISentryLifecycleToken ignored = lock.acquire()) { + return onTransactionFinish( + transaction.getName(), + transaction.getEventId().toString(), + transaction.getSpanContext().getTraceId().toString(), + false, + performanceCollectionData, + options + ); + } + } +} +``` + +### 3.2 事务完成处理 + +```java +private @Nullable ProfilingTraceData onTransactionFinish( + final @NotNull String transactionName, + final @NotNull String transactionId, + final @NotNull String traceId, + final boolean isTimeout, + final @Nullable List performanceCollectionData, + final @NotNull SentryOptions options) { + + try (final @NotNull ISentryLifecycleToken ignored = lock.acquire()) { + if (profiler == null) { + return null; + } + + // 检查当前事务是否在性能分析中 + if (currentProfilingTransactionData == null + || !currentProfilingTransactionData.getId().equals(transactionId)) { + logger.log(SentryLevel.INFO, + "Transaction %s (%s) finished, but was not currently being profiled. Skipping", + transactionName, traceId); + return null; + } + + if (transactionsCounter > 0) { + transactionsCounter--; + } + + logger.log(SentryLevel.DEBUG, "Transaction %s (%s) finished.", transactionName, traceId); + + // 如果还有其他事务在运行,只更新当前事务数据 + if (transactionsCounter != 0) { + if (currentProfilingTransactionData != null) { + currentProfilingTransactionData.notifyFinish( + SystemClock.elapsedRealtimeNanos(), + profileStartNanos, + Process.getElapsedCpuTime(), + profileStartCpuMillis + ); + } + return null; + } + + // 所有事务都完成,结束性能分析 + final AndroidProfiler.ProfileEndData endData = profiler.endAndCollect(isTimeout, performanceCollectionData); + if (endData == null) { + logger.log(SentryLevel.INFO, "Profiler returned null on end."); + return null; + } + + // 计算事务持续时间 + final long transactionDurationNanos = endData.endNanos - profileStartNanos; + + // 创建事务列表 + final List transactionList = new ArrayList<>(); + if (currentProfilingTransactionData != null) { + currentProfilingTransactionData.notifyFinish( + endData.endNanos, profileStartNanos, endData.endCpuMillis, profileStartCpuMillis); + transactionList.add(currentProfilingTransactionData); + } + + // 获取设备信息 + final String[] abis = Build.SUPPORTED_ABIS; + String totalMem = "0"; + if (context != null) { + totalMem = String.valueOf(getTotalMem(context)); + } + + // 创建性能跟踪数据 + return new ProfilingTraceData( + endData.traceFile, + profileStartTimestamp, + transactionList, + transactionName, + transactionId, + traceId, + Long.toString(transactionDurationNanos), + buildInfoProvider.getSdkInfoVersion(), + abis != null && abis.length > 0 ? abis[0] : "", + () -> CpuInfoUtils.getInstance().readMaxFrequencies(), + buildInfoProvider.getManufacturer(), + buildInfoProvider.getModel(), + buildInfoProvider.getVersionRelease(), + buildInfoProvider.isEmulator(), + totalMem, + options.getProguardUuid(), + options.getRelease(), + options.getEnvironment(), + (endData.didTimeout || isTimeout) + ? ProfilingTraceData.TRUNCATION_REASON_TIMEOUT + : ProfilingTraceData.TRUNCATION_REASON_NORMAL, + endData.measurementsMap + ); + } +} +``` + +## 4. AndroidContinuousProfiler - 连续性能分析 + +### 4.1 连续性能分析启动 + +```java +public class AndroidContinuousProfiler implements IContinuousProfiler, RateLimiter.IRateLimitObserver { + private static final long MAX_CHUNK_DURATION_MILLIS = 60000; // 60秒一个块 + + @Override + public void startProfiler( + final @NotNull ProfileLifecycle profileLifecycle, + final @Nullable TracesSampler tracesSampler) { + + try (final @NotNull ISentryLifecycleToken ignored = lock.acquire()) { + switch (profileLifecycle) { + case TRACE: + rootSpanCounter++; + // 如果已经在运行,不重复启动 + if (isRunning) { + return; + } + break; + case MANUAL: + // 手动模式直接启动 + break; + } + + // 采样决策 + if (tracesSampler != null) { + final TracesSamplingDecision samplingDecision = tracesSampler.sample(null); + shouldSample = samplingDecision != null && samplingDecision.getSampled(); + isSampled = shouldSample; + } + + if (!shouldSample) { + logger.log(SentryLevel.DEBUG, "Profiler is not sampled, not starting."); + return; + } + + start(); + } + } + + private void start() { + // 检查API版本支持 + if (buildInfoProvider.getSdkInfoVersion() < Build.VERSION_CODES.LOLLIPOP_MR1) return; + + init(); + if (profiler == null) { + return; + } + + // 检查速率限制 + if (scopes != null) { + final @Nullable RateLimiter rateLimiter = scopes.getRateLimiter(); + if (rateLimiter != null && + (rateLimiter.isActiveForCategory(All) || + rateLimiter.isActiveForCategory(DataCategory.ProfileChunkUi))) { + logger.log(SentryLevel.WARNING, "SDK is rate limited. Stopping profiler."); + stop(false); + return; + } + + // 检查网络连接状态 + if (scopes.getOptions().getConnectionStatusProvider().getConnectionStatus() == DISCONNECTED) { + logger.log(SentryLevel.WARNING, "Device is offline. Stopping profiler."); + stop(false); + return; + } + } + + // 启动性能分析 + final AndroidProfiler.ProfileStartData startData = profiler.start(); + if (startData == null) { + return; + } + + isRunning = true; + + // 生成ID + if (profilerId == SentryId.EMPTY_ID) { + profilerId = new SentryId(); + } + if (chunkId == SentryId.EMPTY_ID) { + chunkId = new SentryId(); + } + + // 启动性能收集器 + if (performanceCollector != null) { + performanceCollector.start(chunkId.toString()); + } + + // 设置定时停止(60秒后) + try { + stopFuture = executorService.schedule(() -> stop(true), MAX_CHUNK_DURATION_MILLIS); + } catch (RejectedExecutionException e) { + logger.log(SentryLevel.ERROR, + "Failed to schedule profiling chunk finish. Did you call Sentry.close()?", e); + shouldStop = true; + } + } +} +``` + +### 4.2 性能块处理 + +```java +private void stop(final boolean isTimeout) { + try (final @NotNull ISentryLifecycleToken ignored = lock.acquire()) { + if (!isRunning) { + return; + } + + if (profiler == null) { + return; + } + + // 停止性能收集器 + List performanceCollectionData = null; + if (performanceCollector != null) { + performanceCollectionData = performanceCollector.stop(chunkId.toString()); + } + + // 结束性能分析 + final AndroidProfiler.ProfileEndData endData = profiler.endAndCollect(isTimeout, performanceCollectionData); + if (endData == null) { + logger.log(SentryLevel.INFO, "Profiler returned null on end."); + return; + } + + isRunning = false; + + // 取消定时任务 + if (stopFuture != null) { + stopFuture.cancel(true); + stopFuture = null; + } + + // 创建性能块 + final ProfileChunk.Builder builder = new ProfileChunk.Builder(); + builder.setProfilerId(profilerId); + builder.setChunkId(chunkId); + builder.setTimestamp(startProfileChunkTimestamp); + builder.setTraceFile(endData.traceFile); + builder.setMeasurements(endData.measurementsMap); + + // 添加到待发送列表 + try (final @NotNull ISentryLifecycleToken ignored2 = payloadLock.acquire()) { + payloadBuilders.add(builder); + } + + // 发送性能块 + executorService.submit(() -> { + try (final @NotNull ISentryLifecycleToken ignored3 = payloadLock.acquire()) { + for (ProfileChunk.Builder payloadBuilder : payloadBuilders) { + final ProfileChunk profileChunk = payloadBuilder.build(); + if (scopes != null) { + scopes.captureProfileChunk(profileChunk); + } + } + payloadBuilders.clear(); + } + }); + + // 重置状态 + chunkId = SentryId.EMPTY_ID; + + // 如果需要继续运行,重新启动 + if (!shouldStop && !isClosed.get()) { + start(); + } + } +} +``` + +## 5. ProfilingTraceData - 性能跟踪数据 + +### 5.1 数据结构 + +```java +public final class ProfilingTraceData implements JsonUnknown, JsonSerializable { + // 截断原因常量 + public static final String TRUNCATION_REASON_NORMAL = "normal"; + public static final String TRUNCATION_REASON_TIMEOUT = "timeout"; + public static final String TRUNCATION_REASON_BACKGROUNDED = "backgrounded"; + + // 核心数据 + private final @NotNull File traceFile; // 跟踪文件 + private final @NotNull Date profileStartTimestamp; // 性能分析开始时间 + private final @NotNull List transactions; // 事务列表 + private final @NotNull String transactionName; // 事务名称 + private final @NotNull String transactionId; // 事务ID + private final @NotNull String traceId; // 跟踪ID + private final @NotNull String durationNanos; // 持续时间(纳秒) + private final @NotNull String truncationReason; // 截断原因 + private final @NotNull Map measurementsMap; // 性能指标 + + // 设备信息 + private int androidApiLevel; // Android API级别 + private @NotNull String deviceLocale; // 设备语言 + private @NotNull String deviceManufacturer; // 设备制造商 + private @NotNull String deviceModel; // 设备型号 + private @NotNull String deviceOsBuildNumber; // OS构建号 + private @NotNull String deviceOsName; // OS名称 + private @NotNull String deviceOsVersion; // OS版本 + private boolean deviceIsEmulator; // 是否模拟器 + private @NotNull String cpuArchitecture; // CPU架构 + private @NotNull List deviceCpuFrequencies; // CPU频率 + private @NotNull String devicePhysicalMemoryBytes; // 物理内存 + + // 应用信息 + private @NotNull String platform; // 平台 + private @NotNull String buildId; // 构建ID + private @Nullable String release; // 版本 + private @Nullable String environment; // 环境 + private @Nullable String sampledProfile; // Base64编码的性能数据 +} +``` + +### 5.2 性能数据编码 + +```java +// 在 SentryEnvelopeItem 中处理性能数据编码 +public static @NotNull SentryEnvelopeItem fromProfilingTrace( + final @NotNull ProfilingTraceData profilingTraceData, + final long maxTraceFileSize, + final @NotNull ISerializer serializer) throws SentryEnvelopeException { + + final CachedItem cachedItem = new CachedItem(() -> { + if (!traceFile.exists()) { + throw new SentryEnvelopeException( + String.format("Dropping profiling trace data, because the file '%s' doesn't exists", + traceFile.getName())); + } + + // 读取跟踪文件并Base64编码 + final byte[] traceFileBytes = readBytesFromFile(traceFile.getPath(), maxTraceFileSize); + final @NotNull String base64Trace = Base64.encodeToString(traceFileBytes, NO_WRAP | NO_PADDING); + + if (base64Trace.isEmpty()) { + throw new SentryEnvelopeException("Profiling trace file is empty"); + } + + profilingTraceData.setSampledProfile(base64Trace); + profilingTraceData.readDeviceCpuFrequencies(); + + try (final ByteArrayOutputStream stream = new ByteArrayOutputStream(); + final Writer writer = new BufferedWriter(new OutputStreamWriter(stream, UTF_8))) { + + serializer.serialize(profilingTraceData, writer); + return stream.toByteArray(); + + } catch (IOException e) { + throw new SentryEnvelopeException( + String.format("Failed to serialize profiling trace data\n%s", e.getMessage())); + } finally { + // 删除跟踪文件 + traceFile.delete(); + } + }); + + SentryEnvelopeItemHeader itemHeader = new SentryEnvelopeItemHeader( + SentryItemType.Profile, + () -> cachedItem.getBytes().length, + "application-json", + traceFile.getName() + ); + + return new SentryEnvelopeItem(itemHeader, cachedItem); +} +``` + +## 6. 性能指标收集 + +### 6.1 ProfileMeasurement 结构 + +```java +public final class ProfileMeasurement implements JsonSerializable { + // 指标ID常量 + public static final String ID_CPU_USAGE = "cpu_usage"; + public static final String ID_MEMORY_FOOTPRINT = "memory_footprint"; + public static final String ID_MEMORY_NATIVE_FOOTPRINT = "memory_native_footprint"; + public static final String ID_SLOW_FRAME_RENDERS = "slow_frame_renders"; + public static final String ID_FROZEN_FRAME_RENDERS = "frozen_frame_renders"; + public static final String ID_SCREEN_FRAME_RATES = "screen_frame_rates"; + + // 单位常量 + public static final String UNIT_NANOSECONDS = "nanosecond"; + public static final String UNIT_BYTES = "byte"; + public static final String UNIT_PERCENT = "percent"; + public static final String UNIT_HZ = "hz"; + + private final @NotNull String unit; // 单位 + private final @NotNull List values; // 值列表 +} + +public final class ProfileMeasurementValue implements JsonSerializable { + private final long relativeStartNs; // 相对开始时间(纳秒) + private final double value; // 值 + private final @NotNull Date timestamp; // 时间戳 +} +``` + +### 6.2 性能数据收集器集成 + +```java +// 在 SentryTracer 中集成性能收集器 +public final class SentryTracer implements ITransaction { + private final @Nullable CompositePerformanceCollector compositePerformanceCollector; + + // 事务开始时启动性能收集 + SentryTracer(final @NotNull TransactionContext context, ...) { + // ... + + if (compositePerformanceCollector != null) { + compositePerformanceCollector.start(this); + } + } + + // 事务结束时停止性能收集 + private void finishInternal(final @Nullable SpanStatus finishStatus, final @Nullable SentryDate finishTimestamp) { + final @NotNull AtomicReference> performanceCollectionData = new AtomicReference<>(); + + this.root.setSpanFinishedCallback(span -> { + // ... + + if (compositePerformanceCollector != null) { + performanceCollectionData.set(compositePerformanceCollector.stop(this)); + } + }); + + root.finish(finishStatus.spanStatus, finishTimestamp); + + // 生成性能跟踪数据 + ProfilingTraceData profilingTraceData = null; + if (Boolean.TRUE.equals(isSampled()) && Boolean.TRUE.equals(isProfileSampled())) { + profilingTraceData = scopes.getOptions() + .getTransactionProfiler() + .onTransactionFinish(this, performanceCollectionData.get(), scopes.getOptions()); + } + } +} +``` + +## 7. 配置和最佳实践 + +### 7.1 关键配置选项 + +```java +// 启用性能分析 +options.setProfilesSampleRate(0.1); // 10% 采样率 + +// 连续性能分析 +options.setContinuousProfilingEnabled(true); +options.setContinuousProfilingAutoStart(true); + +// 性能分析采样间隔(微秒) +options.setProfilingTracesIntervalMillis(10); // 10ms间隔 + +// 性能分析超时时间 +options.setProfilingTimeoutMillis(30000); // 30秒超时 + +// 跟踪文件最大大小 +options.setMaxTraceFileSize(5 * 1024 * 1024); // 5MB + +// 启用性能收集器 +options.setEnablePerformanceV2(true); +``` + +### 7.2 性能优化建议 + +#### ✅ 推荐做法 + +1. **合理设置采样率** + ```java + // 生产环境:低采样率 + options.setProfilesSampleRate(0.01); // 1% + + // 开发环境:高采样率 + options.setProfilesSampleRate(1.0); // 100% + ``` + +2. **选择合适的采样间隔** + ```java + // 高精度分析:短间隔 + options.setProfilingTracesIntervalMillis(5); // 5ms + + // 常规分析:中等间隔 + options.setProfilingTracesIntervalMillis(10); // 10ms + + // 低开销分析:长间隔 + options.setProfilingTracesIntervalMillis(20); // 20ms + ``` + +3. **限制跟踪文件大小** + ```java + // 移动设备:较小文件 + options.setMaxTraceFileSize(3 * 1024 * 1024); // 3MB + + // 高端设备:较大文件 + options.setMaxTraceFileSize(8 * 1024 * 1024); // 8MB + ``` + +#### ❌ 避免做法 + +- **过高的采样率**:会显著影响应用性能 +- **过短的采样间隔**:会产生巨大的跟踪文件 +- **在低端设备上启用高精度分析**:可能导致ANR + +### 7.3 性能影响评估 + +#### CPU 开销估算 + +```java +// 估算公式 +public class ProfilingOverhead { + public static double estimateCpuOverhead(int intervalMs, double baselineOverhead) { + // 基础开销 + 采样频率相关开销 + double samplingOverhead = 1000.0 / intervalMs * 0.001; // 每次采样0.1%开销 + return baselineOverhead + samplingOverhead; + } + + // 示例:10ms间隔,基础开销1% + // 总开销约 1% + (1000/10)*0.001 = 1% + 0.1% = 1.1% +} +``` + +#### 存储开销估算 + +```java +public class ProfilingStorage { + public static long estimateTraceFileSize(int durationSeconds, int intervalMs) { + // 每次采样约100字节(方法调用信息) + int samplesPerSecond = 1000 / intervalMs; + int totalSamples = durationSeconds * samplesPerSecond; + return totalSamples * 100L; // 字节 + } + + // 示例:30秒事务,10ms间隔 + // 约 30 * (1000/10) * 100 = 300KB +} +``` + +## 8. 故障排查 + +### 8.1 常见问题 + +**Q: 性能分析没有数据?** +A: 检查采样率设置,确保 `profilesSampleRate` > 0,且事务被采样 + +**Q: 跟踪文件过大?** +A: 增加采样间隔或减少分析持续时间,检查 `maxTraceFileSize` 设置 + +**Q: 应用性能下降明显?** +A: 降低采样率,增加采样间隔,或在低端设备上禁用性能分析 + +**Q: 连续性能分析不工作?** +A: 检查 `continuousProfilingEnabled` 设置,确保有活跃的事务或手动启动 + +### 8.2 调试技巧 + +```java +// 启用性能分析调试日志 +options.setDebug(true); +options.setLogger(new SystemOutLogger()); + +// 监控性能分析状态 +ITransactionProfiler transactionProfiler = options.getTransactionProfiler(); +System.out.println("Transaction profiler running: " + transactionProfiler.isRunning()); + +IContinuousProfiler continuousProfiler = options.getContinuousProfiler(); +System.out.println("Continuous profiler running: " + continuousProfiler.isRunning()); +System.out.println("Profiler ID: " + continuousProfiler.getProfilerId()); + +// 检查跟踪文件 +File tracesDir = new File(options.getCacheDirPath(), "profiling_traces"); +System.out.println("Traces dir exists: " + tracesDir.exists()); +System.out.println("Trace files count: " + (tracesDir.listFiles() != null ? tracesDir.listFiles().length : 0)); + +// 手动触发性能分析 +Sentry.startTransaction("manual-profiling", "test").finish(); +``` + +## 总结 + +Sentry 的 Profiling 功能通过精密的方法调用跟踪和全面的性能指标收集,为开发者提供了深度的性能分析能力: + +### 🎯 **核心优势** + +1. **双重分析模式**: 事务性能分析和连续性能分析满足不同需求 +2. **方法级跟踪**: 基于 Android Debug API 的精确方法调用跟踪 +3. **全面性能指标**: CPU、内存、帧率等多维度性能数据 +4. **智能采样**: 灵活的采样策略平衡数据价值和性能影响 +5. **设备信息集成**: 完整的设备和环境信息关联 + +### 🔍 **技术特点** + +- **低开销设计**: 可配置的采样间隔和文件大小限制 +- **数据压缩**: Base64编码和文件压缩减少传输开销 +- **异常处理**: 完善的超时和错误处理机制 +- **内存管理**: 自动清理跟踪文件和缓存数据 + +### 📊 **应用价值** + +通过这套 Profiling 系统,开发者可以: +- 识别性能瓶颈方法和调用路径 +- 分析CPU和内存使用模式 +- 监控UI渲染性能 +- 优化关键业务流程性能 +- 对比不同版本的性能表现 + +这套机制在保证应用性能的前提下,为深度性能优化提供了强有力的数据支撑和分析工具。 \ No newline at end of file diff --git a/cursor/sentry-replay-analysis.md b/cursor/sentry-replay-analysis.md new file mode 100644 index 0000000000..381d0df7cd --- /dev/null +++ b/cursor/sentry-replay-analysis.md @@ -0,0 +1,775 @@ +# Sentry Replay 功能深度分析 + +本文档详细分析了 Sentry Android SDK 的 Session Replay 功能,包括录制策略、缓存机制、视频编码、事件捕获等核心实现。 + +## 🎯 Replay 功能概览 + +Sentry Replay 通过屏幕录制和事件捕获,为开发者提供用户操作的完整回放: + +```mermaid +graph TD + A[应用启动] --> B{采样决策} + B --> C[全会话录制] + B --> D[错误缓冲录制] + + C --> E[SessionCaptureStrategy] + D --> F[BufferCaptureStrategy] + + E --> G[实时屏幕截图] + F --> H[缓冲屏幕截图] + + G --> I[ReplayCache] + H --> I + + I --> J[帧存储] + J --> K[视频编码] + K --> L[MP4生成] + + L --> M[ReplayEvent创建] + M --> N[数据上报] + + F --> O{错误发生} + O --> P[策略转换] + P --> E + + style C fill:#e8f5e8 + style D fill:#fff3cd + style E fill:#e8f5e8 + style F fill:#fff3cd +``` + +## 1. 录制策略架构 + +### 1.1 CaptureStrategy 接口设计 + +```kotlin +internal interface CaptureStrategy { + val currentReplayId: SentryId + val currentSegment: Int + val replayCacheDir: File? + val replayType: ReplayType + + // 生命周期管理 + fun start(recorderConfig: ScreenshotRecorderConfig, segmentId: Int = 0, replayId: SentryId = SentryId(), replayType: ReplayType? = null) + fun resume() + fun pause() + fun stop() + + // 录制控制 + fun captureReplay(isTerminating: Boolean, onSegmentSent: (Date) -> Unit) + fun onScreenshotRecorded(bitmap: Bitmap?, store: ReplayCache.(frameTimestamp: Long) -> Unit) + fun onConfigurationChanged(recorderConfig: ScreenshotRecorderConfig) + fun onTouchEvent(event: MotionEvent) + + // 策略转换 + fun convert(): CaptureStrategy +} +``` + +### 1.2 ReplayType 枚举 + +```java +public enum ReplayType implements JsonSerializable { + SESSION, // 全会话录制 + BUFFER; // 错误缓冲录制 + + @Override + public void serialize(final @NotNull ObjectWriter writer, final @NotNull ILogger logger) throws IOException { + writer.value(name().toLowerCase(Locale.ROOT)); + } +} +``` + +## 2. SessionCaptureStrategy - 全会话录制 + +### 2.1 核心特性 + +```kotlin +internal class SessionCaptureStrategy( + private val options: SentryOptions, + private val scopes: IScopes?, + private val dateProvider: ICurrentDateProvider, + executor: ScheduledExecutorService +) : BaseCaptureStrategy(options, scopes, dateProvider, executor) { + + override fun start(recorderConfig: ScreenshotRecorderConfig, segmentId: Int, replayId: SentryId, replayType: ReplayType?) { + super.start(recorderConfig, segmentId, replayId, replayType) + + // 全会话录制时立即设置 replayId 到 scope + scopes?.configureScope { + it.replayId = currentReplayId + screenAtStart = it.screen?.substringAfterLast('.') + } + } +} +``` + +### 2.2 实时段创建 + +```kotlin +override fun onScreenshotRecorded(bitmap: Bitmap?, store: ReplayCache.(frameTimestamp: Long) -> Unit) { + val frameTimestamp = dateProvider.currentTimeMillis + val height = recorderConfig.recordingHeight + val width = recorderConfig.recordingWidth + + replayExecutor.submitSafely(options, "$TAG.add_frame") { + cache?.store(frameTimestamp) + + val currentSegmentTimestamp = segmentTimestamp ?: run { + options.logger.log(DEBUG, "Segment timestamp is not set, not recording frame") + return@submitSafely + } + + if (isTerminating.get()) { + options.logger.log(DEBUG, "Not capturing segment, because the app is terminating") + return@submitSafely + } + + val now = dateProvider.currentTimeMillis + + // 检查是否需要创建新段(默认10秒一段) + if ((now - currentSegmentTimestamp.time >= options.sessionReplay.sessionSegmentDuration)) { + val segment = createSegmentInternal( + options.sessionReplay.sessionSegmentDuration, + currentSegmentTimestamp, + currentReplayId, + currentSegment, + height, + width + ) + + if (segment is ReplaySegment.Created) { + segment.capture(scopes) // 立即发送段 + currentSegment++ + segmentTimestamp = segment.replay.timestamp // 设置下一段时间戳 + } + } + + // 检查会话总时长限制(默认1小时) + if ((now - replayStartTimestamp.get() >= options.sessionReplay.sessionDuration)) { + options.replayController.stop() + options.logger.log(INFO, "Session replay deadline exceeded (1h), stopping recording") + } + } +} +``` + +### 2.3 配置变更处理 + +```kotlin +override fun onConfigurationChanged(recorderConfig: ScreenshotRecorderConfig) { + createCurrentSegment("onConfigurationChanged") { segment -> + if (segment is ReplaySegment.Created) { + segment.capture(scopes) + currentSegment++ + segmentTimestamp = segment.replay.timestamp // 避免段间隙 + } + } + + // 在提交最后一段后刷新录制配置 + super.onConfigurationChanged(recorderConfig) +} +``` + +## 3. BufferCaptureStrategy - 错误缓冲录制 + +### 3.1 缓冲机制 + +```kotlin +internal class BufferCaptureStrategy( + private val options: SentryOptions, + private val scopes: IScopes?, + private val dateProvider: ICurrentDateProvider, + private val random: Random, + executor: ScheduledExecutorService +) : BaseCaptureStrategy(options, scopes, dateProvider, executor) { + + // 缓冲的段列表,不立即发送 + private val bufferedSegments = mutableListOf() + + override fun pause() { + createCurrentSegment("pause") { segment -> + if (segment is ReplaySegment.Created) { + bufferedSegments += segment // 只缓冲,不发送 + currentSegment++ + } + } + super.pause() + } +} +``` + +### 3.2 错误触发录制 + +```kotlin +override fun captureReplay(isTerminating: Boolean, onSegmentSent: (Date) -> Unit) { + // 错误采样检查 + val sampled = random.sample(options.sessionReplay.onErrorSampleRate) + if (!sampled) { + options.logger.log(INFO, "Replay wasn't sampled by onErrorSampleRate, not capturing for event") + return + } + + // 立即设置 replayId 到 scope,确保错误事件能关联到 replay + scopes?.configureScope { + it.replayId = currentReplayId + } + + if (isTerminating) { + this.isTerminating.set(true) + options.logger.log(DEBUG, "Not capturing replay for crashed event, will be captured on next launch") + return + } + + createCurrentSegment("capture_replay") { segment -> + // 发送所有缓冲的段 + bufferedSegments.capture() + + if (segment is ReplaySegment.Created) { + segment.capture(scopes) + onSegmentSent(segment.replay.timestamp) + } + } +} +``` + +### 3.3 缓冲轮转机制 + +```kotlin +override fun onScreenshotRecorded(bitmap: Bitmap?, store: ReplayCache.(frameTimestamp: Long) -> Unit) { + val frameTimestamp = dateProvider.currentTimeMillis + + replayExecutor.submitSafely(options, "$TAG.add_frame") { + cache?.store(frameTimestamp) + + val now = dateProvider.currentTimeMillis + val bufferLimit = now - options.sessionReplay.errorReplayDuration // 默认30秒缓冲 + + // 轮转缓存,只保留最近30秒的帧 + screenAtStart = cache?.rotate(bufferLimit) + bufferedSegments.rotate(bufferLimit) + } +} +``` + +### 3.4 策略转换 + +```kotlin +override fun convert(): CaptureStrategy { + if (isTerminating.get()) { + options.logger.log(DEBUG, "Not converting to session mode, because the process is about to terminate") + return this + } + + // 转换为全会话录制策略 + val captureStrategy = SessionCaptureStrategy(options, scopes, dateProvider, replayExecutor) + captureStrategy.start(recorderConfig, segmentId = currentSegment, replayId = currentReplayId, replayType = BUFFER) + return captureStrategy +} +``` + +## 4. ReplayCache - 缓存和视频编码 + +### 4.1 帧存储结构 + +```kotlin +class ReplayCache( + private val options: SentryOptions, + private val replayId: SentryId +) : Closeable { + + // 帧列表,按时间戳排序 + internal val frames = mutableListOf() + + // 缓存目录 + val replayCacheDir: File? = makeReplayCacheDir(options, replayId) + + // 线程安全锁 + private val lock = AutoClosableReentrantLock() + + data class ReplayFrame( + val screenshot: File, // 截图文件 + val timestamp: Long, // 时间戳 + val screen: String? = null // 屏幕名称 + ) +} +``` + +### 4.2 帧添加和轮转 + +```kotlin +fun addFrame(screenshot: File, timestamp: Long, screen: String? = null) { + lock.acquire().use { + frames.add(ReplayFrame(screenshot, timestamp, screen)) + } +} + +fun rotate(pivotTime: Long): String? { + lock.acquire().use { + var screenAtStart: String? = null + + // 移除超出时间窗口的帧 + val iterator = frames.iterator() + while (iterator.hasNext()) { + val frame = iterator.next() + if (frame.timestamp < pivotTime) { + if (screenAtStart == null) { + screenAtStart = frame.screen + } + + // 删除文件 + if (!frame.screenshot.delete()) { + options.logger.log(ERROR, "Failed to delete replay frame: ${frame.screenshot.absolutePath}") + } + iterator.remove() + } else { + break // 帧按时间排序,后续帧都在窗口内 + } + } + + return screenAtStart + } +} +``` + +### 4.3 视频编码 + +```kotlin +fun createVideoOf( + duration: Long, + from: Date, + to: Date, + frameRate: Int, + bitRate: Int, + height: Int, + width: Int +): GeneratedVideo? { + + lock.acquire().use { + if (frames.isEmpty()) { + options.logger.log(INFO, "No frames to encode for replay: $replayId") + return null + } + + // 过滤时间范围内的帧 + val segmentFrames = frames.filter { frame -> + frame.timestamp >= from.time && frame.timestamp <= to.time + } + + if (segmentFrames.isEmpty()) { + options.logger.log(INFO, "No frames found for the specified time range") + return null + } + + val videoFile = File(replayCacheDir, "${from.time}_$duration.mp4") + + try { + // 使用 MediaCodec 编码视频 + val encoder = SimpleVideoEncoder( + options, + MuxerConfig( + file = videoFile, + width = width, + height = height, + bitRate = bitRate, + frameRate = frameRate, + iFrameInterval = 1 // 每秒一个关键帧 + ) + ) + + encoder.start() + + // 编码每一帧 + segmentFrames.forEachIndexed { index, frame -> + val bitmap = BitmapFactory.decodeFile(frame.screenshot.absolutePath) + if (bitmap != null) { + encoder.encodeFrame(bitmap, index * (1000000L / frameRate)) // 微秒时间戳 + bitmap.recycle() + } else { + options.logger.log(WARNING, "Failed to decode frame: ${frame.screenshot.absolutePath}") + } + } + + encoder.stop() + + return GeneratedVideo( + video = videoFile, + frameCount = segmentFrames.size, + duration = duration + ) + + } catch (e: Exception) { + options.logger.log(ERROR, e, "Failed to encode replay video") + return null + } + } +} +``` + +## 5. ReplayIntegration - 主控制器 + +### 5.1 生命周期状态机 + +```kotlin +class ReplayIntegration( + private val context: Context, + private val dateProvider: ICurrentDateProvider = CurrentDateProvider.getInstance() +) : Integration, ReplayController { + + // 状态枚举 + enum class State { NONE, STARTED, PAUSED, STOPPED } + + private val lifecycle = StateMachine(State.NONE) + private var captureStrategy: CaptureStrategy? = null + private var recorder: ScreenshotRecorder? = null + + override fun start() { + lifecycleLock.acquire().use { + if (!isEnabled.get() || !lifecycle.isAllowed(STARTED)) { + return + } + + // 采样决策 + val isFullSession = random.sample(options.sessionReplay.sessionSampleRate) + if (!isFullSession && !options.sessionReplay.isSessionReplayForErrorsEnabled) { + options.logger.log(INFO, "Session replay is not started, full session was not sampled and onErrorSampleRate is not specified") + return + } + + // 创建录制配置 + val recorderConfig = recorderConfigProvider?.invoke(false) + ?: ScreenshotRecorderConfig.from(context, options.sessionReplay) + + // 选择录制策略 + captureStrategy = if (isFullSession) { + SessionCaptureStrategy(options, scopes, dateProvider, replayExecutor, replayCacheProvider) + } else { + BufferCaptureStrategy(options, scopes, dateProvider, random, replayExecutor, replayCacheProvider) + } + + // 启动录制 + captureStrategy?.start(recorderConfig) + recorder?.start(recorderConfig) + registerRootViewListeners() + lifecycle.currentState = STARTED + } + } +} +``` + +### 5.2 错误触发机制 + +```kotlin +override fun captureReplay(isTerminating: Boolean) { + lifecycleLock.acquire().use { + if (!isEnabled.get()) { + return + } + + when (lifecycle.currentState) { + STARTED -> { + // 已在录制中,触发缓冲策略的错误录制 + captureStrategy?.captureReplay(isTerminating) { segmentTimestamp -> + // 转换为全会话录制策略 + captureStrategy = captureStrategy?.convert() + } + } + PAUSED, STOPPED -> { + // 尝试从上次会话恢复录制 + finalizePreviousReplay() + } + else -> { + options.logger.log(DEBUG, "Replay is not enabled, not capturing for event") + } + } + } +} +``` + +### 5.3 前一会话恢复 + +```kotlin +private fun finalizePreviousReplay() { + replayExecutor.submitSafely(options, "ReplayIntegration.finalizePreviousReplay") { + val lastSegmentData = ReplayCache.fromDisk(options, replayCacheProvider) + + if (lastSegmentData != null) { + options.logger.log(DEBUG, "Finalizing previous replay segment") + + val segment = createSegment( + lastSegmentData.cache, + lastSegmentData.recorderConfig, + lastSegmentData.duration, + lastSegmentData.timestamp, + lastSegmentData.cache.replayId, + lastSegmentData.id, + lastSegmentData.replayType, + lastSegmentData.screenAtStart, + emptyList(), // 面包屑 + lastSegmentData.events + ) + + if (segment is ReplaySegment.Created) { + segment.capture(scopes) + } + + // 清理缓存 + FileUtils.deleteRecursively(lastSegmentData.cache.replayCacheDir) + } + } +} +``` + +## 6. 屏幕截图录制 + +### 6.1 ScreenshotRecorderConfig + +```kotlin +data class ScreenshotRecorderConfig( + val recordingWidth: Int, // 录制宽度 + val recordingHeight: Int, // 录制高度 + val scaleFactorX: Float, // X轴缩放因子 + val scaleFactorY: Float, // Y轴缩放因子 + val frameRate: Int, // 帧率 (默认1fps) + val bitRate: Int // 比特率 +) { + companion object { + fun from(context: Context, options: SentryReplayOptions): ScreenshotRecorderConfig { + val displayMetrics = context.resources.displayMetrics + val density = displayMetrics.density + + // 计算录制分辨率(考虑质量设置) + val quality = options.quality + val targetWidth = (displayMetrics.widthPixels / density * quality.sizeScale).toInt() + val targetHeight = (displayMetrics.heightPixels / density * quality.sizeScale).toInt() + + return ScreenshotRecorderConfig( + recordingWidth = targetWidth, + recordingHeight = targetHeight, + scaleFactorX = targetWidth.toFloat() / displayMetrics.widthPixels, + scaleFactorY = targetHeight.toFloat() / displayMetrics.heightPixels, + frameRate = 1, // 固定1fps以减少存储和带宽 + bitRate = quality.bitRate + ) + } + } +} +``` + +### 6.2 质量配置 + +```kotlin +enum class SentryReplayOptions.Quality( + val sizeScale: Float, // 分辨率缩放 + val bitRate: Int // 比特率 +) { + LOW(0.8f, 75_000), // 低质量:80%分辨率,75kbps + MEDIUM(1.0f, 100_000), // 中等质量:100%分辨率,100kbps + HIGH(1.0f, 150_000); // 高质量:100%分辨率,150kbps +} +``` + +## 7. 事件和手势捕获 + +### 7.1 触摸事件捕获 + +```kotlin +override fun onTouchEvent(event: MotionEvent) { + if (!isRecording) return + + try { + // 转换为 RRWeb 格式的事件 + val rrwebEvent = ReplayGestureConverter.convert( + event, + recorderConfig.scaleFactorX, + recorderConfig.scaleFactorY + ) + + if (rrwebEvent != null) { + currentEvents.add(rrwebEvent) + + // 在缓冲模式下轮转事件 + if (this is BufferCaptureStrategy) { + val bufferLimit = dateProvider.currentTimeMillis - options.sessionReplay.errorReplayDuration + rotateEvents(currentEvents, bufferLimit) + } + } + } catch (e: Exception) { + options.logger.log(ERROR, e, "Failed to capture touch event") + } +} +``` + +### 7.2 面包屑集成 + +```kotlin +// 在段创建时收集相关面包屑 +private fun collectBreadcrumbs(from: Date, to: Date): List { + return scopes?.configureScope { scope -> + scope.breadcrumbs.filter { breadcrumb -> + breadcrumb.timestamp != null && + breadcrumb.timestamp!! >= from && + breadcrumb.timestamp!! <= to + } + } ?: emptyList() +} +``` + +## 8. 配置和最佳实践 + +### 8.1 关键配置选项 + +```kotlin +// 启用 Session Replay +options.sessionReplay.isEnabled = true + +// 全会话采样率(0.0-1.0) +options.sessionReplay.sessionSampleRate = 0.1 // 10% + +// 错误时采样率(0.0-1.0) +options.sessionReplay.onErrorSampleRate = 1.0 // 100% + +// 录制质量 +options.sessionReplay.quality = SentryReplayOptions.Quality.MEDIUM + +// 会话持续时间(默认1小时) +options.sessionReplay.sessionDuration = 60 * 60 * 1000L + +// 段持续时间(默认10秒) +options.sessionReplay.sessionSegmentDuration = 10 * 1000L + +// 错误缓冲时长(默认30秒) +options.sessionReplay.errorReplayDuration = 30 * 1000L + +// 遮罩敏感信息 +options.sessionReplay.maskAllText = true +options.sessionReplay.maskAllImages = false +``` + +### 8.2 性能优化建议 + +#### ✅ 推荐做法 + +1. **合理设置采样率** + ```kotlin + // 生产环境:低采样率 + options.sessionReplay.sessionSampleRate = 0.01 // 1% + options.sessionReplay.onErrorSampleRate = 1.0 // 100% + + // 开发环境:高采样率 + options.sessionReplay.sessionSampleRate = 1.0 // 100% + ``` + +2. **选择合适的质量** + ```kotlin + // 移动网络:低质量 + options.sessionReplay.quality = SentryReplayOptions.Quality.LOW + + // WiFi网络:中等质量 + options.sessionReplay.quality = SentryReplayOptions.Quality.MEDIUM + ``` + +3. **启用敏感信息遮罩** + ```kotlin + options.sessionReplay.maskAllText = true + options.sessionReplay.maskAllImages = true + ``` + +#### ❌ 避免做法 + +- **过高的采样率**:会显著增加存储和网络使用 +- **过长的缓冲时间**:会占用更多内存和存储空间 +- **在低端设备上启用高质量**:可能影响应用性能 + +### 8.3 存储和网络影响 + +#### 存储使用估算 + +```kotlin +// 估算公式(每分钟) +fun estimateStoragePerMinute(config: ScreenshotRecorderConfig): Long { + val frameSize = config.recordingWidth * config.recordingHeight * 3 / 4 // JPEG压缩约75% + val framesPerMinute = config.frameRate * 60 + return frameSize * framesPerMinute +} + +// 示例:中等质量,1080x1920,1fps +// 约 1080 * 1920 * 0.75 * 60 = 93MB/分钟 +``` + +#### 网络使用优化 + +```kotlin +// 视频压缩后大小显著减少 +fun estimateVideoSize(frameCount: Int, duration: Long, bitRate: Int): Long { + return (bitRate * duration / 8000) // 比特率转字节,毫秒转秒 +} + +// 示例:10秒段,100kbps比特率 +// 约 100000 * 10 / 8000 = 125KB +``` + +## 9. 故障排查 + +### 9.1 常见问题 + +**Q: Replay 没有录制?** +A: 检查采样率设置,确保 `sessionSampleRate` 或 `onErrorSampleRate` > 0 + +**Q: 视频质量太差?** +A: 调整 `quality` 设置或增加 `bitRate` + +**Q: 应用性能下降?** +A: 降低采样率,使用较低的录制质量,或减少缓冲时长 + +**Q: 存储空间不足?** +A: 检查缓存清理机制,确保旧的 replay 文件被正确删除 + +### 9.2 调试技巧 + +```kotlin +// 启用 Replay 调试日志 +options.sessionReplay.isDebug = true +options.setDebug(true) + +// 监控 Replay 状态 +val replayController = options.replayController +println("Is recording: ${replayController.isRecording}") + +// 检查缓存目录 +val cacheDir = File(options.cacheDirPath, "replay_${replayId}") +println("Cache dir exists: ${cacheDir.exists()}") +println("Frame count: ${cacheDir.listFiles()?.count { it.name.endsWith(".jpg") }}") + +// 手动触发错误录制 +replayController.captureReplay(isTerminating = false) +``` + +## 总结 + +Sentry 的 Session Replay 功能通过精密的录制策略和高效的视频编码,为开发者提供了强大的用户行为回放能力: + +### 🎯 **核心优势** + +1. **双重录制策略**: 全会话和错误缓冲模式满足不同需求 +2. **智能采样**: 灵活的采样率控制,平衡数据价值和性能影响 +3. **高效编码**: 基于 MediaCodec 的硬件加速视频编码 +4. **事件关联**: 触摸事件和面包屑的完整捕获 +5. **隐私保护**: 内置的敏感信息遮罩机制 + +### 🔍 **技术特点** + +- **内存优化**: 帧轮转和缓存限制机制 +- **存储高效**: JPEG 压缩和 MP4 编码大幅减少存储需求 +- **网络友好**: 分段上传和压缩优化 +- **崩溃恢复**: 前一会话的智能恢复机制 + +### 📊 **应用价值** + +通过这套 Replay 系统,开发者可以: +- 直观了解用户操作流程 +- 快速定位和重现问题 +- 分析用户体验痛点 +- 验证修复效果 + +这套机制在保证用户隐私和应用性能的前提下,为问题诊断和用户体验优化提供了强有力的工具支撑。 \ No newline at end of file diff --git a/cursor/sentry-session-management.md b/cursor/sentry-session-management.md new file mode 100644 index 0000000000..b342dcd227 --- /dev/null +++ b/cursor/sentry-session-management.md @@ -0,0 +1,773 @@ +# Sentry 会话管理机制深度分析 + +本文档详细分析了 Sentry Java SDK 如何管理用户会话,包括会话生命周期、状态跟踪、持久化机制、异常处理等核心功能。 + +## 🎯 会话管理概览 + +Sentry 通过完整的会话管理系统来跟踪应用的使用情况和稳定性: + +```mermaid +graph TD + A[应用启动] --> B[会话初始化] + B --> C[会话状态: Ok] + + C --> D{应用状态变化} + D --> E[前台/后台切换] + D --> F[异常发生] + D --> G[正常退出] + + E --> H[LifecycleWatcher] + F --> I[异常状态更新] + G --> J[会话结束] + + H --> K[会话间隔检查] + K --> L[新会话创建] + K --> M[当前会话继续] + + I --> N[状态: Crashed/Abnormal] + J --> O[状态: Exited] + + L --> P[会话持久化] + M --> P + N --> P + O --> P + + P --> Q[EnvelopeCache] + Q --> R[磁盘存储] + R --> S[数据上报] +``` + +## 1. 会话数据结构 + +### 1.1 Session 核心属性 + +```java +public final class Session implements JsonUnknown, JsonSerializable { + + /** 会话状态枚举 */ + public enum State { + Ok, // 正常运行 + Exited, // 正常退出 + Crashed, // 崩溃 + Abnormal // 异常退出(如ANR) + } + + // 核心时间戳 + private final @NotNull Date started; // 会话开始时间 + private @Nullable Date timestamp; // 最后更新时间 + + // 会话标识 + private final @Nullable String sessionId; // 会话ID (sid) + private final @Nullable String distinctId; // 用户唯一标识 (did) + + // 会话状态 + private @NotNull State status; // 当前状态 + private final @NotNull AtomicInteger errorCount; // 错误计数 + private @Nullable Boolean init; // 初始化标志 + + // 会话指标 + private @Nullable Long sequence; // 逻辑时钟 + private @Nullable Double duration; // 会话持续时间 + + // 环境信息 + private final @NotNull String release; // 应用版本 + private final @Nullable String environment; // 环境标识 + private final @Nullable String ipAddress; // IP地址 + private @Nullable String userAgent; // 用户代理 + + // 异常信息 + private @Nullable String abnormalMechanism; // 异常机制(如ANR) + + // 线程安全 + private final @NotNull AutoClosableReentrantLock sessionLock = new AutoClosableReentrantLock(); +} +``` + +### 1.2 会话创建 + +```java +// 标准会话创建 +public Session( + @Nullable String distinctId, + final @Nullable User user, + final @Nullable String environment, + final @NotNull String release) { + + this( + State.Ok, // 初始状态为 Ok + DateUtils.getCurrentDateTime(), // 当前时间作为开始时间 + DateUtils.getCurrentDateTime(), // 当前时间作为时间戳 + 0, // 错误计数为 0 + distinctId, // 用户唯一标识 + SentryUUID.generateSentryId(), // 生成会话ID + true, // 标记为初始化会话 + null, // 序列号稍后设置 + null, // 持续时间稍后计算 + (user != null ? user.getIpAddress() : null), // 用户IP + null, // 用户代理稍后设置 + environment, // 环境 + release, // 版本 + null // 异常机制 + ); +} +``` + +## 2. 会话生命周期管理 + +### 2.1 Scope 中的会话管理 + +```java +public final class Scope implements IScope { + private volatile @Nullable Session session; + private final @NotNull AutoClosableReentrantLock sessionLock = new AutoClosableReentrantLock(); + + /** + * 启动新会话,返回会话对 + * @return SessionPair 包含当前会话和前一个会话 + */ + @Override + public @Nullable SessionPair startSession() { + Session previousSession; + SessionPair pair = null; + + try (final @NotNull ISentryLifecycleToken ignored = sessionLock.acquire()) { + if (session != null) { + // 结束当前会话(不传递scope,避免自动刷新) + session.end(); + } + previousSession = session; + + if (options.getRelease() != null) { + // 创建新会话 + session = new Session( + options.getDistinctId(), + user, + options.getEnvironment(), + options.getRelease() + ); + + final Session previousClone = previousSession != null ? previousSession.clone() : null; + pair = new SessionPair(session.clone(), previousClone); + } else { + options.getLogger().log(SentryLevel.WARNING, + "Release is not set on SentryOptions. Session could not be started"); + } + } + return pair; + } + + /** + * 结束会话 + * @return 结束的会话 + */ + @Override + public @Nullable Session endSession() { + Session previousSession = null; + try (final @NotNull ISentryLifecycleToken ignored = sessionLock.acquire()) { + if (session != null) { + session.end(); + previousSession = session.clone(); + session = null; // 从scope中移除 + } + } + return previousSession; + } +} +``` + +### 2.2 SessionPair 设计 + +```java +static final class SessionPair { + /** 前一个会话(如果存在) */ + private final @Nullable Session previous; + + /** 当前会话 */ + private final @NotNull Session current; + + public SessionPair(final @NotNull Session current, final @Nullable Session previous) { + this.current = current; + this.previous = previous; + } + + public @Nullable Session getPrevious() { return previous; } + public @NotNull Session getCurrent() { return current; } +} +``` + +## 3. Android 生命周期集成 + +### 3.1 LifecycleWatcher 核心机制 + +```java +final class LifecycleWatcher implements DefaultLifecycleObserver { + private final AtomicLong lastUpdatedSession = new AtomicLong(0L); + private final long sessionIntervalMillis; // 会话间隔阈值 + private final @NotNull IScopes scopes; + private final boolean enableSessionTracking; + + // 应用进入前台 + @Override + public void onStart(final @NotNull LifecycleOwner owner) { + startSession(); + addAppBreadcrumb("foreground"); + AppState.getInstance().setInBackground(false); + } + + // 应用进入后台 + @Override + public void onStop(final @NotNull LifecycleOwner owner) { + final long currentTimeMillis = currentDateProvider.getCurrentTimeMillis(); + this.lastUpdatedSession.set(currentTimeMillis); + + scopes.getOptions().getReplayController().pause(); + scheduleEndSession(); // 延迟结束会话 + + AppState.getInstance().setInBackground(true); + addAppBreadcrumb("background"); + } +} +``` + +### 3.2 会话间隔检查 + +```java +private void startSession() { + cancelTask(); // 取消之前的延迟任务 + + final long currentTimeMillis = currentDateProvider.getCurrentTimeMillis(); + + // 获取当前会话的开始时间 + scopes.configureScope(scope -> { + if (lastUpdatedSession.get() == 0L) { + final @Nullable Session currentSession = scope.getSession(); + if (currentSession != null && currentSession.getStarted() != null) { + lastUpdatedSession.set(currentSession.getStarted().getTime()); + } + } + }); + + final long lastUpdatedSession = this.lastUpdatedSession.get(); + + // 检查是否需要创建新会话 + if (lastUpdatedSession == 0L || + (lastUpdatedSession + sessionIntervalMillis) <= currentTimeMillis) { + + if (enableSessionTracking) { + scopes.startSession(); // 创建新会话 + } + scopes.getOptions().getReplayController().start(); + } + + scopes.getOptions().getReplayController().resume(); + this.lastUpdatedSession.set(currentTimeMillis); +} +``` + +### 3.3 延迟会话结束 + +```java +private void scheduleEndSession() { + try (final @NotNull ISentryLifecycleToken ignored = timerLock.acquire()) { + cancelTask(); + if (timer != null) { + timerTask = new TimerTask() { + @Override + public void run() { + if (enableSessionTracking) { + scopes.endSession(); // 结束会话 + } + scopes.getOptions().getReplayController().stop(); + } + }; + + // 在会话间隔时间后执行 + timer.schedule(timerTask, sessionIntervalMillis); + } + } +} +``` + +## 4. 会话状态更新 + +### 4.1 原子性状态更新 + +```java +public boolean update( + final @Nullable State status, + final @Nullable String userAgent, + final boolean addErrorsCount, + final @Nullable String abnormalMechanism) { + + try (final @NotNull ISentryLifecycleToken ignored = sessionLock.acquire()) { + boolean sessionHasBeenUpdated = false; + + // 更新状态 + if (status != null) { + this.status = status; + sessionHasBeenUpdated = true; + } + + // 更新用户代理 + if (userAgent != null) { + this.userAgent = userAgent; + sessionHasBeenUpdated = true; + } + + // 增加错误计数 + if (addErrorsCount) { + errorCount.addAndGet(1); + sessionHasBeenUpdated = true; + } + + // 更新异常机制(一旦设置就不会被覆盖) + if (abnormalMechanism != null) { + this.abnormalMechanism = abnormalMechanism; + sessionHasBeenUpdated = true; + } + + if (sessionHasBeenUpdated) { + init = null; // 清除初始化标志 + timestamp = DateUtils.getCurrentDateTime(); + if (timestamp != null) { + sequence = getSequenceTimestamp(timestamp); // 更新逻辑时钟 + } + } + + return sessionHasBeenUpdated; + } +} +``` + +### 4.2 会话结束处理 + +```java +public void end(final @Nullable Date timestamp) { + try (final @NotNull ISentryLifecycleToken ignored = sessionLock.acquire()) { + init = null; // 清除初始化标志 + + // 只有Ok状态才会变为Exited,Crashed状态保持不变 + if (status == State.Ok) { + status = State.Exited; + } + + // 设置结束时间戳 + if (timestamp != null) { + this.timestamp = timestamp; + } else { + this.timestamp = DateUtils.getCurrentDateTime(); + } + + // 计算持续时间和序列号 + if (this.timestamp != null) { + duration = calculateDurationTime(this.timestamp); + sequence = getSequenceTimestamp(this.timestamp); + } + } +} + +/** + * 计算会话持续时间(秒) + */ +private double calculateDurationTime(final @NotNull Date timestamp) { + final long diff = Math.abs(timestamp.getTime() - started.getTime()); + return (double) diff / 1000; // 转换为秒 +} +``` + +## 5. 会话持久化机制 + +### 5.1 EnvelopeCache 会话文件管理 + +```java +public final class EnvelopeCache implements IEnvelopeCache { + // 会话文件命名 + private static final String PREFIX_CURRENT_SESSION_FILE = "session"; + private static final String PREFIX_PREVIOUS_SESSION_FILE = "previous_session"; + private static final String SUFFIX_SESSION_FILE = ".json"; + + public static @NotNull File getCurrentSessionFile(final @NotNull String cacheDirPath) { + return new File(cacheDirPath, PREFIX_CURRENT_SESSION_FILE + SUFFIX_SESSION_FILE); + } + + public static @NotNull File getPreviousSessionFile(final @NotNull String cacheDirPath) { + return new File(cacheDirPath, PREFIX_PREVIOUS_SESSION_FILE + SUFFIX_SESSION_FILE); + } +} +``` + +### 5.2 会话存储流程 + +```java +@Override +public void store(final @NotNull SentryEnvelope envelope, final @NotNull Hint hint) { + final File currentSessionFile = getCurrentSessionFile(directory.getAbsolutePath()); + final File previousSessionFile = getPreviousSessionFile(directory.getAbsolutePath()); + + // 处理会话结束 + if (HintUtils.hasType(hint, SessionEnd.class)) { + if (!currentSessionFile.delete()) { + options.getLogger().log(WARNING, "Current envelope doesn't exist."); + } + } + + // 处理异常退出 + if (HintUtils.hasType(hint, AbnormalExit.class)) { + tryEndPreviousSession(hint); + } + + // 处理会话开始 + if (HintUtils.hasType(hint, SessionStart.class)) { + if (currentSessionFile.exists()) { + options.getLogger().log(WARNING, "Current session is not ended, we'd need to end it."); + + // 将当前会话移动到previous文件 + try (final Reader reader = new BufferedReader( + new InputStreamReader(new FileInputStream(currentSessionFile), UTF_8))) { + + final Session session = serializer.getValue().deserialize(reader, Session.class); + if (session != null) { + writeSessionToDisk(previousSessionFile, session); + } + } catch (Throwable e) { + options.getLogger().log(SentryLevel.ERROR, "Error processing session.", e); + } + } + + // 更新当前会话文件 + updateCurrentSession(currentSessionFile, envelope); + + // 检查崩溃标记文件 + boolean crashedLastRun = false; + final File crashMarkerFile = new File(options.getCacheDirPath(), NATIVE_CRASH_MARKER_FILE); + if (crashMarkerFile.exists()) { + crashedLastRun = true; + } + + // 处理前一次运行的崩溃 + if (crashedLastRun) { + handlePreviousCrash(previousSessionFile, crashMarkerFile); + } + } +} +``` + +### 5.3 异常退出处理 + +```java +private void tryEndPreviousSession(final @NotNull Hint hint) { + final Object sdkHint = HintUtils.getSentrySdkHint(hint); + if (sdkHint instanceof AbnormalExit) { + final File previousSessionFile = getPreviousSessionFile(directory.getAbsolutePath()); + + if (previousSessionFile.exists()) { + try (final Reader reader = new BufferedReader( + new InputStreamReader(new FileInputStream(previousSessionFile), UTF_8))) { + + final Session session = serializer.getValue().deserialize(reader, Session.class); + if (session != null) { + final AbnormalExit abnormalHint = (AbnormalExit) sdkHint; + final @Nullable Long abnormalExitTimestamp = abnormalHint.timestamp(); + Date timestamp = null; + + if (abnormalExitTimestamp != null) { + timestamp = DateUtils.getDateTime(abnormalExitTimestamp); + + // 检查异常退出时间是否在会话期间 + final Date sessionStart = session.getStarted(); + if (sessionStart == null || timestamp.before(sessionStart)) { + options.getLogger().log(WARNING, + "Abnormal exit happened before previous session start, not ending the session."); + return; + } + } + + final String abnormalMechanism = abnormalHint.mechanism(); + session.update(Session.State.Abnormal, null, true, abnormalMechanism); + session.end(timestamp); // 使用异常退出的实际时间戳 + writeSessionToDisk(previousSessionFile, session); + } + } catch (Throwable e) { + options.getLogger().log(ERROR, "Error processing previous session.", e); + } + } + } +} +``` + +## 6. 前一会话终结器 + +### 6.1 PreviousSessionFinalizer 机制 + +```java +final class PreviousSessionFinalizer implements Runnable { + + @Override + public void run() { + final String cacheDirPath = options.getCacheDirPath(); + if (cacheDirPath == null || !options.isEnableAutoSessionTracking()) { + return; + } + + // 等待前一会话刷新完成 + final IEnvelopeCache cache = options.getEnvelopeDiskCache(); + if (cache instanceof EnvelopeCache) { + if (!((EnvelopeCache) cache).waitPreviousSessionFlush()) { + options.getLogger().log(SentryLevel.WARNING, + "Timed out waiting to flush previous session to its own file in session finalizer."); + return; + } + } + + final File previousSessionFile = EnvelopeCache.getPreviousSessionFile(cacheDirPath); + + if (previousSessionFile.exists()) { + try (final Reader reader = new BufferedReader( + new InputStreamReader(new FileInputStream(previousSessionFile), UTF_8))) { + + final Session session = serializer.deserialize(reader, Session.class); + if (session != null) { + Date timestamp = null; + + // 检查崩溃标记文件 + final File crashMarkerFile = new File(options.getCacheDirPath(), NATIVE_CRASH_MARKER_FILE); + if (crashMarkerFile.exists()) { + options.getLogger().log(INFO, "Crash marker file exists, last Session is gonna be Crashed."); + + timestamp = getTimestampFromCrashMarkerFile(crashMarkerFile); + + if (!crashMarkerFile.delete()) { + options.getLogger().log(ERROR, "Failed to delete the crash marker file. %s.", + crashMarkerFile.getAbsolutePath()); + } + session.update(Session.State.Crashed, null, true); + } + + // 如果没有异常机制,使用计算的时间戳结束会话 + if (session.getAbnormalMechanism() == null) { + session.end(timestamp); + } + + // 创建信封并发送 + final SentryEnvelope fromSession = SentryEnvelope.from(serializer, session, options.getSdkVersion()); + scopes.captureEnvelope(fromSession); + } + } catch (Throwable e) { + options.getLogger().log(ERROR, "Error processing previous session file.", e); + } finally { + if (!previousSessionFile.delete()) { + options.getLogger().log(ERROR, "Failed to delete the previous session file."); + } + } + } + } +} +``` + +## 7. 事件与会话关联 + +### 7.1 SentryClient 中的会话更新 + +```java +@Nullable +Session updateSessionData( + final @NotNull SentryEvent event, + final @NotNull Hint hint, + final @Nullable IScope scope) { + + Session clonedSession = null; + + if (HintUtils.shouldApplyScopeData(hint) && scope != null) { + clonedSession = scope.withSession(session -> { + if (session != null) { + Session.State status = null; + if (event.isCrashed()) { + status = Session.State.Crashed; + } + + boolean crashedOrErrored = false; + if (Session.State.Crashed == status || event.isErrored()) { + crashedOrErrored = true; + } + + // 从请求头中提取用户代理 + String userAgent = null; + if (event.getRequest() != null && event.getRequest().getHeaders() != null) { + userAgent = event.getRequest().getHeaders().get("user-agent"); + } + + // 处理异常退出提示 + final Object sentrySdkHint = HintUtils.getSentrySdkHint(hint); + @Nullable String abnormalMechanism = null; + if (sentrySdkHint instanceof AbnormalExit) { + abnormalMechanism = ((AbnormalExit) sentrySdkHint).mechanism(); + status = Session.State.Abnormal; + } + + // 更新会话 + if (session.update(status, userAgent, crashedOrErrored, abnormalMechanism)) { + // 如果会话已终止,结束它 + if (session.isTerminated()) { + session.end(); + } + } + } + }); + } + + return clonedSession; +} +``` + +## 8. 配置和最佳实践 + +### 8.1 关键配置选项 + +```java +// 启用自动会话跟踪 +options.setEnableAutoSessionTracking(true); + +// 设置会话间隔(默认30秒) +options.setSessionTrackingIntervalMillis(30000L); + +// 启用应用生命周期面包屑 +options.setEnableAppLifecycleBreadcrumbs(true); + +// 设置版本信息(会话必需) +options.setRelease("your-app@1.0.0"); +options.setEnvironment("production"); + +// 设置用户唯一标识 +options.setDistinctId("user-unique-id"); +``` + +### 8.2 会话跟踪最佳实践 + +#### ✅ 推荐做法 + +1. **正确设置版本信息** + ```java + options.setRelease(BuildConfig.VERSION_NAME + "@" + BuildConfig.VERSION_CODE); + options.setEnvironment(BuildConfig.DEBUG ? "debug" : "production"); + ``` + +2. **合理设置会话间隔** + ```java + // 生产环境:30秒(默认) + options.setSessionTrackingIntervalMillis(30000L); + + // 开发环境:可以设置更短的间隔用于测试 + options.setSessionTrackingIntervalMillis(5000L); + ``` + +3. **设置用户标识** + ```java + // 使用稳定的用户标识 + options.setDistinctId(getUserUniqueId()); + + // 或在运行时设置 + Sentry.configureScope(scope -> { + scope.setUser(new User().setId("user-123")); + }); + ``` + +#### ❌ 避免做法 + +- **频繁的会话间隔**:过短的间隔会导致过多的会话创建 +- **缺少版本信息**:没有release信息会导致会话无法创建 +- **手动会话管理**:除非特殊需求,避免手动调用startSession/endSession + +### 8.3 会话数据解读 + +#### 会话指标含义 + +- **会话数量**: 应用启动和使用的次数 +- **崩溃率**: Crashed状态会话占总会话的比例 +- **异常率**: Abnormal状态会话占总会话的比例 +- **会话持续时间**: 用户使用应用的时长分布 + +#### 健康度评估 + +```java +// 会话健康度计算示例 +public class SessionHealth { + public static double calculateCrashFreeRate(int totalSessions, int crashedSessions) { + if (totalSessions == 0) return 1.0; + return 1.0 - ((double) crashedSessions / totalSessions); + } + + public static boolean isHealthy(double crashFreeRate) { + return crashFreeRate >= 0.99; // 99%以上为健康 + } +} +``` + +## 9. 故障排查 + +### 9.1 常见问题 + +**Q: 会话没有被创建?** +A: 检查是否设置了release信息,这是会话创建的必要条件 + +**Q: 会话持续时间异常?** +A: 检查设备时间是否正确,会话持续时间基于系统时间计算 + +**Q: 崩溃会话没有被标记?** +A: 确保崩溃处理器正确集成,检查崩溃标记文件是否正常创建 + +**Q: 会话间隔不生效?** +A: 检查应用生命周期监听是否正确注册,确保LifecycleWatcher正常工作 + +### 9.2 调试技巧 + +```java +// 启用详细日志 +options.setDebug(true); +options.setLogger(new SystemOutLogger()); + +// 监控会话状态 +Sentry.configureScope(scope -> { + Session session = scope.getSession(); + if (session != null) { + System.out.println("Session ID: " + session.getSessionId()); + System.out.println("Session Status: " + session.getStatus()); + System.out.println("Error Count: " + session.errorCount()); + System.out.println("Duration: " + session.getDuration()); + } +}); + +// 检查会话文件 +File currentSessionFile = EnvelopeCache.getCurrentSessionFile(options.getCacheDirPath()); +File previousSessionFile = EnvelopeCache.getPreviousSessionFile(options.getCacheDirPath()); +System.out.println("Current session file exists: " + currentSessionFile.exists()); +System.out.println("Previous session file exists: " + previousSessionFile.exists()); +``` + +## 总结 + +Sentry 的会话管理机制通过完善的生命周期跟踪和状态管理,为开发者提供了全面的应用使用情况洞察: + +### 🎯 **核心优势** + +1. **自动化管理**: 与Android生命周期深度集成,自动跟踪会话 +2. **状态完整性**: 支持Ok、Exited、Crashed、Abnormal四种状态 +3. **持久化保障**: 完善的磁盘存储和恢复机制 +4. **异常处理**: 智能处理各种异常退出场景 +5. **线程安全**: 全面的锁机制确保并发安全 + +### 🔍 **监控范围** + +- **会话生命周期**: 从创建到结束的完整跟踪 +- **应用稳定性**: 崩溃率和异常率统计 +- **用户行为**: 会话持续时间和使用模式 +- **版本对比**: 不同版本间的稳定性对比 + +### 📊 **数据价值** + +通过这套会话管理机制,开发者可以: +- 监控应用稳定性趋势 +- 识别影响用户体验的问题 +- 评估版本发布的影响 +- 优化应用的可靠性 + +这套机制确保了在各种使用场景下,都能准确跟踪和分析用户会话,为应用质量改进提供可靠的数据支撑。 \ No newline at end of file diff --git a/cursor/sentry-startup-monitoring.md b/cursor/sentry-startup-monitoring.md new file mode 100644 index 0000000000..755ff4f693 --- /dev/null +++ b/cursor/sentry-startup-monitoring.md @@ -0,0 +1,681 @@ +# Sentry 启动监控机制深度分析 + +本文档详细分析了 Sentry Android SDK 如何监控应用的冷启动和热启动,包括启动类型检测、性能指标收集、时间跨度测量等核心机制。 + +## 🎯 启动监控概览 + +Sentry 通过多层监控机制来全面跟踪应用启动性能: + +```mermaid +graph TD + A[应用启动] --> B{启动类型检测} + B --> C[冷启动 Cold Start] + B --> D[热启动 Warm Start] + + C --> E[进程初始化监控] + C --> F[ContentProvider 监控] + C --> G[Application.onCreate 监控] + C --> H[Activity 生命周期监控] + + D --> I[Activity 重启监控] + D --> J[前台恢复监控] + + E --> K[性能指标收集] + F --> K + G --> K + H --> K + I --> K + J --> K + + K --> L[TTID/TTFD 测量] + L --> M[事务和跨度创建] + M --> N[性能数据上报] +``` + +## 1. 启动类型检测机制 + +### 1.1 启动类型定义 + +```java +public enum AppStartType { + UNKNOWN, // 未知类型 + COLD, // 冷启动:进程从零开始创建 + WARM // 热启动:进程已存在,重新启动Activity +} +``` + +### 1.2 启动类型判断逻辑 + +```java +private void setColdStart(final @Nullable Bundle savedInstanceState) { + if (!firstActivityCreated) { + final @NotNull TimeSpan appStartSpan = AppStartMetrics.getInstance().getAppStartTimeSpan(); + + // 判断是否为热启动的条件: + // 1. 应用启动跨度已经开始并停止(进程重启但未杀死) + // 2. 冷启动无效(后台启动,如通过BroadcastReceiver) + if ((appStartSpan.hasStarted() && appStartSpan.hasStopped()) + || (!AppStartMetrics.getInstance().isColdStartValid())) { + + // 重启应用启动测量,标记为热启动 + AppStartMetrics.getInstance().restartAppStart(lastPausedUptimeMillis); + AppStartMetrics.getInstance().setAppStartType(AppStartMetrics.AppStartType.WARM); + } else { + // 根据 savedInstanceState 判断启动类型 + AppStartMetrics.getInstance().setAppStartType( + savedInstanceState == null + ? AppStartMetrics.AppStartType.COLD // 无状态保存 = 冷启动 + : AppStartMetrics.AppStartType.WARM // 有状态保存 = 热启动 + ); + } + } +} +``` + +### 1.3 冷启动有效性检查 + +```java +public boolean isColdStartValid() { + return appLaunchedInForeground && !appLaunchTooLong; +} + +// 前台重要性检查 +private void checkCreateTimeOnMain(final @NotNull Application application) { + new Handler(Looper.getMainLooper()).post(() -> { + // 如果没有Activity被创建,说明应用在后台启动 + if (onCreateTime == null) { + appLaunchedInForeground = false; + + // 停止应用启动分析器,因为后台启动的分析没有意义 + if (appStartProfiler != null && appStartProfiler.isRunning()) { + appStartProfiler.close(); + appStartProfiler = null; + } + } + application.unregisterActivityLifecycleCallbacks(instance); + }); +} + +// 启动时间过长检查 +@Override +public void onActivityCreated(@NonNull Activity activity, @Nullable Bundle savedInstanceState) { + if (!appLaunchedInForeground || onCreateTime != null) { + return; + } + onCreateTime = new SentryNanotimeDate(); + + final long spanStartMillis = appStartSpan.getStartTimestampMs(); + final long spanEndMillis = appStartSpan.hasStopped() + ? appStartSpan.getProjectedStopTimestampMs() + : System.currentTimeMillis(); + final long durationMillis = spanEndMillis - spanStartMillis; + + // 如果应用启动超过1分钟,认为是异常情况 + if (durationMillis > TimeUnit.MINUTES.toMillis(1)) { + appLaunchTooLong = true; + } +} +``` + +## 2. 时间跨度测量体系 + +### 2.1 核心时间跨度 + +Sentry 使用多个 `TimeSpan` 来精确测量启动过程的各个阶段: + +```java +public class AppStartMetrics { + private final @NotNull TimeSpan appStartSpan; // 应用启动总时间 + private final @NotNull TimeSpan sdkInitTimeSpan; // SDK初始化时间 + private final @NotNull TimeSpan applicationOnCreate; // Application.onCreate时间 + private final @NotNull Map contentProviderOnCreates; // ContentProvider创建时间 + private final @NotNull List activityLifecycles; // Activity生命周期时间 +} +``` + +### 2.2 时间测量流程 + +```mermaid +sequenceDiagram + participant Process as 进程启动 + participant Provider as SentryPerformanceProvider + participant SDK as Sentry SDK + participant App as Application + participant Activity as Activity + participant Metrics as AppStartMetrics + + Note over Process: 冷启动开始 + Process->>Provider: ContentProvider.onCreate() + Provider->>Metrics: 设置 SDK 初始化时间 + Note over Provider: sdkInitMillis = SystemClock.uptimeMillis() + + alt Android N+ (API 24+) + Provider->>Metrics: 设置应用启动时间 + Note over Provider: Process.getStartUptimeMillis() + end + + Provider->>App: Application.onCreate() + App->>Metrics: onApplicationCreate() + Note over Metrics: 记录 Application.onCreate 开始时间 + + App->>Activity: Activity.onCreate() + Activity->>Metrics: onActivityCreated() + Note over Metrics: 检查启动类型和有效性 + + Activity->>Activity: 首帧绘制 + Activity->>Metrics: onFirstFrameDrawn() + Note over Metrics: 停止所有时间跨度测量 +``` + +### 2.3 时间戳获取策略 + +```java +// Performance V2 (Android N+): 使用进程启动时间 +@SuppressLint("NewApi") +private void onAppLaunched(final @Nullable Context context, final @NotNull AppStartMetrics appStartMetrics) { + // SDK初始化时间:使用静态字段初始化时间 + final @NotNull TimeSpan sdkInitTimeSpan = appStartMetrics.getSdkInitTimeSpan(); + sdkInitTimeSpan.setStartedAt(sdkInitMillis); + + // 应用启动时间:需要 API 24+ + if (buildInfoProvider.getSdkInfoVersion() >= android.os.Build.VERSION_CODES.N) { + final @NotNull TimeSpan appStartTimespan = appStartMetrics.getAppStartTimeSpan(); + appStartTimespan.setStartedAt(Process.getStartUptimeMillis()); + } +} + +// 回退策略:使用 SDK 初始化时间 +public @NotNull TimeSpan getAppStartTimeSpanWithFallback(final @NotNull SentryAndroidOptions options) { + // 如果启动时间过长或后台启动,返回空跨度 + if (!isColdStartValid()) { + return new TimeSpan(); + } + + if (options.isEnablePerformanceV2()) { + final @NotNull TimeSpan appStartSpan = getAppStartTimeSpan(); + if (appStartSpan.hasStarted()) { + return appStartSpan; + } + } + + // 回退:使用 SDK 初始化时间跨度 + return getSdkInitTimeSpan(); +} +``` + +## 3. 冷启动监控详解 + +### 3.1 冷启动阶段划分 + +冷启动被细分为多个可测量的阶段: + +```mermaid +gantt + title 冷启动时间线 + dateFormat X + axisFormat %s + + section 进程初始化 + Process Init :0, 100 + + section ContentProvider + Provider 1 :100, 150 + Provider 2 :150, 200 + + section Application + Application.onCreate :200, 300 + + section Activity + Activity.onCreate :300, 400 + Activity.onStart :400, 450 + Activity.onResume :450, 500 + + section 渲染 + First Frame :500, 600 + Full Display :600, 800 +``` + +### 3.2 进程初始化监控 + +```java +public @NotNull TimeSpan createProcessInitSpan() { + final @NotNull TimeSpan processInitSpan = new TimeSpan(); + processInitSpan.setup( + "Process Initialization", + appStartSpan.getStartTimestampMs(), // 进程启动时间 + appStartSpan.getStartUptimeMs(), + CLASS_LOADED_UPTIME_MS // 类加载完成时间 + ); + return processInitSpan; +} +``` + +### 3.3 ContentProvider 监控 + +```java +// 通过字节码插桩自动调用 +public static void onContentProviderCreate(final @NotNull ContentProvider contentProvider) { + final long now = SystemClock.uptimeMillis(); + + final TimeSpan measurement = new TimeSpan(); + measurement.setStartedAt(now); + getInstance().contentProviderOnCreates.put(contentProvider, measurement); +} + +public static void onContentProviderPostCreate(final @NotNull ContentProvider contentProvider) { + final long now = SystemClock.uptimeMillis(); + + final @Nullable TimeSpan measurement = getInstance().contentProviderOnCreates.get(contentProvider); + if (measurement != null && measurement.hasNotStopped()) { + measurement.setDescription(contentProvider.getClass().getName() + ".onCreate"); + measurement.setStoppedAt(now); + } +} +``` + +### 3.4 Application.onCreate 监控 + +```java +// 通过字节码插桩自动调用 +public static void onApplicationCreate(final @NotNull Application application) { + final long now = SystemClock.uptimeMillis(); + + final @NotNull AppStartMetrics instance = getInstance(); + if (instance.applicationOnCreate.hasNotStarted()) { + instance.applicationOnCreate.setStartedAt(now); + } +} + +public static void onApplicationPostCreate(final @NotNull Application application) { + final long now = SystemClock.uptimeMillis(); + + final @NotNull AppStartMetrics instance = getInstance(); + if (instance.applicationOnCreate.hasNotStopped()) { + instance.applicationOnCreate.setDescription(application.getClass().getName() + ".onCreate"); + instance.applicationOnCreate.setStoppedAt(now); + } +} +``` + +## 4. 热启动监控详解 + +### 4.1 热启动触发条件 + +热启动在以下情况下发生: + +1. **进程重启但未杀死**:应用在后台时被系统回收部分资源 +2. **Activity 重新创建**:用户从最近任务或其他应用返回 +3. **后台启动转前台**:通过通知、快捷方式等方式启动 + +### 4.2 热启动重置机制 + +```java +public void restartAppStart(final long uptimeMillis) { + shouldSendStartMeasurements = true; + appLaunchTooLong = false; + appLaunchedInForeground = true; + + // 重置应用启动跨度 + appStartSpan.reset(); + appStartSpan.start(); + appStartSpan.setStartedAt(uptimeMillis); + + // 更新类加载时间为当前启动时间 + CLASS_LOADED_UPTIME_MS = appStartSpan.getStartUptimeMs(); +} +``` + +### 4.3 热启动时间测量 + +```java +// 热启动不包含进程初始化、ContentProvider 和 Application.onCreate +private void attachAppStartSpans(final @NotNull AppStartMetrics appStartMetrics, final @NotNull SentryTransaction txn) { + // 只有冷启动才包含进程初始化、ContentProvider 和 Application.onCreate 跨度 + if (appStartMetrics.getAppStartType() != AppStartMetrics.AppStartType.COLD) { + return; + } + + // ... 添加冷启动特有的跨度 +} +``` + +## 5. TTID 和 TTFD 监控 + +### 5.1 关键性能指标 + +- **TTID (Time To Initial Display)**: 首次内容显示时间 +- **TTFD (Time To Full Display)**: 完全显示时间 + +```java +private void onFirstFrameDrawn(final @Nullable ISpan ttfdSpan, final @Nullable ISpan ttidSpan) { + // 停止应用启动时间测量 + final @NotNull AppStartMetrics appStartMetrics = AppStartMetrics.getInstance(); + final @NotNull TimeSpan appStartTimeSpan = appStartMetrics.getAppStartTimeSpan(); + final @NotNull TimeSpan sdkInitTimeSpan = appStartMetrics.getSdkInitTimeSpan(); + + if (appStartTimeSpan.hasStarted() && appStartTimeSpan.hasNotStopped()) { + appStartTimeSpan.stop(); + } + if (sdkInitTimeSpan.hasStarted() && sdkInitTimeSpan.hasNotStopped()) { + sdkInitTimeSpan.stop(); + } + + // 设置 TTID 测量值 + if (options != null && ttidSpan != null) { + final SentryDate endDate = options.getDateProvider().now(); + final long durationNanos = endDate.diff(ttidSpan.getStartDate()); + final long durationMillis = TimeUnit.NANOSECONDS.toMillis(durationNanos); + + ttidSpan.setMeasurement(MeasurementValue.KEY_TIME_TO_INITIAL_DISPLAY, durationMillis, MILLISECOND); + finishSpan(ttidSpan, endDate); + } +} +``` + +### 5.2 TTFD 超时处理 + +```java +private static final long TTFD_TIMEOUT_MILLIS = 25000; // 25秒超时 + +private void finishExceededTtfdSpan(final @Nullable ISpan ttfdSpan, final @Nullable ISpan ttidSpan) { + if (ttfdSpan == null || ttfdSpan.isFinished()) { + return; + } + + ttfdSpan.setDescription(getExceededTtfdDesc(ttfdSpan)); + + // 将 TTFD 跨度的结束时间设置为等于 TTID 跨度 + final @Nullable SentryDate ttidEndDate = ttidSpan != null ? ttidSpan.getFinishDate() : null; + final @NotNull SentryDate ttfdEndDate = ttidEndDate != null ? ttidEndDate : ttfdSpan.getStartDate(); + + finishSpan(ttfdSpan, ttfdEndDate, SpanStatus.DEADLINE_EXCEEDED); +} +``` + +## 6. 性能数据收集和上报 + +### 6.1 测量值生成 + +```java +@Override +public @NotNull SentryTransaction process(@NotNull SentryTransaction transaction, @NotNull Hint hint) { + final @NotNull AppStartMetrics appStartMetrics = AppStartMetrics.getInstance(); + + if (hasAppStartSpan(transaction) && appStartMetrics.shouldSendStartMeasurements()) { + final @NotNull TimeSpan appStartTimeSpan = appStartMetrics.getAppStartTimeSpanWithFallback(options); + final long appStartUpDurationMs = appStartTimeSpan.getDurationMs(); + + if (appStartUpDurationMs != 0) { + final MeasurementValue value = new MeasurementValue( + (float) appStartUpDurationMs, + MeasurementUnit.Duration.MILLISECOND.apiName() + ); + + // 根据启动类型设置不同的测量键 + final String appStartKey = appStartMetrics.getAppStartType() == AppStartMetrics.AppStartType.COLD + ? MeasurementValue.KEY_APP_START_COLD // "app_start_cold" + : MeasurementValue.KEY_APP_START_WARM; // "app_start_warm" + + transaction.getMeasurements().put(appStartKey, value); + + // 附加详细的启动跨度(仅冷启动) + attachAppStartSpans(appStartMetrics, transaction); + appStartMetrics.onAppStartSpansSent(); + } + } + + return transaction; +} +``` + +### 6.2 跨度层次结构 + +```mermaid +graph TD + A[app.start.cold Transaction] --> B[app.start.cold Span] + B --> C[process.load Span] + B --> D[contentprovider.load Span 1] + B --> E[contentprovider.load Span 2] + B --> F[application.load Span] + B --> G[activity.load Span] + + H[app.start.warm Transaction] --> I[app.start.warm Span] + I --> J[activity.load Span] + + style A fill:#e1f5fe + style H fill:#fff3e0 + style B fill:#f3e5f5 + style I fill:#f3e5f5 +``` + +### 6.3 跨度数据转换 + +```java +@NotNull +private static SentrySpan timeSpanToSentrySpan( + final @NotNull TimeSpan span, + final @Nullable SpanId parentSpanId, + final @NotNull SentryId traceId, + final @NotNull String operation) { + + final Map defaultSpanData = new HashMap<>(4); + defaultSpanData.put(SpanDataConvention.THREAD_ID, AndroidThreadChecker.mainThreadSystemId); + defaultSpanData.put(SpanDataConvention.THREAD_NAME, "main"); + defaultSpanData.put(SpanDataConvention.CONTRIBUTES_TTID, true); + defaultSpanData.put(SpanDataConvention.CONTRIBUTES_TTFD, true); + + return new SentrySpan( + span.getStartTimestampSecs(), + span.getProjectedStopTimestampSecs(), + traceId, + new SpanId(), + parentSpanId, + operation, + span.getDescription(), + SpanStatus.OK, + APP_METRICS_ORIGIN, // "auto.ui" + new ConcurrentHashMap<>(), + new ConcurrentHashMap<>(), + defaultSpanData + ); +} +``` + +## 7. 启动性能分析 + +### 7.1 应用启动分析器 + +```java +private void launchAppStartProfiler(final @NotNull AppStartMetrics appStartMetrics) { + // 读取分析配置文件 + final @NotNull File configFile = new File(cacheDir, APP_START_PROFILING_CONFIG_FILE_NAME); + + if (!configFile.exists() || !configFile.canRead()) { + return; // 未启用应用启动分析 + } + + // 反序列化分析选项 + final @Nullable SentryAppStartProfilingOptions profilingOptions = + new JsonSerializer(SentryOptions.empty()).deserialize(reader, SentryAppStartProfilingOptions.class); + + if (profilingOptions != null && profilingOptions.isProfilingEnabled()) { + // 创建采样决策 + final @NotNull TracesSamplingDecision appStartSamplingDecision = new TracesSamplingDecision( + profilingOptions.isTraceSampled(), + profilingOptions.getTraceSampleRate(), + profilingOptions.isProfileSampled(), + profilingOptions.getProfileSampleRate() + ); + + appStartMetrics.setAppStartSamplingDecision(appStartSamplingDecision); + + if (appStartSamplingDecision.getProfileSampled() && appStartSamplingDecision.getSampled()) { + // 启动应用启动分析器 + final @NotNull ITransactionProfiler appStartProfiler = new AndroidTransactionProfiler( + context, buildInfoProvider, frameMetricsCollector, logger, + profilingOptions.getProfilingTracesDirPath(), + profilingOptions.isProfilingEnabled(), + profilingOptions.getProfilingTracesHz(), + new SentryExecutorService() + ); + + appStartMetrics.setAppStartProfiler(appStartProfiler); + appStartProfiler.start(); + } + } +} +``` + +### 7.2 性能数据收集 + +```java +// 收集启动期间的性能数据 +private void putPerformanceCollectionDataInMeasurements( + final @Nullable List performanceCollectionData) { + + // 时间戳差异计算,因为 PerformanceCollectionData 使用 System.currentTimeMillis() + // 而测量时间戳需要 SystemClock.elapsedRealtimeNanos() 的纳秒值 + long timestampDiff = SystemClock.elapsedRealtimeNanos() - profileStartNanos + - TimeUnit.MILLISECONDS.toNanos(System.currentTimeMillis()); + + // 处理性能数据... +} +``` + +## 8. 配置和最佳实践 + +### 8.1 关键配置选项 + +```java +// 启用性能监控 +options.setTracingEnabled(true); +options.setTracesSampleRate(1.0); + +// 启用 Performance V2 (推荐) +options.setEnablePerformanceV2(true); + +// 启用 Activity 生命周期跟踪 +options.setEnableActivityLifecycleTracingAutoFinish(true); +options.setIdleTimeout(3000L); + +// 启用 TTFD 跨度 +options.setEnableTimeToFullDisplayTracing(true); + +// 启用应用启动分析 +options.setProfilesSampleRate(1.0); +``` + +### 8.2 最佳实践 + +#### ✅ 推荐做法 + +1. **尽早初始化 Sentry** + ```java + // 在 Application.onCreate() 的最开始初始化 + public class MyApplication extends Application { + @Override + public void onCreate() { + SentryAndroid.init(this, options -> { + options.setDsn("YOUR_DSN"); + options.setEnablePerformanceV2(true); + }); + super.onCreate(); + } + } + ``` + +2. **使用 SentryPerformanceProvider** + ```xml + + + ``` + +3. **合理设置采样率** + ```java + options.setTracesSampleRate(0.1); // 生产环境建议 10% + options.setProfilesSampleRate(0.1); // 分析采样率 + ``` + +#### ❌ 避免做法 + +- **延迟初始化 Sentry**:会导致启动时间测量不准确 +- **在 ContentProvider 中执行重操作**:会影响启动性能 +- **忽略 TTFD 超时**:可能导致内存泄漏 +- **过高的采样率**:影响应用性能和数据传输 + +### 8.3 性能优化建议 + +1. **减少 ContentProvider 数量**:合并或延迟加载非关键的 ContentProvider +2. **优化 Application.onCreate()**:将非关键初始化移到后台线程 +3. **使用启动主题**:提供即时的视觉反馈 +4. **监控启动指标**:定期检查 TTID 和 TTFD 指标 + +## 9. 故障排查 + +### 9.1 常见问题 + +**Q: 启动时间测量不准确?** +A: 检查是否启用了 Performance V2,确保 Android 版本 >= N (API 24) + +**Q: 冷启动被误判为热启动?** +A: 检查应用是否在后台启动,或启动时间是否超过1分钟 + +**Q: TTFD 跨度一直超时?** +A: 检查是否正确调用了 `reportFullyDisplayed()` 或设置了合理的超时时间 + +**Q: 启动分析数据缺失?** +A: 确保分析配置文件存在且采样决策正确 + +### 9.2 调试技巧 + +```java +// 启用详细日志 +options.setDebug(true); +options.setLogger(new SystemOutLogger()); + +// 检查启动指标 +AppStartMetrics metrics = AppStartMetrics.getInstance(); +System.out.println("App start type: " + metrics.getAppStartType()); +System.out.println("Cold start valid: " + metrics.isColdStartValid()); +System.out.println("App start duration: " + metrics.getAppStartTimeSpan().getDurationMs()); + +// 监控事务处理 +options.setBeforeTransactionCallback((transaction, hint) -> { + if (transaction.getName().contains("Activity")) { + System.out.println("Processing activity transaction: " + transaction.getName()); + } + return transaction; +}); +``` + +## 总结 + +Sentry 的启动监控机制通过精密的时间测量和智能的启动类型检测,为开发者提供了全面的应用启动性能洞察: + +### 🎯 **核心优势** + +1. **精确测量**: 使用系统级 API 获取准确的启动时间 +2. **智能检测**: 自动区分冷启动和热启动 +3. **细粒度分析**: 分解启动过程的各个阶段 +4. **性能分析**: 集成 CPU 和内存分析 +5. **易于集成**: 自动化的字节码插桩和配置 + +### 🔍 **监控范围** + +- **冷启动**: 进程初始化 → ContentProvider → Application → Activity → 首帧显示 +- **热启动**: Activity 重启 → 首帧显示 +- **性能指标**: TTID、TTFD、启动总时间 +- **详细跨度**: 每个启动阶段的精确时间 + +### 📊 **数据价值** + +通过这套监控机制,开发者可以: +- 识别启动性能瓶颈 +- 优化关键启动路径 +- 监控版本间的性能变化 +- 提供更好的用户体验 + +这套机制确保了在各种启动场景下,都能准确捕获和分析应用的启动性能,为性能优化提供可靠的数据支撑。 \ No newline at end of file diff --git a/cursor/sentry-ui-jank-monitoring.md b/cursor/sentry-ui-jank-monitoring.md new file mode 100644 index 0000000000..5ad2d5da2d --- /dev/null +++ b/cursor/sentry-ui-jank-monitoring.md @@ -0,0 +1,689 @@ +# Sentry UI 卡顿监控机制深度分析 + +本文档详细分析了 Sentry Android SDK 如何监控 UI 卡顿,包括帧率监控、慢帧和冻结帧检测、性能指标收集等核心机制。 + +## 🎯 UI 卡顿监控概览 + +Sentry 通过多层帧率监控机制来全面跟踪 UI 性能: + +```mermaid +graph TD + A[UI 渲染] --> B{监控方式} + B --> C[Performance V1] + B --> D[Performance V2] + + C --> E[FrameMetricsAggregator] + C --> F[Activity 级别监控] + + D --> G[SentryFrameMetricsCollector] + D --> H[Span 级别监控] + + E --> I[帧数据聚合] + F --> I + G --> J[实时帧监控] + H --> J + + I --> K[慢帧/冻结帧统计] + J --> L[精确帧分析] + + K --> M[Activity 事务指标] + L --> N[Span 性能数据] + + M --> O[性能数据上报] + N --> O +``` + +## 1. 帧率监控架构 + +### 1.1 双重监控策略 + +Sentry 提供两种帧率监控方式,根据配置自动选择: + +```java +// Performance V1: Activity 级别监控 +public final class ActivityFramesTracker { + private @Nullable FrameMetricsAggregator frameMetricsAggregator = null; + + @VisibleForTesting + public boolean isFrameMetricsAggregatorAvailable() { + return frameMetricsAggregator != null + && options.isEnableFramesTracking() + && !options.isEnablePerformanceV2(); // V2 启用时禁用 V1 + } +} + +// Performance V2: Span 级别监控 +public class SpanFrameMetricsCollector implements IPerformanceContinuousCollector { + private final boolean enabled; + + public SpanFrameMetricsCollector( + final @NotNull SentryAndroidOptions options, + final @NotNull SentryFrameMetricsCollector frameMetricsCollector) { + + enabled = options.isEnablePerformanceV2() && options.isEnableFramesTracking(); + } +} +``` + +### 1.2 帧率监控判断标准 + +```java +public final class SentryFrameMetricsCollector { + private static final long frozenFrameThresholdNanos = TimeUnit.MILLISECONDS.toNanos(700); + + public static boolean isFrozen(long frameDuration) { + return frameDuration > frozenFrameThresholdNanos; // > 700ms = 冻结帧 + } + + public static boolean isSlow(long frameDuration, final long expectedFrameDuration) { + return frameDuration > expectedFrameDuration; // > 期望帧时间 = 慢帧 + } +} +``` + +## 2. Performance V1: Activity 级别监控 + +### 2.1 FrameMetricsAggregator 集成 + +```java +public final class ActivityFramesTracker { + public void addActivity(final @NotNull Activity activity) { + try (final @NotNull ISentryLifecycleToken ignored = lock.acquire()) { + if (!isFrameMetricsAggregatorAvailable()) { + return; + } + + // 在 UI 线程上安全执行 + runSafelyOnUiThread(() -> frameMetricsAggregator.add(activity), "FrameMetricsAggregator.add"); + + // 记录开始时的帧数快照 + snapshotFrameCountsAtStart(activity); + } + } + + private void snapshotFrameCountsAtStart(final @NotNull Activity activity) { + FrameCounts frameCounts = calculateCurrentFrameCounts(); + if (frameCounts != null) { + frameCountAtStartSnapshots.put(activity, frameCounts); + } + } +} +``` + +### 2.2 帧数据计算 + +```java +private @Nullable FrameCounts calculateCurrentFrameCounts() { + if (!isFrameMetricsAggregatorAvailable() || frameMetricsAggregator == null) { + return null; + } + + final @Nullable SparseIntArray[] framesRates = frameMetricsAggregator.getMetrics(); + + int totalFrames = 0; + int slowFrames = 0; + int frozenFrames = 0; + + if (framesRates != null && framesRates.length > 0) { + final SparseIntArray totalIndexArray = framesRates[FrameMetricsAggregator.TOTAL_INDEX]; + if (totalIndexArray != null) { + for (int i = 0; i < totalIndexArray.size(); i++) { + int frameTime = totalIndexArray.keyAt(i); // 帧时间 (ms) + int numFrames = totalIndexArray.valueAt(i); // 该时间的帧数 + + totalFrames += numFrames; + + // 硬编码阈值,与官方 Android 文档和 Frame Metrics API 一致 + if (frameTime > 700) { + frozenFrames += numFrames; // 冻结帧:> 700ms + } else if (frameTime > 16) { + slowFrames += numFrames; // 慢帧:> 16ms (60fps) + } + } + } + } + + return new FrameCounts(totalFrames, slowFrames, frozenFrames); +} +``` + +### 2.3 Activity 事务集成 + +```java +// 在 ActivityLifecycleIntegration 中集成 +transactionOptions.setTransactionFinishedCallback((finishingTransaction) -> { + @Nullable Activity unwrappedActivity = weakActivity.get(); + if (unwrappedActivity != null) { + // Activity 结束时收集帧指标 + activityFramesTracker.setMetrics(unwrappedActivity, finishingTransaction.getEventId()); + } +}); + +// 在 PerformanceAndroidEventProcessor 中处理 +if (eventId != null && spanContext != null && spanContext.getOperation().contentEquals(UI_LOAD_OP)) { + final Map framesMetrics = + activityFramesTracker.takeMetrics(eventId); + if (framesMetrics != null) { + transaction.getMeasurements().putAll(framesMetrics); + } +} +``` + +## 3. Performance V2: Span 级别监控 + +### 3.1 SentryFrameMetricsCollector 核心机制 + +```java +@SuppressLint("NewApi") +public SentryFrameMetricsCollector(final @NotNull Context context, ...) { + // 获取 Choreographer 实例(必须在主线程) + new Handler(Looper.getMainLooper()).post(() -> { + try { + choreographer = Choreographer.getInstance(); + } catch (Throwable e) { + logger.log(SentryLevel.ERROR, + "Error retrieving Choreographer instance. Slow and frozen frames will not be reported.", e); + } + }); + + // 通过反射获取 Choreographer 的私有字段 + try { + choreographerLastFrameTimeField = Choreographer.class.getDeclaredField("mLastFrameTimeNanos"); + choreographerLastFrameTimeField.setAccessible(true); + } catch (NoSuchFieldException e) { + logger.log(SentryLevel.ERROR, "Unable to get the frame timestamp from the choreographer: ", e); + } +} +``` + +### 3.2 实时帧监控回调 + +```java +frameMetricsAvailableListener = (window, frameMetrics, dropCountSinceLastInvocation) -> { + final long now = System.nanoTime(); + + // 获取屏幕刷新率 + final float refreshRate = buildInfoProvider.getSdkInfoVersion() >= Build.VERSION_CODES.R + ? window.getContext().getDisplay().getRefreshRate() + : window.getWindowManager().getDefaultDisplay().getRefreshRate(); + + final long expectedFrameDuration = (long) (oneSecondInNanos / refreshRate); + + // 计算 CPU 主线程帧时间(不包括 GPU 时间) + final long cpuDuration = getFrameCpuDuration(frameMetrics); + + // 计算帧延迟 + final long delayNanos = Math.max(0, cpuDuration - expectedFrameDuration); + + // 获取帧开始时间戳 + long startTime = getFrameStartTimestamp(frameMetrics); + if (startTime < 0) { + startTime = now - cpuDuration; // 回退策略 + } + + // 调整帧开始时间,确保在上一帧结束之后 + startTime = Math.max(startTime, lastFrameEndNanos); + + // 避免重复帧 + if (startTime == lastFrameStartNanos) { + return; + } + + lastFrameStartNanos = startTime; + lastFrameEndNanos = startTime + cpuDuration; + + // 判断慢帧和冻结帧 + // 减去 1fps 以避免大多数帧被误判为慢帧 + final boolean isSlow = isSlow(cpuDuration, (long) ((float) oneSecondInNanos / (refreshRate - 1.0f))); + final boolean isFrozen = isSlow && isFrozen(cpuDuration); + + // 通知所有监听器 + for (FrameMetricsCollectorListener l : listenerMap.values()) { + l.onFrameMetricCollected(startTime, lastFrameEndNanos, cpuDuration, delayNanos, isSlow, isFrozen, refreshRate); + } +}; +``` + +### 3.3 CPU 帧时间计算 + +```java +@RequiresApi(api = Build.VERSION_CODES.N) +private long getFrameCpuDuration(final @NotNull FrameMetrics frameMetrics) { + // 受 JankStats 库启发 + // 只计算主线程 CPU 时间,不包括 GPU 渲染时间 + return frameMetrics.getMetric(FrameMetrics.UNKNOWN_DELAY_DURATION) + + frameMetrics.getMetric(FrameMetrics.INPUT_HANDLING_DURATION) + + frameMetrics.getMetric(FrameMetrics.ANIMATION_DURATION) + + frameMetrics.getMetric(FrameMetrics.LAYOUT_MEASURE_DURATION) + + frameMetrics.getMetric(FrameMetrics.DRAW_DURATION) + + frameMetrics.getMetric(FrameMetrics.SYNC_DURATION); +} +``` + +## 4. Span 级别帧监控 + +### 4.1 SpanFrameMetricsCollector 工作流程 + +```java +public class SpanFrameMetricsCollector implements IPerformanceContinuousCollector { + // 最大帧缓存数量:30秒 × 120fps = 3600帧 + private static final int MAX_FRAMES_COUNT = 3600; + + // 所有运行中的 Span,按开始时间排序 + private final @NotNull SortedSet runningSpans = new TreeSet<>(...); + + // 所有收集的帧,按结束时间排序(并发安全) + private final @NotNull ConcurrentSkipListSet frames = new ConcurrentSkipListSet<>(); + + @Override + public void onSpanStarted(final @NotNull ISpan span) { + if (!enabled || span instanceof NoOpSpan || span instanceof NoOpTransaction) { + return; + } + + try (final @NotNull ISentryLifecycleToken ignored = lock.acquire()) { + runningSpans.add(span); + + // 第一个 Span 开始时启动帧收集 + if (listenerId == null) { + listenerId = frameMetricsCollector.startCollection(this); + } + } + } +} +``` + +### 4.2 帧数据收集和处理 + +```java +@Override +public void onFrameMetricCollected( + long frameStartNanos, long frameEndNanos, long durationNanos, long delayNanos, + boolean isSlow, boolean isFrozen, float refreshRate) { + + // 缓存已满,跳过新帧(Span 结束时会清理缓存) + if (frames.size() > MAX_FRAMES_COUNT) { + return; + } + + final long expectedFrameDurationNanos = (long) ((double) ONE_SECOND_NANOS / (double) refreshRate); + lastKnownFrameDurationNanos = expectedFrameDurationNanos; + + // 只存储慢帧和冻结帧以节省内存 + if (isSlow || isFrozen) { + frames.add(new Frame( + frameStartNanos, frameEndNanos, durationNanos, delayNanos, + isSlow, isFrozen, expectedFrameDurationNanos + )); + } +} +``` + +### 4.3 Span 结束时的指标计算 + +```java +private void captureFrameMetrics(final @NotNull ISpan span) { + final @NotNull SentryNanotimeDate spanStartTime = toNanoTime(span.getStartDate()); + final @NotNull SentryNanotimeDate spanEndTime = toNanoTime(span.getFinishDate()); + + // 查找 Span 时间范围内的所有帧 + final @NotNull SortedSet spanFrames = frames.subSet( + new Frame(spanStartTime), new Frame(spanEndTime) + ); + + final @NotNull SentryFrameMetrics frameMetrics = new SentryFrameMetrics(); + + // 处理每一帧 + for (final @NotNull Frame frame : spanFrames) { + // 计算帧与 Span 的重叠时间 + final long frameStartClampedNanos = Math.max(frame.getStartTimestampNanos(), spanStartNanos); + final long frameEndClampedNanos = Math.min(frame.getEndTimestampNanos(), spanEndNanos); + + if (frameEndClampedNanos > frameStartClampedNanos) { + final long overlapNanos = frameEndClampedNanos - frameStartClampedNanos; + final long frameDurationNanos = frame.getEndTimestampNanos() - frame.getStartTimestampNanos(); + + // 按重叠比例计算延迟 + final long frameDelayNanos = (long) ((double) frame.getDelayNanos() * overlapNanos / frameDurationNanos); + + frameMetrics.addFrame(overlapNanos, frameDelayNanos, frame.isSlow(), frame.isFrozen()); + } + } + + // 计算总帧数(包括插值) + final long spanDurationNanos = spanEndNanos - spanStartNanos; + final long frameDurationNanos = lastKnownFrameDurationNanos; + + int totalFrameCount = frameMetrics.getSlowFrozenFrameCount(); + + // 处理待渲染帧延迟 + final long nextScheduledFrameNanos = frameMetricsCollector.getLastKnownFrameStartTimeNanos(); + if (nextScheduledFrameNanos != -1) { + totalFrameCount += addPendingFrameDelay(frameMetrics, frameDurationNanos, spanEndNanos, nextScheduledFrameNanos); + totalFrameCount += interpolateFrameCount(frameMetrics, frameDurationNanos, spanDurationNanos); + } + + // 设置 Span 数据 + final long frameDelayNanos = frameMetrics.getSlowFrameDelayNanos() + frameMetrics.getFrozenFrameDelayNanos(); + final double frameDelayInSeconds = frameDelayNanos / 1e9d; + + span.setData(SpanDataConvention.FRAMES_TOTAL, totalFrameCount); + span.setData(SpanDataConvention.FRAMES_SLOW, frameMetrics.getSlowFrameCount()); + span.setData(SpanDataConvention.FRAMES_FROZEN, frameMetrics.getFrozenFrameCount()); + span.setData(SpanDataConvention.FRAMES_DELAY, frameDelayInSeconds); + + // 如果是事务,同时设置测量值 + if (span instanceof ITransaction) { + span.setMeasurement(MeasurementValue.KEY_FRAMES_TOTAL, totalFrameCount); + span.setMeasurement(MeasurementValue.KEY_FRAMES_SLOW, frameMetrics.getSlowFrameCount()); + span.setMeasurement(MeasurementValue.KEY_FRAMES_FROZEN, frameMetrics.getFrozenFrameCount()); + span.setMeasurement(MeasurementValue.KEY_FRAMES_DELAY, frameDelayInSeconds); + } +} +``` + +## 5. 帧插值和补偿机制 + +### 5.1 帧数插值 + +```java +private static int interpolateFrameCount( + final @NotNull SentryFrameMetrics frameMetrics, + final long frameDurationNanos, + final long spanDurationNanos) { + + // 如果 Android 上没有内容变化,系统也不会提供新的帧指标 + // 为了匹配 Span 持续时间和总帧数,我们基于 Span 持续时间简单插值总帧数 + // 这样数据更加合理,也与 Cocoa SDK 的输出匹配 + final long frameMetricsDurationNanos = frameMetrics.getTotalDurationNanos(); + final long nonRenderedDuration = spanDurationNanos - frameMetricsDurationNanos; + + if (nonRenderedDuration > 0) { + return (int) Math.ceil((double) nonRenderedDuration / frameDurationNanos); + } + return 0; +} +``` + +### 5.2 待渲染帧延迟处理 + +```java +private int addPendingFrameDelay( + final @NotNull SentryFrameMetrics frameMetrics, + final long frameDurationNanos, + final long spanEndNanos, + final long nextScheduledFrameNanos) { + + // 如果 Span 结束时有待渲染的帧,计算其延迟 + if (nextScheduledFrameNanos < spanEndNanos) { + final long pendingFrameDelayNanos = spanEndNanos - nextScheduledFrameNanos; + + if (pendingFrameDelayNanos > frameDurationNanos) { + // 待渲染帧被认为是冻结帧 + frameMetrics.addFrame(frameDurationNanos, pendingFrameDelayNanos - frameDurationNanos, false, true); + return 1; + } + } + return 0; +} +``` + +## 6. 性能优化策略 + +### 6.1 内存管理 + +```java +public class SpanFrameMetricsCollector { + @Override + public void onSpanFinished(final @NotNull ISpan span) { + // ... 处理帧指标 + + try (final @NotNull ISentryLifecycleToken ignored = lock.acquire()) { + if (runningSpans.isEmpty()) { + clear(); // 所有 Span 结束时清理 + } else { + // 只移除旧的/无关的帧 + final @NotNull ISpan oldestSpan = runningSpans.first(); + frames.headSet(new Frame(toNanoTime(oldestSpan.getStartDate()))).clear(); + } + } + } + + @Override + public void clear() { + try (final @NotNull ISentryLifecycleToken ignored = lock.acquire()) { + if (listenerId != null) { + frameMetricsCollector.stopCollection(listenerId); + listenerId = null; + } + frames.clear(); + runningSpans.clear(); + } + } +} +``` + +### 6.2 线程安全 + +```java +public final class ActivityFramesTracker { + private void runSafelyOnUiThread(final Runnable runnable, final String tag) { + try { + if (AndroidThreadChecker.getInstance().isMainThread()) { + runnable.run(); + } else { + handler.post(() -> { + try { + runnable.run(); + } catch (Throwable ignored) { + if (tag != null) { + options.getLogger().log(SentryLevel.WARNING, "Failed to execute " + tag); + } + } + }); + } + } catch (Throwable ignored) { + if (tag != null) { + options.getLogger().log(SentryLevel.WARNING, "Failed to execute " + tag); + } + } + } +} +``` + +### 6.3 缓存限制 + +```java +@Override +public void onFrameMetricCollected(...) { + // 缓存已满,跳过添加新帧 + // Span 结束时会修剪缓存 + if (frames.size() > MAX_FRAMES_COUNT) { + return; + } + + // 只存储慢帧和冻结帧以节省内存 + if (isSlow || isFrozen) { + frames.add(new Frame(...)); + } +} +``` + +## 7. 配置和集成 + +### 7.1 关键配置选项 + +```java +// 启用帧率跟踪 +options.setEnableFramesTracking(true); + +// 选择性能监控版本 +options.setEnablePerformanceV2(true); // 推荐使用 V2 + +// 启用跟踪 +options.setTracingEnabled(true); +options.setTracesSampleRate(1.0); + +// Activity 生命周期跟踪(V1 需要) +options.setEnableActivityLifecycleTracingAutoFinish(true); +``` + +### 7.2 自动集成 + +```java +// ActivityLifecycleIntegration 自动注册 ActivityFramesTracker +public void register(final @NotNull IScopes scopes, final @NotNull SentryOptions options) { + // ... + if (performanceEnabled) { + activityFramesTracker.addActivity(activity); + } +} + +// SentryFrameMetricsCollector 自动注册 Activity 生命周期回调 +public SentryFrameMetricsCollector(final @NotNull Context context, ...) { + if (appContext instanceof Application) { + ((Application) appContext).registerActivityLifecycleCallbacks(this); + } +} +``` + +## 8. 指标含义和阈值 + +### 8.1 帧分类标准 + +| 帧类型 | 阈值 | 说明 | +|--------|------|------| +| **正常帧** | ≤ 16ms (60fps) | 流畅的用户体验 | +| **慢帧** | > 16ms 且 ≤ 700ms | 轻微卡顿,用户可感知 | +| **冻结帧** | > 700ms | 严重卡顿,用户体验差 | + +### 8.2 性能指标 + +```java +// 关键测量值 +public static final String KEY_FRAMES_TOTAL = "frames_total"; // 总帧数 +public static final String KEY_FRAMES_SLOW = "frames_slow"; // 慢帧数 +public static final String KEY_FRAMES_FROZEN = "frames_frozen"; // 冻结帧数 +public static final String KEY_FRAMES_DELAY = "frames_delay"; // 帧延迟(秒) + +// Span 数据 +public static final String FRAMES_TOTAL = "frames.total"; +public static final String FRAMES_SLOW = "frames.slow"; +public static final String FRAMES_FROZEN = "frames.frozen"; +public static final String FRAMES_DELAY = "frames.delay"; +``` + +### 8.3 刷新率适配 + +```java +// 动态获取屏幕刷新率 +final float refreshRate = buildInfoProvider.getSdkInfoVersion() >= Build.VERSION_CODES.R + ? window.getContext().getDisplay().getRefreshRate() + : window.getWindowManager().getDefaultDisplay().getRefreshRate(); + +final long expectedFrameDuration = (long) (oneSecondInNanos / refreshRate); + +// 适配不同刷新率的慢帧判断 +// 减去 1fps 以避免大多数帧被误判为慢帧 +final boolean isSlow = isSlow(cpuDuration, (long) ((float) oneSecondInNanos / (refreshRate - 1.0f))); +``` + +## 9. 最佳实践 + +### 9.1 推荐配置 + +```java +// 生产环境推荐配置 +SentryAndroid.init(this, options -> { + options.setDsn("YOUR_DSN"); + + // 启用 Performance V2(推荐) + options.setEnablePerformanceV2(true); + options.setEnableFramesTracking(true); + + // 合理的采样率 + options.setTracesSampleRate(0.1); // 10% 采样 + + // 启用 Activity 跟踪 + options.setEnableActivityLifecycleTracingAutoFinish(true); + options.setIdleTimeout(3000L); +}); +``` + +### 9.2 性能优化建议 + +1. **减少主线程工作**:避免在主线程执行耗时操作 +2. **优化布局层次**:减少 Layout 和 Draw 时间 +3. **合理使用动画**:避免复杂动画导致的帧丢失 +4. **监控关键页面**:重点关注用户交互频繁的页面 + +### 9.3 指标解读 + +- **慢帧率 < 5%**:用户体验良好 +- **慢帧率 5-10%**:轻微卡顿,需要优化 +- **慢帧率 > 10%**:明显卡顿,需要重点优化 +- **冻结帧 > 0**:严重问题,需要立即修复 + +## 10. 故障排查 + +### 10.1 常见问题 + +**Q: 帧率数据不准确?** +A: 检查是否启用了正确的性能监控版本,确保 Android 版本 >= N (API 24) + +**Q: 没有帧率数据?** +A: 确认 `isEnableFramesTracking()` 已启用,且 AndroidX 库可用 + +**Q: 慢帧数过多?** +A: 检查主线程是否有耗时操作,使用 Systrace 或 GPU 渲染分析工具 + +**Q: Performance V2 vs V1 选择?** +A: 推荐使用 V2,提供更精确的 Span 级别监控 + +### 10.2 调试技巧 + +```java +// 启用详细日志 +options.setDebug(true); +options.setLogger(new SystemOutLogger()); + +// 检查帧率跟踪状态 +ActivityFramesTracker tracker = activityFramesTracker; +System.out.println("Frame tracking available: " + tracker.isFrameMetricsAggregatorAvailable()); + +// 监控帧指标回调 +frameMetricsCollector.startCollection((frameStartNanos, frameEndNanos, durationNanos, + delayNanos, isSlow, isFrozen, refreshRate) -> { + System.out.println(String.format("Frame: duration=%dms, slow=%b, frozen=%b", + TimeUnit.NANOSECONDS.toMillis(durationNanos), isSlow, isFrozen)); +}); +``` + +## 总结 + +Sentry 的 UI 卡顿监控机制通过精密的帧率分析和智能的性能指标收集,为开发者提供了全面的 UI 性能洞察: + +### 🎯 **核心优势** + +1. **双重监控策略**: Performance V1 和 V2 满足不同需求 +2. **精确帧分析**: 基于 Android FrameMetrics API 的准确测量 +3. **实时监控**: Choreographer 集成提供实时帧数据 +4. **智能分类**: 自动区分正常帧、慢帧和冻结帧 +5. **内存优化**: 只存储异常帧,减少内存占用 + +### 🔍 **监控范围** + +- **Activity 级别**: 整个 Activity 生命周期的帧性能 +- **Span 级别**: 精确到具体操作的帧分析 +- **多维度指标**: 总帧数、慢帧数、冻结帧数、帧延迟 +- **刷新率适配**: 支持不同刷新率设备的准确监控 + +### 📊 **数据价值** + +通过这套监控机制,开发者可以: +- 识别 UI 性能瓶颈 +- 量化用户体验质量 +- 监控版本间的性能变化 +- 优化关键用户交互路径 + +这套机制确保了在各种设备和场景下,都能准确捕获和分析 UI 卡顿问题,为性能优化提供可靠的数据支撑。 \ No newline at end of file From 531e9a253bd591c1aa0523ef088d239a076b833d Mon Sep 17 00:00:00 2001 From: funcodingdev Date: Wed, 28 May 2025 10:55:28 +0800 Subject: [PATCH 2/4] feat: update docs --- {cursor => aidocs}/README.md | 0 aidocs/sentry-crash-monitoring.md | 1450 +++++++++++++++++ .../sentry-init-quick-reference.md | 0 .../sentry-initialization-details.md | 0 .../sentry-initialization-flow.md | 72 +- .../sentry-network-monitoring.md | 0 .../sentry-profiling-analysis.md | 0 {cursor => aidocs}/sentry-replay-analysis.md | 0 .../sentry-session-management.md | 0 .../sentry-startup-monitoring.md | 0 .../sentry-ui-jank-monitoring.md | 0 cursor/sentry-crash-monitoring.md | 688 -------- 12 files changed, 1486 insertions(+), 724 deletions(-) rename {cursor => aidocs}/README.md (100%) create mode 100644 aidocs/sentry-crash-monitoring.md rename {cursor => aidocs}/sentry-init-quick-reference.md (100%) rename {cursor => aidocs}/sentry-initialization-details.md (100%) rename {cursor => aidocs}/sentry-initialization-flow.md (88%) rename {cursor => aidocs}/sentry-network-monitoring.md (100%) rename {cursor => aidocs}/sentry-profiling-analysis.md (100%) rename {cursor => aidocs}/sentry-replay-analysis.md (100%) rename {cursor => aidocs}/sentry-session-management.md (100%) rename {cursor => aidocs}/sentry-startup-monitoring.md (100%) rename {cursor => aidocs}/sentry-ui-jank-monitoring.md (100%) delete mode 100644 cursor/sentry-crash-monitoring.md diff --git a/cursor/README.md b/aidocs/README.md similarity index 100% rename from cursor/README.md rename to aidocs/README.md diff --git a/aidocs/sentry-crash-monitoring.md b/aidocs/sentry-crash-monitoring.md new file mode 100644 index 0000000000..e87a173862 --- /dev/null +++ b/aidocs/sentry-crash-monitoring.md @@ -0,0 +1,1450 @@ +# Sentry 崩溃监控机制深度分析 + +本文档详细分析了 Sentry Java SDK 如何监控和处理各种类型的崩溃,包括 Java 异常、Android ANR、Native 崩溃等。 + +## 📁 核心代码文件结构 + +``` +sentry-java/ +├── sentry/src/main/java/io/sentry/ +│ ├── UncaughtExceptionHandlerIntegration.java # Java 未捕获异常处理 +│ ├── DeduplicateMultithreadedEventProcessor.java # 多线程崩溃去重 +│ ├── DuplicateEventDetectionEventProcessor.java # 重复事件检测 +│ ├── MainEventProcessor.java # 主事件处理器 +│ ├── SentryClient.java # 事件捕获客户端 +│ └── cache/EnvelopeCache.java # 离线缓存机制 +├── sentry-android-core/src/main/java/io/sentry/android/core/ +│ ├── ANRWatchDog.java # ANR 实时检测 +│ ├── AnrIntegration.java # ANR 集成(旧版) +│ ├── AnrV2Integration.java # ANR 集成(新版) +│ ├── NdkIntegration.java # NDK 集成 +│ ├── ApplicationNotResponding.java # ANR 异常类 +│ └── cache/AndroidEnvelopeCache.java # Android 缓存实现 +└── sentry-android-ndk/src/main/java/io/sentry/android/ndk/ + ├── SentryNdk.java # NDK 初始化 + └── NdkScopeObserver.java # 作用域同步 +``` + +## 🎯 崩溃监控概览 + +Sentry 通过多层监控机制来捕获不同类型的崩溃: + +```mermaid +graph TD + A[应用运行] --> B{崩溃类型} + B --> C[Java 未捕获异常] + B --> D[Android ANR] + B --> E[Native 崩溃] + B --> F[启动崩溃] + + C --> G[UncaughtExceptionHandler] + D --> H[ANRWatchDog + AnrV2Integration] + E --> I[NDK Signal Handler] + F --> J[启动时间检测] + + G --> K[事件处理流程] + H --> K + I --> K + J --> K + + K --> L[事件处理器链] + L --> M[传输层] + M --> N[Sentry 服务器] +``` + +## 1. Java 未捕获异常监控 + +### 1.1 核心机制:UncaughtExceptionHandlerIntegration + +**文件路径**: `sentry/src/main/java/io/sentry/UncaughtExceptionHandlerIntegration.java` + +```java +public final class UncaughtExceptionHandlerIntegration + implements Integration, Thread.UncaughtExceptionHandler, Closeable { + + private @Nullable Thread.UncaughtExceptionHandler defaultExceptionHandler; + private @Nullable IScopes scopes; + private @Nullable SentryOptions options; + private final @NotNull UncaughtExceptionHandler threadAdapter; + + @Override + public void register(final @NotNull IScopes scopes, final @NotNull SentryOptions options) { + this.scopes = Objects.requireNonNull(scopes, "Scopes are required"); + this.options = Objects.requireNonNull(options, "SentryOptions is required"); + + if (this.options.isEnableUncaughtExceptionHandler()) { + // 保存原有的异常处理器 + final Thread.UncaughtExceptionHandler currentHandler = + threadAdapter.getDefaultUncaughtExceptionHandler(); + + if (currentHandler != null) { + if (currentHandler instanceof UncaughtExceptionHandlerIntegration) { + // 避免重复注册 + final UncaughtExceptionHandlerIntegration currentHandlerIntegration = + (UncaughtExceptionHandlerIntegration) currentHandler; + defaultExceptionHandler = currentHandlerIntegration.defaultExceptionHandler; + } else { + defaultExceptionHandler = currentHandler; + } + } + + // 设置 Sentry 的异常处理器 + threadAdapter.setDefaultUncaughtExceptionHandler(this); + } + } +} +``` + +### 1.2 异常捕获流程 + +```mermaid +sequenceDiagram + participant App as 应用线程 + participant Handler as UncaughtExceptionHandler + participant Sentry as Sentry SDK + participant Transport as 传输层 + participant Original as 原始处理器 + + App->>Handler: 未捕获异常发生 + Handler->>Sentry: 创建 SentryEvent + Note over Handler: 设置 level = FATAL + Note over Handler: 标记 handled = false + + Handler->>Sentry: captureEvent(event, hint) + Sentry->>Transport: 发送事件 + + Note over Handler: 等待事件刷新到磁盘 + Handler->>Handler: waitFlush() + + alt 有原始处理器 + Handler->>Original: 调用原始处理器 + else 无原始处理器 + Handler->>App: printStackTrace() + end +``` + +### 1.3 关键实现细节 + +#### 异常包装机制 +**文件路径**: `sentry/src/main/java/io/sentry/UncaughtExceptionHandlerIntegration.java:185-193` + +```java +@TestOnly +@NotNull +static Throwable getUnhandledThrowable( + final @NotNull Thread thread, final @NotNull Throwable thrown) { + final Mechanism mechanism = new Mechanism(); + mechanism.setHandled(false); // 标记为未处理 + mechanism.setType("UncaughtExceptionHandler"); + + return new ExceptionMechanismException(mechanism, thrown, thread); +} +``` + +#### 阻塞刷新机制 +**文件路径**: `sentry/src/main/java/io/sentry/UncaughtExceptionHandlerIntegration.java:95-140` + +```java +@Override +public void uncaughtException(Thread thread, Throwable thrown) { + if (options != null && scopes != null) { + options.getLogger().log(SentryLevel.INFO, "Uncaught exception received."); + + try { + final UncaughtExceptionHint exceptionHint = + new UncaughtExceptionHint(options.getFlushTimeoutMillis(), options.getLogger()); + final Throwable throwable = getUnhandledThrowable(thread, thrown); + final SentryEvent event = new SentryEvent(throwable); + event.setLevel(SentryLevel.FATAL); + + // 处理事务状态 + final ITransaction transaction = scopes.getTransaction(); + if (transaction == null && event.getEventId() != null) { + exceptionHint.setFlushable(event.getEventId()); + } + final Hint hint = HintUtils.createWithTypeCheckHint(exceptionHint); + + final @NotNull SentryId sentryId = scopes.captureEvent(event, hint); + final boolean isEventDropped = sentryId.equals(SentryId.EMPTY_ID); + final EventDropReason eventDropReason = HintUtils.getEventDropReason(hint); + + // 特殊处理多线程去重情况 + if (!isEventDropped || EventDropReason.MULTITHREADED_DEDUPLICATION.equals(eventDropReason)) { + // 阻塞等待事件刷新到磁盘 + if (!exceptionHint.waitFlush()) { + options.getLogger().log(SentryLevel.WARNING, + "Timed out waiting to flush event to disk before crashing. Event: %s", + event.getEventId()); + } + } + } catch (Throwable e) { + options.getLogger().log(SentryLevel.ERROR, + "Error sending uncaught exception to Sentry.", e); + } + + // 调用原始异常处理器 + if (defaultExceptionHandler != null) { + options.getLogger().log(SentryLevel.INFO, "Invoking inner uncaught exception handler."); + defaultExceptionHandler.uncaughtException(thread, thrown); + } else { + if (options.isPrintUncaughtStackTrace()) { + thrown.printStackTrace(); + } + } + } +} +``` + +## 2. Android ANR 监控 + +### 2.1 双重 ANR 检测机制 + +Sentry Android 提供两种 ANR 检测方式: + +#### 方式一:ANRWatchDog (实时检测) +**文件路径**: `sentry-android-core/src/main/java/io/sentry/android/core/ANRWatchDog.java` + +```java +final class ANRWatchDog extends Thread { + private final boolean reportInDebug; + private final ANRListener anrListener; + private final MainLooperHandler uiHandler; + private final ICurrentDateProvider timeProvider; + private long pollingIntervalMs; // 默认 500ms + private final long timeoutIntervalMillis; // 默认 5000ms + private volatile long lastKnownActiveUiTimestampMs = 0; + private final AtomicBoolean reported = new AtomicBoolean(false); + private final @NotNull Context context; + + @SuppressWarnings("UnnecessaryLambda") + private final Runnable ticker = () -> { + lastKnownActiveUiTimestampMs = timeProvider.getCurrentTimeMillis(); + reported.set(false); + }; + + @Override + public void run() { + // 初始化时假设没有 ANR + ticker.run(); + + while (!isInterrupted()) { + uiHandler.post(ticker); // 向主线程发送心跳 + + try { + Thread.sleep(pollingIntervalMs); + } catch (InterruptedException e) { + // 处理中断 + Thread.currentThread().interrupt(); + return; + } + + final long unresponsiveDurationMs = + timeProvider.getCurrentTimeMillis() - lastKnownActiveUiTimestampMs; + + // 检查是否超过 ANR 阈值 + if (unresponsiveDurationMs > timeoutIntervalMillis) { + // 调试模式下的特殊处理 + if (!reportInDebug && (Debug.isDebuggerConnected() || Debug.waitingForDebugger())) { + logger.log(SentryLevel.DEBUG, + "An ANR was detected but ignored because the debugger is connected."); + reported.set(true); + continue; + } + + // 验证系统确实处于 ANR 状态并报告 + if (isProcessNotResponding() && reported.compareAndSet(false, true)) { + final String message = + "Application Not Responding for at least " + timeoutIntervalMillis + " ms."; + final ApplicationNotResponding error = + new ApplicationNotResponding(message, uiHandler.getThread()); + anrListener.onAppNotResponding(error); + } + } + } + } + + // 通过 ActivityManager 验证进程是否真的处于 ANR 状态 + private boolean isProcessNotResponding() { + final ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); + if (am != null) { + try { + List processesInErrorState = + am.getProcessesInErrorState(); + if (processesInErrorState != null) { + for (ActivityManager.ProcessErrorStateInfo item : processesInErrorState) { + if (item.condition == ActivityManager.ProcessErrorStateInfo.NOT_RESPONDING) { + return true; + } + } + } + return false; + } catch (Throwable e) { + logger.log(SentryLevel.ERROR, "Error getting ActivityManager#getProcessesInErrorState.", e); + } + } + return true; // 如果无法获取 ActivityManager,假设是 ANR + } +} +``` + +#### 方式二:AnrV2Integration (系统级检测) +**文件路径**: `sentry-android-core/src/main/java/io/sentry/android/core/AnrV2Integration.java` + +```java +public final class AnrV2Integration implements Integration { + + @SuppressLint("NewApi") // Android 11+ 才支持 + @Override + public void register(@NotNull IScopes scopes, @NotNull SentryOptions options) { + this.options = (SentryAndroidOptions) options; + + if (this.options.isAnrEnabled()) { + try { + options.getExecutorService() + .submit(new AnrProcessor(context, scopes, this.options, dateProvider)); + } catch (Throwable e) { + options.getLogger().log(SentryLevel.DEBUG, "Failed to start AnrProcessor.", e); + } + } + } + + static class AnrProcessor implements Runnable { + @SuppressLint("NewApi") + @Override + public void run() { + final ActivityManager activityManager = + (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); + + // 获取历史进程退出信息 + final List applicationExitInfoList = + activityManager.getHistoricalProcessExitReasons(null, 0, 0); + + for (ApplicationExitInfo exitInfo : applicationExitInfoList) { + if (exitInfo.getReason() == ApplicationExitInfo.REASON_ANR) { + reportAsSentryEvent(exitInfo, shouldEnrich); + } + } + } + } + + private void reportAsSentryEvent(final @NotNull ApplicationExitInfo exitInfo, final boolean shouldEnrich) { + final long anrTimestamp = exitInfo.getTimestamp(); + final boolean isBackground = + exitInfo.getImportance() != ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND; + + // 解析系统提供的线程转储 + final ParseResult result = parseThreadDump(exitInfo, isBackground); + if (result.type == ParseResult.Type.NO_DUMP) { + options.getLogger().log(SentryLevel.WARNING, + "Not reporting ANR event as there was no thread dump for the ANR %s", + exitInfo.toString()); + return; + } + + final AnrV2Hint anrHint = new AnrV2Hint( + options.getFlushTimeoutMillis(), options.getLogger(), + anrTimestamp, shouldEnrich, isBackground); + final Hint hint = HintUtils.createWithTypeCheckHint(anrHint); + + final SentryEvent event = new SentryEvent(); + if (result.type == ParseResult.Type.DUMP) { + event.setThreads(result.threads); // 设置线程信息 + if (result.debugImages != null) { + final DebugMeta debugMeta = new DebugMeta(); + debugMeta.setImages(result.debugImages); + event.setDebugMeta(debugMeta); + } + } + + event.setLevel(SentryLevel.FATAL); + event.setTimestamp(DateUtils.getDateTime(anrTimestamp)); + + // 可选:附加原始线程转储 + if (options.isAttachAnrThreadDump() && result.dump != null) { + hint.setThreadDump(Attachment.fromThreadDump(result.dump)); + } + + final @NotNull SentryId sentryId = scopes.captureEvent(event, hint); + final boolean isEventDropped = sentryId.equals(SentryId.EMPTY_ID); + if (!isEventDropped) { + // 阻塞等待事件刷新到磁盘 + if (!anrHint.waitFlush()) { + options.getLogger().log(SentryLevel.WARNING, + "Timed out waiting to flush ANR event to disk. Event: %s", + event.getEventId()); + } + } + } +} +``` + +### 2.2 ANR 检测流程 + +```mermaid +sequenceDiagram + participant MainThread as 主线程 + participant WatchDog as ANRWatchDog + participant System as Android系统 + participant AnrV2 as AnrV2Integration + participant Sentry as Sentry SDK + + Note over WatchDog: 实时检测方式 + loop 每500ms + WatchDog->>MainThread: post(ticker) + alt 主线程响应 + MainThread->>WatchDog: 更新心跳时间 + else 主线程无响应 > 5s + WatchDog->>Sentry: 报告ANR事件 + end + end + + Note over System: 系统级检测方式 + System->>System: 检测到ANR + System->>System: 生成ApplicationExitInfo + + Note over AnrV2: 应用重启后 + AnrV2->>System: 查询ApplicationExitInfo + AnrV2->>AnrV2: 解析线程转储 + AnrV2->>Sentry: 报告历史ANR事件 +``` + +### 2.3 线程转储解析 + +```java +public class ThreadDumpParser { + public @NotNull List parse(final @NotNull Lines lines) { + final List threads = new ArrayList<>(); + + while (lines.hasNext()) { + final String line = lines.next(); + + if (THREAD_STATE_RE.matcher(line).matches()) { + final SentryThread thread = parseThread(lines, line, isBackground); + if (thread != null) { + threads.add(thread); + } + } + } + + return threads; + } + + private @Nullable SentryThread parseThread(Lines lines, String threadLine, boolean isBackground) { + // 解析线程名称、状态、ID等信息 + final String threadName = extractThreadName(threadLine); + final String threadState = extractThreadState(threadLine); + + final SentryThread sentryThread = new SentryThread(); + sentryThread.setName(threadName); + sentryThread.setState(threadState); + + if (threadName != null && threadName.equals("main")) { + sentryThread.setMain(true); + sentryThread.setCrashed(true); // ANR中主线程被标记为崩溃 + } + + // 解析堆栈跟踪 + final SentryStackTrace stackTrace = parseStacktrace(lines, sentryThread); + sentryThread.setStacktrace(stackTrace); + + return sentryThread; + } +} +``` + +## 3. Native 崩溃监控 + +### 3.1 NDK 集成架构 + +**文件路径**: `sentry-android-ndk/src/main/java/io/sentry/android/ndk/SentryNdk.java` + +```java +@ApiStatus.Internal +public final class SentryNdk { + + private static final @NotNull CountDownLatch loadLibraryLatch = new CountDownLatch(1); + + static { + // 在后台线程加载 Native 库 + new Thread(() -> { + try { + io.sentry.ndk.SentryNdk.loadNativeLibraries(); + } catch (Throwable t) { + // 忽略加载错误,init() 时会再次抛出异常 + } finally { + loadLibraryLatch.countDown(); + } + }, "SentryNdkLoadLibs").start(); + } + + /** + * 初始化 NDK 集成 + */ + public static void init(@NotNull final SentryAndroidOptions options) { + SentryNdkUtil.addPackage(options.getSdkVersion()); + + try { + // 等待 Native 库加载完成(最多 2 秒) + if (loadLibraryLatch.await(2000, TimeUnit.MILLISECONDS)) { + final @NotNull NdkOptions ndkOptions = new NdkOptions( + Objects.requireNonNull(options.getDsn(), "DSN is required for sentry-ndk"), + options.isDebug(), + Objects.requireNonNull(options.getOutboxPath(), "outbox path is required for sentry-ndk"), + options.getRelease(), + options.getEnvironment(), + options.getDist(), + options.getMaxBreadcrumbs(), + options.getNativeSdkName() + ); + + // 配置 Native 异常处理策略 + final int handlerStrategy = options.getNdkHandlerStrategy(); + if (handlerStrategy == NdkHandlerStrategy.SENTRY_HANDLER_STRATEGY_DEFAULT.getValue()) { + ndkOptions.setNdkHandlerStrategy( + io.sentry.ndk.NdkHandlerStrategy.SENTRY_HANDLER_STRATEGY_DEFAULT); + } else if (handlerStrategy == NdkHandlerStrategy.SENTRY_HANDLER_STRATEGY_CHAIN_AT_START.getValue()) { + ndkOptions.setNdkHandlerStrategy( + io.sentry.ndk.NdkHandlerStrategy.SENTRY_HANDLER_STRATEGY_CHAIN_AT_START); + } + + // 配置性能监控采样率 + final @Nullable Double tracesSampleRate = options.getTracesSampleRate(); + if (tracesSampleRate == null) { + ndkOptions.setTracesSampleRate(0.0f); + } else { + ndkOptions.setTracesSampleRate(tracesSampleRate.floatValue()); + } + + // 初始化 Native SDK + io.sentry.ndk.SentryNdk.init(ndkOptions); + + // 启用作用域同步(将 Java 层的用户信息、标签等同步到 Native 层) + if (options.isEnableScopeSync()) { + options.addScopeObserver(new NdkScopeObserver(options)); + } + + // 设置调试镜像加载器 + options.setDebugImagesLoader(new DebugImagesLoader(options, new NativeModuleListLoader())); + } else { + throw new IllegalStateException("Timeout waiting for Sentry NDK library to load"); + } + } catch (InterruptedException e) { + throw new IllegalStateException("Thread interrupted while waiting for NDK libs to be loaded", e); + } + } + + /** 关闭 NDK 集成 */ + public static void close() { + try { + if (loadLibraryLatch.await(2000, TimeUnit.MILLISECONDS)) { + io.sentry.ndk.SentryNdk.close(); + } else { + throw new IllegalStateException("Timeout waiting for Sentry NDK library to load"); + } + } catch (InterruptedException e) { + throw new IllegalStateException("Thread interrupted while waiting for NDK libs to be loaded", e); + } + } +} +``` + +### 3.2 Native 崩溃处理流程 + +```mermaid +sequenceDiagram + participant App as Native代码 + participant Signal as 信号处理器 + participant NDK as Sentry NDK + participant Java as Java层 + participant Disk as 磁盘缓存 + + App->>Signal: Native崩溃 (SIGSEGV等) + Signal->>NDK: 捕获信号 + NDK->>NDK: 收集崩溃信息 + Note over NDK: - 寄存器状态
- 堆栈跟踪
- 内存映射 + + NDK->>Disk: 写入崩溃文件 + Note over Disk: 避免在信号处理器中
进行复杂操作 + + Note over Java: 应用重启后 + Java->>Disk: 检查崩溃标记文件 + Java->>Java: 读取崩溃信息 + Java->>Java: 创建SentryEvent + Java->>Java: 发送到Sentry服务器 +``` + +### 3.3 作用域同步机制 + +**文件路径**: `sentry-android-ndk/src/main/java/io/sentry/android/ndk/NdkScopeObserver.java` + +```java +@ApiStatus.Internal +public final class NdkScopeObserver extends ScopeObserverAdapter { + + private final @NotNull SentryOptions options; + private final @NotNull INativeScope nativeScope; + + public NdkScopeObserver(final @NotNull SentryOptions options) { + this(options, new NativeScope()); + } + + @Override + public void setUser(final @Nullable User user) { + try { + options.getExecutorService().submit(() -> { + if (user == null) { + // 移除用户信息 + nativeScope.removeUser(); + } else { + nativeScope.setUser( + user.getId(), + user.getEmail(), + user.getIpAddress(), + user.getUsername() + ); + } + }); + } catch (Throwable e) { + options.getLogger().log(SentryLevel.ERROR, e, "Scope sync setUser has an error."); + } + } + + @Override + public void addBreadcrumb(final @NotNull Breadcrumb crumb) { + try { + options.getExecutorService().submit(() -> { + String level = null; + if (crumb.getLevel() != null) { + level = crumb.getLevel().name().toLowerCase(Locale.ROOT); + } + final String timestamp = DateUtils.getTimestamp(crumb.getTimestamp()); + + String data = null; + try { + final Map dataRef = crumb.getData(); + if (!dataRef.isEmpty()) { + data = options.getSerializer().serialize(dataRef); + } + } catch (Throwable e) { + options.getLogger().log(SentryLevel.ERROR, e, "Breadcrumb data is not serializable."); + } + + nativeScope.addBreadcrumb( + level, + crumb.getMessage(), + crumb.getCategory(), + crumb.getType(), + timestamp, + data); + }); + } catch (Throwable e) { + options.getLogger().log(SentryLevel.ERROR, e, "Scope sync addBreadcrumb has an error."); + } + } + + @Override + public void setTag(final @NotNull String key, final @NotNull String value) { + try { + options.getExecutorService().submit(() -> nativeScope.setTag(key, value)); + } catch (Throwable e) { + options.getLogger().log(SentryLevel.ERROR, e, "Scope sync setTag(%s) has an error.", key); + } + } + + @Override + public void removeTag(final @NotNull String key) { + try { + options.getExecutorService().submit(() -> nativeScope.removeTag(key)); + } catch (Throwable e) { + options.getLogger().log(SentryLevel.ERROR, e, "Scope sync removeTag(%s) has an error.", key); + } + } + + @Override + public void setExtra(final @NotNull String key, final @NotNull String value) { + try { + options.getExecutorService().submit(() -> nativeScope.setExtra(key, value)); + } catch (Throwable e) { + options.getLogger().log(SentryLevel.ERROR, e, "Scope sync setExtra(%s) has an error.", key); + } + } + + @Override + public void removeExtra(final @NotNull String key) { + try { + options.getExecutorService().submit(() -> nativeScope.removeExtra(key)); + } catch (Throwable e) { + options.getLogger().log(SentryLevel.ERROR, e, "Scope sync removeExtra(%s) has an error.", key); + } + } + + @Override + public void setTrace(@Nullable SpanContext spanContext, @NotNull IScope scope) { + if (spanContext == null) { + return; + } + + try { + options.getExecutorService().submit(() -> + nativeScope.setTrace( + spanContext.getTraceId().toString(), + spanContext.getSpanId().toString())); + } catch (Throwable e) { + options.getLogger().log(SentryLevel.ERROR, e, "Scope sync setTrace failed."); + } + } +} +``` + +## 4. 启动崩溃检测 + +### 4.1 启动崩溃定义 + +启动崩溃是指在应用启动后的短时间内(默认2秒)发生的崩溃: + +**文件路径**: `sentry-android-core/src/main/java/io/sentry/android/core/cache/AndroidEnvelopeCache.java` + +```java +@ApiStatus.Internal +public final class AndroidEnvelopeCache extends EnvelopeCache { + + public static final String LAST_ANR_REPORT = "last_anr_report"; + private final @NotNull ICurrentDateProvider currentDateProvider; + + public AndroidEnvelopeCache(final @NotNull SentryAndroidOptions options) { + this(options, AndroidCurrentDateProvider.getInstance()); + } + + @Override + public void store(@NotNull SentryEnvelope envelope, @NotNull Hint hint) { + super.store(envelope, hint); + + final SentryAndroidOptions options = (SentryAndroidOptions) this.options; + final TimeSpan sdkInitTimeSpan = AppStartMetrics.getInstance().getSdkInitTimeSpan(); + + // 检测启动崩溃 + if (HintUtils.hasType(hint, UncaughtExceptionHandlerIntegration.UncaughtExceptionHint.class) + && sdkInitTimeSpan.hasStarted()) { + + long timeSinceSdkInit = + currentDateProvider.getCurrentTimeMillis() - sdkInitTimeSpan.getStartUptimeMs(); + + if (timeSinceSdkInit <= options.getStartupCrashDurationThresholdMillis()) { + options.getLogger().log(DEBUG, + "Startup Crash detected %d milliseconds after SDK init. Writing a startup crash marker file to disk.", + timeSinceSdkInit); + writeStartupCrashMarkerFile(); + } + } + + // 处理 ANR V2 事件 + HintUtils.runIfHasType(hint, AnrV2Integration.AnrV2Hint.class, (anrHint) -> { + final @Nullable Long timestamp = anrHint.timestamp(); + options.getLogger().log(SentryLevel.DEBUG, + "Writing last reported ANR marker with timestamp %d", timestamp); + writeLastReportedAnrMarker(timestamp); + }); + } + + private void writeStartupCrashMarkerFile() { + // 使用 outbox 路径,确保与混合 SDK 兼容 + final String outboxPath = options.getOutboxPath(); + if (outboxPath == null) { + options.getLogger().log(DEBUG, + "Outbox path is null, the startup crash marker file will not be written"); + return; + } + + final File crashMarkerFile = new File(outboxPath, STARTUP_CRASH_MARKER_FILE); + try { + crashMarkerFile.createNewFile(); + } catch (Throwable e) { + options.getLogger().log(ERROR, + "Error writing the startup crash marker file to the disk", e); + } + } + + public static boolean hasStartupCrashMarker(final @NotNull SentryOptions options) { + final String outboxPath = options.getOutboxPath(); + if (outboxPath == null) { + options.getLogger().log(DEBUG, + "Outbox path is null, the startup crash marker file does not exist"); + return false; + } + + final File crashMarkerFile = new File(outboxPath, STARTUP_CRASH_MARKER_FILE); + try { + final boolean exists = crashMarkerFile.exists(); + if (exists) { + if (!crashMarkerFile.delete()) { + options.getLogger().log(ERROR, + "Failed to delete the startup crash marker file. %s.", + crashMarkerFile.getAbsolutePath()); + } + } + return exists; + } catch (Throwable e) { + options.getLogger().log(ERROR, + "Error reading/deleting the startup crash marker file on the disk", e); + } + return false; + } +} +``` + +### 4.2 启动崩溃处理 + +```mermaid +sequenceDiagram + participant App as 应用启动 + participant SDK as Sentry SDK + participant Cache as 缓存系统 + participant Sender as 发送器 + + App->>SDK: Sentry.init() + SDK->>SDK: 记录初始化时间 + + Note over App: 应用崩溃 (< 2s) + App->>SDK: UncaughtException + SDK->>Cache: 检查启动时间 + Cache->>Cache: 写入启动崩溃标记 + + Note over App: 应用重启 + App->>SDK: Sentry.init() + SDK->>Cache: 检查启动崩溃标记 + Cache->>Sender: 阻塞发送启动崩溃事件 + Note over Sender: 等待最多5秒确保发送成功 + Sender->>SDK: 发送完成 + SDK->>App: 初始化完成 +``` + +## 5. 事件处理流程 + +### 5.1 事件捕获统一入口 + +```java +public class SentryClient implements ISentryClient { + @Override + public @NotNull SentryId captureEvent( + @NotNull SentryEvent event, + @Nullable IScope scope, + @Nullable Hint hint) { + + // 1. 验证和预处理 + if (shouldApplyScopeData(event, hint)) { + event = applyScope(event, scope, hint); + } + + // 2. 事件处理器链 + event = processEvent(event, hint, options.getEventProcessors()); + if (scope != null) { + event = processEvent(event, hint, scope.getEventProcessors()); + } + + // 3. beforeSend 回调 + event = executeBeforeSend(event, hint); + + // 4. 构建信封并发送 + final SentryEnvelope envelope = buildEnvelope(event, attachments, session, traceContext); + return sendEnvelope(envelope, hint); + } +} +``` + +### 5.2 事件处理器链 + +```mermaid +graph LR + A[原始事件] --> B[MainEventProcessor] + B --> C[DuplicateEventDetectionEventProcessor] + C --> D[DeduplicateMultithreadedEventProcessor] + D --> E[SentryRuntimeEventProcessor] + E --> F[自定义EventProcessor] + F --> G[beforeSend回调] + G --> H[传输层] + + style B fill:#e1f5fe + style C fill:#f3e5f5 + style D fill:#fff3e0 + style E fill:#e8f5e8 +``` + +#### 关键处理器说明 + +**MainEventProcessor**: 添加设备信息、线程信息、上下文数据 +**文件路径**: `sentry/src/main/java/io/sentry/MainEventProcessor.java` + +```java +@ApiStatus.Internal +public final class MainEventProcessor implements EventProcessor, Closeable { + + private final @NotNull SentryOptions options; + private final @NotNull SentryThreadFactory sentryThreadFactory; + private final @NotNull SentryExceptionFactory sentryExceptionFactory; + private volatile @Nullable HostnameCache hostnameCache = null; + + @Override + public @NotNull SentryEvent process(final @NotNull SentryEvent event, final @NotNull Hint hint) { + setCommons(event); // 设置通用信息(release、environment、dist等) + setExceptions(event); // 处理异常信息 + setDebugMeta(event); // 设置调试元数据 + setModules(event); // 设置模块信息 + + if (shouldApplyScopeData(event, hint)) { + processNonCachedEvent(event); // 处理非缓存事件 + setThreads(event, hint); // 设置线程信息 + } + + return event; + } + + private boolean shouldApplyScopeData(final @NotNull SentryBaseEvent event, final @NotNull Hint hint) { + if (HintUtils.shouldApplyScopeData(hint)) { + return true; + } else { + options.getLogger().log(SentryLevel.DEBUG, + "Event was cached so not applying data relevant to the current app execution/version: %s", + event.getEventId()); + return false; + } + } + + private void setThreads(final @NotNull SentryEvent event, final @NotNull Hint hint) { + if (event.getThreads() != null) { + // 标记崩溃线程 + if (HintUtils.hasType(hint, UncaughtExceptionHandlerIntegration.UncaughtExceptionHint.class)) { + final SentryException unhandledException = event.getUnhandledException(); + if (unhandledException != null) { + final Long threadId = unhandledException.getThreadId(); + if (threadId != null) { + for (SentryThread thread : event.getThreads()) { + if (threadId.equals(thread.getId())) { + thread.setCrashed(true); + break; + } + } + } + } + } + } else if (options.isAttachThreads() && !isCachedHint(hint)) { + // 附加线程信息 + event.setThreads(sentryThreadFactory.getAllThreads()); + } else if (options.isAttachStacktrace() && !isCachedHint(hint) && event.getThrowable() == null) { + // 只附加当前线程堆栈 + event.setThreads(sentryThreadFactory.getCurrentThread()); + } + } + + @Override + public @Nullable Long getOrder() { + return 0L; // 最高优先级 + } +} +``` + +**DeduplicateMultithreadedEventProcessor**: 多线程崩溃去重 +**文件路径**: `sentry/src/main/java/io/sentry/DeduplicateMultithreadedEventProcessor.java` + +```java +/** + * 去重同时发生的相同类型崩溃事件的事件处理器。 + * 这种情况可能发生在 OutOfMemory 错误或 CursorWindowAllocationException 等 + * 与内存不足时分配内存相关的错误中。 + */ +public final class DeduplicateMultithreadedEventProcessor implements EventProcessor { + + private final @NotNull Map processedEvents = + Collections.synchronizedMap(new HashMap<>()); + private final @NotNull SentryOptions options; + + @Override + public @Nullable SentryEvent process(final @NotNull SentryEvent event, final @NotNull Hint hint) { + // 只处理来自 UncaughtExceptionHandler 的崩溃 + if (!HintUtils.hasType(hint, UncaughtExceptionHandlerIntegration.UncaughtExceptionHint.class)) { + return event; + } + + final SentryException exception = event.getUnhandledException(); + if (exception == null) return event; + + final String type = exception.getType(); + if (type == null) return event; + + final Long currentEventTid = exception.getThreadId(); + if (currentEventTid == null) return event; + + // 检查是否已经处理过相同类型的异常 + final Long tid = processedEvents.get(type); + if (tid != null && !tid.equals(currentEventTid)) { + options.getLogger().log(SentryLevel.INFO, + "Event %s has been dropped due to multi-threaded deduplication", + event.getEventId()); + // 丢弃重复的多线程崩溃 + HintUtils.setEventDropReason(hint, EventDropReason.MULTITHREADED_DEDUPLICATION); + return null; + } + + processedEvents.put(type, currentEventTid); + return event; + } + + @Override + public @Nullable Long getOrder() { + return 7000L; // 较低优先级,在主要处理器之后执行 + } +} +``` + +## 6. 传输和持久化 + +### 6.1 离线缓存机制 + +**文件路径**: `sentry/src/main/java/io/sentry/cache/EnvelopeCache.java` + +```java +@Open +@ApiStatus.Internal +public class EnvelopeCache extends CacheStrategy implements IEnvelopeCache { + + public static final String SUFFIX_ENVELOPE_FILE = ".envelope"; + public static final String CRASH_MARKER_FILE = "last_crash"; + public static final String NATIVE_CRASH_MARKER_FILE = ".sentry-native/" + CRASH_MARKER_FILE; + public static final String STARTUP_CRASH_MARKER_FILE = "startup_crash"; + + private final CountDownLatch previousSessionLatch; + private final @NotNull Map fileNameMap = new WeakHashMap<>(); + protected final @NotNull AutoClosableReentrantLock cacheLock = new AutoClosableReentrantLock(); + + @Override + public void store(final @NotNull SentryEnvelope envelope, final @NotNull Hint hint) { + Objects.requireNonNull(envelope, "Envelope is required."); + + rotateCacheIfNeeded(allEnvelopeFiles()); + + // 处理会话相关逻辑 + handleSessionHints(hint, envelope); + + final File envelopeFile = getEnvelopeFile(envelope); + if (envelopeFile.exists()) { + options.getLogger().log(WARNING, + "Not adding Envelope to offline storage because it already exists: %s", + envelopeFile.getAbsolutePath()); + return; + } else { + options.getLogger().log(DEBUG, + "Adding Envelope to offline storage: %s", envelopeFile.getAbsolutePath()); + } + + // 将信封写入磁盘 + writeEnvelopeToDisk(envelopeFile, envelope); + + // 写入崩溃标记文件(当即将崩溃时) + if (HintUtils.hasType(hint, UncaughtExceptionHandlerIntegration.UncaughtExceptionHint.class)) { + writeCrashMarkerFile(); + } + } + + private void writeCrashMarkerFile() { + final File crashMarkerFile = new File(options.getCacheDirPath(), CRASH_MARKER_FILE); + try (final OutputStream outputStream = new FileOutputStream(crashMarkerFile)) { + final String timestamp = DateUtils.getTimestamp(DateUtils.getCurrentDateTime()); + outputStream.write(timestamp.getBytes(UTF_8)); + outputStream.flush(); + } catch (Throwable e) { + options.getLogger().log(ERROR, "Error writing the crash marker file to the disk", e); + } + } + + private void handleSessionHints(final @NotNull Hint hint, final @NotNull SentryEnvelope envelope) { + final File currentSessionFile = getCurrentSessionFile(directory.getAbsolutePath()); + final File previousSessionFile = getPreviousSessionFile(directory.getAbsolutePath()); + + if (HintUtils.hasType(hint, SessionEnd.class)) { + if (!currentSessionFile.delete()) { + options.getLogger().log(WARNING, "Current envelope doesn't exist."); + } + } + + if (HintUtils.hasType(hint, AbnormalExit.class)) { + tryEndPreviousSession(hint); + } + + if (HintUtils.hasType(hint, SessionStart.class)) { + // 处理会话开始逻辑 + updateCurrentSession(currentSessionFile, envelope); + + // 检查崩溃标记文件 + boolean crashedLastRun = checkCrashMarkers(); + SentryCrashLastRunState.getInstance().setCrashedLastRun(crashedLastRun); + + flushPreviousSession(); + } + } + + private boolean checkCrashMarkers() { + boolean crashedLastRun = false; + + // 检查 Native 崩溃标记 + final File nativeCrashMarkerFile = new File(options.getCacheDirPath(), NATIVE_CRASH_MARKER_FILE); + if (nativeCrashMarkerFile.exists()) { + crashedLastRun = true; + } + + // 检查 Java 崩溃标记 + if (!crashedLastRun) { + final File javaCrashMarkerFile = new File(options.getCacheDirPath(), CRASH_MARKER_FILE); + if (javaCrashMarkerFile.exists()) { + options.getLogger().log(INFO, "Crash marker file exists, crashedLastRun will return true."); + crashedLastRun = true; + if (!javaCrashMarkerFile.delete()) { + options.getLogger().log(ERROR, + "Failed to delete the crash marker file. %s.", + javaCrashMarkerFile.getAbsolutePath()); + } + } + } + + return crashedLastRun; + } +} +``` + +### 6.2 崩溃恢复流程 + +```mermaid +sequenceDiagram + participant App as 应用重启 + participant Cache as 缓存系统 + participant Sender as 发送器 + participant Server as Sentry服务器 + + App->>Cache: 检查崩溃标记文件 + alt 发现崩溃标记 + Cache->>Cache: 设置 crashedLastRun = true + Cache->>Cache: 删除崩溃标记文件 + end + + Cache->>Sender: 扫描缓存目录 + Sender->>Sender: 读取未发送的信封 + + loop 每个缓存的信封 + Sender->>Server: 发送信封 + alt 发送成功 + Sender->>Cache: 删除缓存文件 + else 发送失败 + Sender->>Cache: 保留缓存文件 + end + end +``` + +## 7. 性能优化策略 + +### 7.1 异步处理 + +```java +public class UncaughtExceptionHandlerIntegration { + @Override + public void uncaughtException(Thread thread, Throwable thrown) { + try { + // 创建事件(同步,快速) + final SentryEvent event = new SentryEvent(throwable); + event.setLevel(SentryLevel.FATAL); + + // 捕获事件(可能异步) + final @NotNull SentryId sentryId = scopes.captureEvent(event, hint); + + // 阻塞等待刷新(确保数据不丢失) + if (!exceptionHint.waitFlush()) { + options.getLogger().log(SentryLevel.WARNING, + "Timed out waiting to flush event to disk before crashing."); + } + } catch (Throwable e) { + // 异常处理不能影响原始崩溃流程 + options.getLogger().log(SentryLevel.ERROR, + "Error sending uncaught exception to Sentry.", e); + } + + // 调用原始异常处理器 + if (defaultExceptionHandler != null) { + defaultExceptionHandler.uncaughtException(thread, thrown); + } + } +} +``` + +### 7.2 内存管理 + +```java +public class SentryClient { + private @NotNull SentryId sendEnvelope(@NotNull SentryEnvelope envelope, @Nullable Hint hint) { + try { + // 发送前清理 hint 中的大对象 + hint.clear(); + + if (hint == null) { + transport.send(envelope); + } else { + transport.send(envelope, hint); + } + } finally { + // 确保资源释放 + if (hint != null) { + hint.clear(); + } + } + } +} +``` + +## 8. 配置和最佳实践 + +### 8.1 关键配置选项 + +```java +// 基础配置 +options.setDsn("YOUR_DSN"); +options.setEnvironment("production"); +options.setRelease("1.0.0"); + +// 崩溃相关配置 +options.setEnableUncaughtExceptionHandler(true); // 启用未捕获异常处理 +options.setFlushTimeoutMillis(5000); // 崩溃时刷新超时 +options.setPrintUncaughtStackTrace(false); // 生产环境关闭堆栈打印 + +// Android 特定配置 +options.setAnrEnabled(true); // 启用ANR检测 +options.setAnrTimeoutIntervalMillis(5000); // ANR超时时间 +options.setAnrReportInDebug(false); // 调试时不报告ANR + +// Native 崩溃配置 +options.setEnableNdk(true); // 启用NDK崩溃捕获 +options.setEnableScopeSync(true); // 启用作用域同步 + +// 启动崩溃配置 +options.setStartupCrashDurationThresholdMillis(2000); // 启动崩溃时间阈值 +options.setStartupCrashFlushTimeoutMillis(5000); // 启动崩溃刷新超时 +``` + +### 8.2 最佳实践 + +#### ✅ 推荐做法 +- **尽早初始化**: 在 Application.onCreate() 中初始化 Sentry +- **合理配置超时**: 根据应用特点调整刷新超时时间 +- **启用所有监控**: 同时启用 Java、ANR、Native 崩溃监控 +- **测试崩溃恢复**: 验证崩溃后的数据恢复机制 + +#### ❌ 避免做法 +- **在崩溃处理器中执行复杂操作**: 可能导致二次崩溃 +- **忽略启动崩溃**: 启动崩溃往往影响更严重 +- **禁用缓存机制**: 可能导致崩溃数据丢失 +- **过短的超时时间**: 可能导致崩溃数据未完全写入 + +## 9. 故障排查 + +### 9.1 常见问题 + +**Q: 崩溃事件没有上报?** +A: 检查网络连接、DSN配置、是否启用了相应的集成 + +**Q: ANR 检测不准确?** +A: 调整 ANR 超时时间,检查是否在调试模式下运行 + +**Q: Native 崩溃信息不完整?** +A: 确保符号文件已上传,检查 NDK 版本兼容性 + +**Q: 启动崩溃处理缓慢?** +A: 调整启动崩溃刷新超时时间,优化网络配置 + +### 9.2 调试技巧 + +```java +// 启用详细日志 +options.setDebug(true); +options.setLogger(new SystemOutLogger()); + +// 监控崩溃处理状态 +options.setBeforeEnvelopeCallback((envelope, hint) -> { + System.out.println("Sending envelope: " + envelope.getHeader().getEventId()); +}); + +// 检查崩溃标记文件 +File crashMarker = new File(options.getCacheDirPath(), "sentry-java-crash-marker"); +if (crashMarker.exists()) { + System.out.println("Previous session crashed"); +} +``` + +## 9. 集成初始化流程 + +### 9.1 Android 集成初始化 + +**文件路径**: `sentry-android-core/src/main/java/io/sentry/android/core/AndroidOptionsInitializer.java` + +```java +static void installDefaultIntegrations( + final @NotNull Context context, + final @NotNull SentryAndroidOptions options, + final @NotNull BuildInfoProvider buildInfoProvider, + final @NotNull LoadClass loadClass, + final @NotNull ActivityFramesTracker activityFramesTracker, + final boolean isFragmentAvailable, + final boolean isTimberAvailable, + final boolean isReplayAvailable) { + + // 读取启动崩溃标记,避免重复 I/O 操作 + LazyEvaluator startupCrashMarkerEvaluator = + new LazyEvaluator<>(() -> AndroidEnvelopeCache.hasStartupCrashMarker(options)); + + // 发送缓存的信封(优先处理启动崩溃) + options.addIntegration( + new SendCachedEnvelopeIntegration( + new SendFireAndForgetEnvelopeSender(() -> options.getCacheDirPath()), + startupCrashMarkerEvaluator)); + + // NDK 集成(必须在文件观察器之前) + final Class sentryNdkClass = loadClass.loadClass(SENTRY_NDK_CLASS_NAME, options.getLogger()); + options.addIntegration(new NdkIntegration(sentryNdkClass)); + + // 文件观察器集成 + options.addIntegration(EnvelopeFileObserverIntegration.getOutboxFileObserver()); + + // 发送 outbox 中的缓存信封 + options.addIntegration( + new SendCachedEnvelopeIntegration( + new SendFireAndForgetOutboxSender(() -> options.getOutboxPath()), + startupCrashMarkerEvaluator)); + + // ANR 集成(根据 Android 版本选择) + options.addIntegration(AnrIntegrationFactory.create(context, options.getLogger())); + + // 其他集成... + options.addIntegration(new ActivityLifecycleIntegration(application, buildInfoProvider, activityFramesTracker)); + options.addIntegration(new AppLifecycleIntegration()); + options.addIntegration(new SystemEventsBreadcrumbsIntegration(context)); + options.addIntegration(new AppComponentsBreadcrumbsIntegration(context)); + options.addIntegration(new NetworkBreadcrumbsIntegration(context, buildInfoProvider, options.getLogger())); + options.addIntegration(new TempSensorBreadcrumbsIntegration(context)); + options.addIntegration(new PhoneStateBreadcrumbsIntegration(context)); +} +``` + +### 9.2 启动崩溃处理流程 + +**文件路径**: `sentry-android-core/src/main/java/io/sentry/android/core/SendCachedEnvelopeIntegration.java` + +```java +final class SendCachedEnvelopeIntegration implements Integration { + + private final @NotNull LazyEvaluator startupCrashMarkerEvaluator; + private final AtomicBoolean startupCrashHandled = new AtomicBoolean(false); + + @Override + public void register(@NotNull IScopes scopes, @NotNull SentryOptions options) { + // 提交发送任务到执行器 + final Future future = options.getExecutorService().submit(() -> { + try { + if (sender == null) { + options.getLogger().log(SentryLevel.ERROR, + "SendCachedEnvelopeIntegration factory is null."); + return; + } + sender.send(); + } catch (Throwable e) { + options.getLogger().log(SentryLevel.ERROR, + "Failed trying to send cached events.", e); + } + }); + + // 如果存在启动崩溃标记,阻塞等待发送完成 + if (startupCrashMarkerEvaluator.getValue() && startupCrashHandled.compareAndSet(false, true)) { + options.getLogger().log(SentryLevel.DEBUG, "Startup Crash marker exists, blocking flush."); + try { + future.get(options.getStartupCrashFlushTimeoutMillis(), TimeUnit.MILLISECONDS); + } catch (TimeoutException e) { + options.getLogger().log(SentryLevel.DEBUG, + "Synchronous send timed out, continuing in the background."); + } + } + } +} +``` + +## 10. 性能优化和最佳实践 + +### 10.1 关键性能优化 + +#### 异步处理 +- **后台线程加载**: NDK 库在后台线程加载,避免阻塞主线程 +- **异步作用域同步**: 所有 Native 作用域同步操作都在后台执行 +- **非阻塞事件处理**: 除崩溃场景外,事件处理不阻塞应用 + +#### 内存管理 +- **弱引用映射**: 使用 `WeakHashMap` 存储文件映射,避免内存泄漏 +- **及时清理**: 事件发送后立即清理 Hint 对象 +- **缓存轮转**: 自动清理过期的缓存文件 + +#### 去重机制 +- **多线程去重**: 防止同一类型异常在多个线程同时报告 +- **重复事件检测**: 基于异常对象的弱引用检测重复事件 +- **ANR 去重**: 防止同一 ANR 被多次报告 + +### 10.2 配置最佳实践 + +```java +// 生产环境推荐配置 +SentryAndroid.init(this, options -> { + options.setDsn("YOUR_DSN"); + options.setEnvironment("production"); + options.setRelease("1.0.0"); + + // 崩溃监控配置 + options.setEnableUncaughtExceptionHandler(true); + options.setFlushTimeoutMillis(5000); + options.setPrintUncaughtStackTrace(false); + + // ANR 监控配置 + options.setAnrEnabled(true); + options.setAnrTimeoutIntervalMillis(5000); + options.setAnrReportInDebug(false); + + // Native 崩溃配置 + options.setEnableNdk(true); + options.setEnableScopeSync(true); + + // 启动崩溃配置 + options.setStartupCrashDurationThresholdMillis(2000); + options.setStartupCrashFlushTimeoutMillis(5000); + + // 性能优化配置 + options.setMaxCacheItems(30); + options.setEnableDeduplication(true); + options.setAttachThreads(true); + options.setAttachStacktrace(true); +}); +``` + +## 总结 + +Sentry Java SDK 的崩溃监控机制通过精心设计的多层架构,实现了对各种类型崩溃的全面、可靠监控: + +### 🎯 核心特性 +1. **全面覆盖**: Java异常、Android ANR、Native崩溃、启动崩溃 +2. **可靠传输**: 离线缓存、重试机制、阻塞刷新 +3. **性能优化**: 异步处理、内存管理、智能去重 +4. **易于集成**: 自动注册、合理默认值、灵活配置 + +### 🔧 技术亮点 +- **双重 ANR 检测**: 结合实时检测和系统级检测,确保准确性 +- **智能启动崩溃处理**: 阻塞发送确保关键崩溃数据不丢失 +- **多线程崩溃去重**: 防止内存不足等场景下的重复报告 +- **Native-Java 作用域同步**: 确保 Native 崩溃包含完整上下文 + +### 📊 监控覆盖 +| 崩溃类型 | 检测机制 | 文件路径 | 特殊处理 | +|---------|---------|----------|----------| +| Java 未捕获异常 | UncaughtExceptionHandler | `UncaughtExceptionHandlerIntegration.java` | 阻塞刷新、多线程去重 | +| Android ANR | ANRWatchDog + AnrV2 | `ANRWatchDog.java`, `AnrV2Integration.java` | 双重检测、线程转储解析 | +| Native 崩溃 | NDK Signal Handler | `SentryNdk.java`, `NdkIntegration.java` | 作用域同步、调试镜像 | +| 启动崩溃 | 时间阈值检测 | `AndroidEnvelopeCache.java` | 阻塞发送、优先处理 | + +这套完整的崩溃监控机制确保了在各种异常情况下,崩溃信息都能被准确捕获并可靠地发送到 Sentry 服务器,为开发者提供完整、准确的崩溃分析数据,助力应用质量提升。 \ No newline at end of file diff --git a/cursor/sentry-init-quick-reference.md b/aidocs/sentry-init-quick-reference.md similarity index 100% rename from cursor/sentry-init-quick-reference.md rename to aidocs/sentry-init-quick-reference.md diff --git a/cursor/sentry-initialization-details.md b/aidocs/sentry-initialization-details.md similarity index 100% rename from cursor/sentry-initialization-details.md rename to aidocs/sentry-initialization-details.md diff --git a/cursor/sentry-initialization-flow.md b/aidocs/sentry-initialization-flow.md similarity index 88% rename from cursor/sentry-initialization-flow.md rename to aidocs/sentry-initialization-flow.md index e462bbfedb..22ac0a1eb1 100644 --- a/cursor/sentry-initialization-flow.md +++ b/aidocs/sentry-initialization-flow.md @@ -282,48 +282,48 @@ sequenceDiagram ```mermaid sequenceDiagram - participant Sentry as Sentry - participant Options as SentryOptions - participant External as ExternalOptions - participant Properties as PropertiesProvider - participant Manifest as AndroidManifest - participant Files as 配置文件 - - Note over Sentry: 预初始化配置检查 - Sentry->>Options: isEnableExternalConfiguration() - alt 启用外部配置 - Sentry->>Properties: PropertiesProviderFactory.create() - Properties->>Files: 读取sentry.properties - Properties->>Files: 读取系统属性 - Properties->>Files: 读取环境变量 - Properties-->>External: 配置数据 - Sentry->>External: from(properties, logger) - External-->>Options: 外部配置 - Sentry->>Options: merge(externalOptions) + participant S as Sentry + participant O as SentryOptions + participant E as ExternalOptions + participant P as PropertiesProvider + participant M as AndroidManifest + participant F as ConfigFiles + + Note over S: Pre-initialization Configuration Check + S->>O: isEnableExternalConfiguration() + alt External Configuration Enabled + S->>P: PropertiesProviderFactory.create() + P->>F: Read sentry.properties + P->>F: Read system properties + P->>F: Read environment variables + P-->>E: Configuration data + S->>E: from(properties, logger) + E-->>O: External configuration + S->>O: merge(externalOptions) end - Note over Sentry: Android特定配置加载 - alt Android平台 - Sentry->>Manifest: 读取meta-data - Manifest->>Options: 设置DSN - Manifest->>Options: 设置debug模式 - Manifest->>Options: 设置采样率 - Manifest->>Options: 设置环境信息 - Manifest->>Options: 设置发布版本 + Note over S: Android Specific Configuration Loading + alt Android Platform + S->>M: Read meta-data + M->>O: Set DSN + M->>O: Set debug mode + M->>O: Set sample rate + M->>O: Set environment info + M->>O: Set release version end - Note over Sentry: DSN验证 - Sentry->>Options: getDsn() - alt DSN为空或禁用 - Sentry->>Sentry: close() - Sentry-->>Sentry: return false - else DSN无效 - Sentry-->>Sentry: throw IllegalArgumentException + Note over S: DSN Validation + S->>O: getDsn() + alt DSN Empty or Disabled + S->>S: close() + S-->>S: return false + else DSN Invalid + S-->>S: throw IllegalArgumentException end - Sentry->>Options: retrieveParsedDsn() - Options->>Options: 解析和验证DSN格式 - Options-->>Sentry: 解析完成 + S->>O: retrieveParsedDsn() + O->>O: Parse and validate DSN format + O-->>S: Parse completed ``` ## 关键组件说明 diff --git a/cursor/sentry-network-monitoring.md b/aidocs/sentry-network-monitoring.md similarity index 100% rename from cursor/sentry-network-monitoring.md rename to aidocs/sentry-network-monitoring.md diff --git a/cursor/sentry-profiling-analysis.md b/aidocs/sentry-profiling-analysis.md similarity index 100% rename from cursor/sentry-profiling-analysis.md rename to aidocs/sentry-profiling-analysis.md diff --git a/cursor/sentry-replay-analysis.md b/aidocs/sentry-replay-analysis.md similarity index 100% rename from cursor/sentry-replay-analysis.md rename to aidocs/sentry-replay-analysis.md diff --git a/cursor/sentry-session-management.md b/aidocs/sentry-session-management.md similarity index 100% rename from cursor/sentry-session-management.md rename to aidocs/sentry-session-management.md diff --git a/cursor/sentry-startup-monitoring.md b/aidocs/sentry-startup-monitoring.md similarity index 100% rename from cursor/sentry-startup-monitoring.md rename to aidocs/sentry-startup-monitoring.md diff --git a/cursor/sentry-ui-jank-monitoring.md b/aidocs/sentry-ui-jank-monitoring.md similarity index 100% rename from cursor/sentry-ui-jank-monitoring.md rename to aidocs/sentry-ui-jank-monitoring.md diff --git a/cursor/sentry-crash-monitoring.md b/cursor/sentry-crash-monitoring.md deleted file mode 100644 index bbfa6129e7..0000000000 --- a/cursor/sentry-crash-monitoring.md +++ /dev/null @@ -1,688 +0,0 @@ -# Sentry 崩溃监控机制深度分析 - -本文档详细分析了 Sentry Java SDK 如何监控和处理各种类型的崩溃,包括 Java 异常、Android ANR、Native 崩溃等。 - -## 🎯 崩溃监控概览 - -Sentry 通过多层监控机制来捕获不同类型的崩溃: - -```mermaid -graph TD - A[应用运行] --> B{崩溃类型} - B --> C[Java 未捕获异常] - B --> D[Android ANR] - B --> E[Native 崩溃] - B --> F[启动崩溃] - - C --> G[UncaughtExceptionHandler] - D --> H[ANRWatchDog + AnrV2Integration] - E --> I[NDK Signal Handler] - F --> J[启动时间检测] - - G --> K[事件处理流程] - H --> K - I --> K - J --> K - - K --> L[事件处理器链] - L --> M[传输层] - M --> N[Sentry 服务器] -``` - -## 1. Java 未捕获异常监控 - -### 1.1 核心机制:UncaughtExceptionHandlerIntegration - -```java -public final class UncaughtExceptionHandlerIntegration - implements Integration, Thread.UncaughtExceptionHandler, Closeable { - - private @Nullable Thread.UncaughtExceptionHandler defaultExceptionHandler; - - @Override - public void register(final @NotNull IScopes scopes, final @NotNull SentryOptions options) { - // 保存原有的异常处理器 - final Thread.UncaughtExceptionHandler currentHandler = - threadAdapter.getDefaultUncaughtExceptionHandler(); - - if (currentHandler != null) { - defaultExceptionHandler = currentHandler; - } - - // 设置 Sentry 的异常处理器 - threadAdapter.setDefaultUncaughtExceptionHandler(this); - } -} -``` - -### 1.2 异常捕获流程 - -```mermaid -sequenceDiagram - participant App as 应用线程 - participant Handler as UncaughtExceptionHandler - participant Sentry as Sentry SDK - participant Transport as 传输层 - participant Original as 原始处理器 - - App->>Handler: 未捕获异常发生 - Handler->>Sentry: 创建 SentryEvent - Note over Handler: 设置 level = FATAL - Note over Handler: 标记 handled = false - - Handler->>Sentry: captureEvent(event, hint) - Sentry->>Transport: 发送事件 - - Note over Handler: 等待事件刷新到磁盘 - Handler->>Handler: waitFlush() - - alt 有原始处理器 - Handler->>Original: 调用原始处理器 - else 无原始处理器 - Handler->>App: printStackTrace() - end -``` - -### 1.3 关键实现细节 - -#### 异常包装机制 -```java -static Throwable getUnhandledThrowable( - final @NotNull Thread thread, final @NotNull Throwable thrown) { - final Mechanism mechanism = new Mechanism(); - mechanism.setHandled(false); // 标记为未处理 - mechanism.setType("UncaughtExceptionHandler"); - - return new ExceptionMechanismException(mechanism, thrown, thread); -} -``` - -#### 阻塞刷新机制 -```java -public void uncaughtException(Thread thread, Throwable thrown) { - final UncaughtExceptionHint exceptionHint = - new UncaughtExceptionHint(options.getFlushTimeoutMillis(), options.getLogger()); - - final SentryEvent event = new SentryEvent(throwable); - event.setLevel(SentryLevel.FATAL); - - final @NotNull SentryId sentryId = scopes.captureEvent(event, hint); - - // 阻塞等待事件刷新到磁盘 - if (!exceptionHint.waitFlush()) { - options.getLogger().log(SentryLevel.WARNING, - "Timed out waiting to flush event to disk before crashing."); - } -} -``` - -## 2. Android ANR 监控 - -### 2.1 双重 ANR 检测机制 - -Sentry Android 提供两种 ANR 检测方式: - -#### 方式一:ANRWatchDog (实时检测) -```java -final class ANRWatchDog extends Thread { - private final Runnable ticker = () -> { - lastKnownActiveUiTimestampMs = timeProvider.getCurrentTimeMillis(); - reported.set(false); - }; - - @Override - public void run() { - while (!isInterrupted()) { - uiHandler.post(ticker); // 向主线程发送心跳 - Thread.sleep(pollingIntervalMs); - - final long unresponsiveDurationMs = - timeProvider.getCurrentTimeMillis() - lastKnownActiveUiTimestampMs; - - if (unresponsiveDurationMs > timeoutIntervalMillis) { - if (isProcessNotResponding() && reported.compareAndSet(false, true)) { - final ApplicationNotResponding error = - new ApplicationNotResponding(message, uiHandler.getThread()); - anrListener.onAppNotResponding(error); - } - } - } - } -} -``` - -#### 方式二:AnrV2Integration (系统级检测) -```java -public final class AnrV2Integration implements Integration { - private void reportAsSentryEvent(final @NotNull ApplicationExitInfo exitInfo) { - // 解析系统提供的线程转储 - final ParseResult result = parseThreadDump(exitInfo, isBackground); - - final SentryEvent event = new SentryEvent(); - if (result.type == ParseResult.Type.DUMP) { - event.setThreads(result.threads); // 设置线程信息 - } - - event.setLevel(SentryLevel.FATAL); - event.setTimestamp(DateUtils.getDateTime(exitInfo.getTimestamp())); - - scopes.captureEvent(event, hint); - } -} -``` - -### 2.2 ANR 检测流程 - -```mermaid -sequenceDiagram - participant MainThread as 主线程 - participant WatchDog as ANRWatchDog - participant System as Android系统 - participant AnrV2 as AnrV2Integration - participant Sentry as Sentry SDK - - Note over WatchDog: 实时检测方式 - loop 每500ms - WatchDog->>MainThread: post(ticker) - alt 主线程响应 - MainThread->>WatchDog: 更新心跳时间 - else 主线程无响应 > 5s - WatchDog->>Sentry: 报告ANR事件 - end - end - - Note over System: 系统级检测方式 - System->>System: 检测到ANR - System->>System: 生成ApplicationExitInfo - - Note over AnrV2: 应用重启后 - AnrV2->>System: 查询ApplicationExitInfo - AnrV2->>AnrV2: 解析线程转储 - AnrV2->>Sentry: 报告历史ANR事件 -``` - -### 2.3 线程转储解析 - -```java -public class ThreadDumpParser { - public @NotNull List parse(final @NotNull Lines lines) { - final List threads = new ArrayList<>(); - - while (lines.hasNext()) { - final String line = lines.next(); - - if (THREAD_STATE_RE.matcher(line).matches()) { - final SentryThread thread = parseThread(lines, line, isBackground); - if (thread != null) { - threads.add(thread); - } - } - } - - return threads; - } - - private @Nullable SentryThread parseThread(Lines lines, String threadLine, boolean isBackground) { - // 解析线程名称、状态、ID等信息 - final String threadName = extractThreadName(threadLine); - final String threadState = extractThreadState(threadLine); - - final SentryThread sentryThread = new SentryThread(); - sentryThread.setName(threadName); - sentryThread.setState(threadState); - - if (threadName != null && threadName.equals("main")) { - sentryThread.setMain(true); - sentryThread.setCrashed(true); // ANR中主线程被标记为崩溃 - } - - // 解析堆栈跟踪 - final SentryStackTrace stackTrace = parseStacktrace(lines, sentryThread); - sentryThread.setStacktrace(stackTrace); - - return sentryThread; - } -} -``` - -## 3. Native 崩溃监控 - -### 3.1 NDK 集成架构 - -```java -public final class SentryNdk { - public static void init(@NotNull final SentryAndroidOptions options) { - final @NotNull NdkOptions ndkOptions = new NdkOptions( - options.getDsn(), - options.isDebug(), - options.getOutboxPath(), - options.getRelease(), - options.getEnvironment() - ); - - // 初始化 Native SDK - io.sentry.ndk.SentryNdk.init(ndkOptions); - - // 启用作用域同步 - if (options.isEnableScopeSync()) { - options.addScopeObserver(new NdkScopeObserver(options)); - } - } -} -``` - -### 3.2 Native 崩溃处理流程 - -```mermaid -sequenceDiagram - participant App as Native代码 - participant Signal as 信号处理器 - participant NDK as Sentry NDK - participant Java as Java层 - participant Disk as 磁盘缓存 - - App->>Signal: Native崩溃 (SIGSEGV等) - Signal->>NDK: 捕获信号 - NDK->>NDK: 收集崩溃信息 - Note over NDK: - 寄存器状态
- 堆栈跟踪
- 内存映射 - - NDK->>Disk: 写入崩溃文件 - Note over Disk: 避免在信号处理器中
进行复杂操作 - - Note over Java: 应用重启后 - Java->>Disk: 检查崩溃标记文件 - Java->>Java: 读取崩溃信息 - Java->>Java: 创建SentryEvent - Java->>Java: 发送到Sentry服务器 -``` - -### 3.3 作用域同步机制 - -```java -public class NdkScopeObserver implements IScopeObserver { - @Override - public void setUser(final @Nullable User user) { - try { - options.getExecutorService().submit(() -> { - if (user != null) { - nativeScope.setUser( - user.getId(), - user.getEmail(), - user.getIpAddress(), - user.getUsername() - ); - } else { - nativeScope.removeUser(); - } - }); - } catch (Throwable e) { - options.getLogger().log(SentryLevel.ERROR, e, "Scope sync setUser has an error."); - } - } -} -``` - -## 4. 启动崩溃检测 - -### 4.1 启动崩溃定义 - -启动崩溃是指在应用启动后的短时间内(默认2秒)发生的崩溃: - -```java -public class AndroidEnvelopeCache extends EnvelopeCache { - @Override - public void store(@NotNull SentryEnvelope envelope, @NotNull Hint hint) { - super.store(envelope, hint); - - final TimeSpan sdkInitTimeSpan = AppStartMetrics.getInstance().getSdkInitTimeSpan(); - - if (HintUtils.hasType(hint, UncaughtExceptionHandlerIntegration.UncaughtExceptionHint.class) - && sdkInitTimeSpan.hasStarted()) { - - long timeSinceSdkInit = - currentDateProvider.getCurrentTimeMillis() - sdkInitTimeSpan.getStartUptimeMs(); - - if (timeSinceSdkInit <= options.getStartupCrashDurationThresholdMillis()) { - options.getLogger().log(DEBUG, - "Startup Crash detected %d milliseconds after SDK init.", timeSinceSdkInit); - writeStartupCrashMarkerFile(); - } - } - } -} -``` - -### 4.2 启动崩溃处理 - -```mermaid -sequenceDiagram - participant App as 应用启动 - participant SDK as Sentry SDK - participant Cache as 缓存系统 - participant Sender as 发送器 - - App->>SDK: Sentry.init() - SDK->>SDK: 记录初始化时间 - - Note over App: 应用崩溃 (< 2s) - App->>SDK: UncaughtException - SDK->>Cache: 检查启动时间 - Cache->>Cache: 写入启动崩溃标记 - - Note over App: 应用重启 - App->>SDK: Sentry.init() - SDK->>Cache: 检查启动崩溃标记 - Cache->>Sender: 阻塞发送启动崩溃事件 - Note over Sender: 等待最多5秒确保发送成功 - Sender->>SDK: 发送完成 - SDK->>App: 初始化完成 -``` - -## 5. 事件处理流程 - -### 5.1 事件捕获统一入口 - -```java -public class SentryClient implements ISentryClient { - @Override - public @NotNull SentryId captureEvent( - @NotNull SentryEvent event, - @Nullable IScope scope, - @Nullable Hint hint) { - - // 1. 验证和预处理 - if (shouldApplyScopeData(event, hint)) { - event = applyScope(event, scope, hint); - } - - // 2. 事件处理器链 - event = processEvent(event, hint, options.getEventProcessors()); - if (scope != null) { - event = processEvent(event, hint, scope.getEventProcessors()); - } - - // 3. beforeSend 回调 - event = executeBeforeSend(event, hint); - - // 4. 构建信封并发送 - final SentryEnvelope envelope = buildEnvelope(event, attachments, session, traceContext); - return sendEnvelope(envelope, hint); - } -} -``` - -### 5.2 事件处理器链 - -```mermaid -graph LR - A[原始事件] --> B[MainEventProcessor] - B --> C[DuplicateEventDetectionEventProcessor] - C --> D[DeduplicateMultithreadedEventProcessor] - D --> E[SentryRuntimeEventProcessor] - E --> F[自定义EventProcessor] - F --> G[beforeSend回调] - G --> H[传输层] - - style B fill:#e1f5fe - style C fill:#f3e5f5 - style D fill:#fff3e0 - style E fill:#e8f5e8 -``` - -#### 关键处理器说明 - -**MainEventProcessor**: 添加设备信息、线程信息、上下文数据 -```java -public class MainEventProcessor implements EventProcessor { - @Override - public @NotNull SentryEvent process(@NotNull SentryEvent event, @NotNull Hint hint) { - // 设置设备信息 - setDevice(event); - // 设置操作系统信息 - setOperatingSystem(event); - // 设置运行时信息 - setRuntime(event); - // 设置应用信息 - setApp(event); - // 处理线程信息 - setThreads(event, hint); - - return event; - } -} -``` - -**DeduplicateMultithreadedEventProcessor**: 多线程崩溃去重 -```java -public class DeduplicateMultithreadedEventProcessor implements EventProcessor { - @Override - public @Nullable SentryEvent process(@NotNull SentryEvent event, @NotNull Hint hint) { - // 只处理来自 UncaughtExceptionHandler 的崩溃 - if (!HintUtils.hasType(hint, UncaughtExceptionHandlerIntegration.UncaughtExceptionHint.class)) { - return event; - } - - final SentryException exception = event.getUnhandledException(); - if (exception == null) return event; - - final String type = exception.getType(); - final Long currentEventTid = exception.getThreadId(); - - // 检查是否已经处理过相同类型的异常 - final Long tid = processedEvents.get(type); - if (tid != null && !tid.equals(currentEventTid)) { - // 丢弃重复的多线程崩溃 - HintUtils.setEventDropReason(hint, EventDropReason.MULTITHREADED_DEDUPLICATION); - return null; - } - - processedEvents.put(type, currentEventTid); - return event; - } -} -``` - -## 6. 传输和持久化 - -### 6.1 离线缓存机制 - -```java -public class EnvelopeCache implements IEnvelopeCache { - @Override - public void store(@NotNull SentryEnvelope envelope, @NotNull Hint hint) { - // 写入崩溃标记文件 - if (HintUtils.hasType(hint, UncaughtExceptionHandlerIntegration.UncaughtExceptionHint.class)) { - writeCrashMarkerFile(); - } - - // 将信封写入磁盘 - final File envelopeFile = getEnvelopeFile(envelope); - writeEnvelopeToDisk(envelopeFile, envelope); - } - - private void writeCrashMarkerFile() { - final File crashMarkerFile = new File(options.getCacheDirPath(), CRASH_MARKER_FILE); - try { - crashMarkerFile.createNewFile(); - } catch (IOException e) { - options.getLogger().log(SentryLevel.ERROR, "Failed to create crash marker file.", e); - } - } -} -``` - -### 6.2 崩溃恢复流程 - -```mermaid -sequenceDiagram - participant App as 应用重启 - participant Cache as 缓存系统 - participant Sender as 发送器 - participant Server as Sentry服务器 - - App->>Cache: 检查崩溃标记文件 - alt 发现崩溃标记 - Cache->>Cache: 设置 crashedLastRun = true - Cache->>Cache: 删除崩溃标记文件 - end - - Cache->>Sender: 扫描缓存目录 - Sender->>Sender: 读取未发送的信封 - - loop 每个缓存的信封 - Sender->>Server: 发送信封 - alt 发送成功 - Sender->>Cache: 删除缓存文件 - else 发送失败 - Sender->>Cache: 保留缓存文件 - end - end -``` - -## 7. 性能优化策略 - -### 7.1 异步处理 - -```java -public class UncaughtExceptionHandlerIntegration { - @Override - public void uncaughtException(Thread thread, Throwable thrown) { - try { - // 创建事件(同步,快速) - final SentryEvent event = new SentryEvent(throwable); - event.setLevel(SentryLevel.FATAL); - - // 捕获事件(可能异步) - final @NotNull SentryId sentryId = scopes.captureEvent(event, hint); - - // 阻塞等待刷新(确保数据不丢失) - if (!exceptionHint.waitFlush()) { - options.getLogger().log(SentryLevel.WARNING, - "Timed out waiting to flush event to disk before crashing."); - } - } catch (Throwable e) { - // 异常处理不能影响原始崩溃流程 - options.getLogger().log(SentryLevel.ERROR, - "Error sending uncaught exception to Sentry.", e); - } - - // 调用原始异常处理器 - if (defaultExceptionHandler != null) { - defaultExceptionHandler.uncaughtException(thread, thrown); - } - } -} -``` - -### 7.2 内存管理 - -```java -public class SentryClient { - private @NotNull SentryId sendEnvelope(@NotNull SentryEnvelope envelope, @Nullable Hint hint) { - try { - // 发送前清理 hint 中的大对象 - hint.clear(); - - if (hint == null) { - transport.send(envelope); - } else { - transport.send(envelope, hint); - } - } finally { - // 确保资源释放 - if (hint != null) { - hint.clear(); - } - } - } -} -``` - -## 8. 配置和最佳实践 - -### 8.1 关键配置选项 - -```java -// 基础配置 -options.setDsn("YOUR_DSN"); -options.setEnvironment("production"); -options.setRelease("1.0.0"); - -// 崩溃相关配置 -options.setEnableUncaughtExceptionHandler(true); // 启用未捕获异常处理 -options.setFlushTimeoutMillis(5000); // 崩溃时刷新超时 -options.setPrintUncaughtStackTrace(false); // 生产环境关闭堆栈打印 - -// Android 特定配置 -options.setAnrEnabled(true); // 启用ANR检测 -options.setAnrTimeoutIntervalMillis(5000); // ANR超时时间 -options.setAnrReportInDebug(false); // 调试时不报告ANR - -// Native 崩溃配置 -options.setEnableNdk(true); // 启用NDK崩溃捕获 -options.setEnableScopeSync(true); // 启用作用域同步 - -// 启动崩溃配置 -options.setStartupCrashDurationThresholdMillis(2000); // 启动崩溃时间阈值 -options.setStartupCrashFlushTimeoutMillis(5000); // 启动崩溃刷新超时 -``` - -### 8.2 最佳实践 - -#### ✅ 推荐做法 -- **尽早初始化**: 在 Application.onCreate() 中初始化 Sentry -- **合理配置超时**: 根据应用特点调整刷新超时时间 -- **启用所有监控**: 同时启用 Java、ANR、Native 崩溃监控 -- **测试崩溃恢复**: 验证崩溃后的数据恢复机制 - -#### ❌ 避免做法 -- **在崩溃处理器中执行复杂操作**: 可能导致二次崩溃 -- **忽略启动崩溃**: 启动崩溃往往影响更严重 -- **禁用缓存机制**: 可能导致崩溃数据丢失 -- **过短的超时时间**: 可能导致崩溃数据未完全写入 - -## 9. 故障排查 - -### 9.1 常见问题 - -**Q: 崩溃事件没有上报?** -A: 检查网络连接、DSN配置、是否启用了相应的集成 - -**Q: ANR 检测不准确?** -A: 调整 ANR 超时时间,检查是否在调试模式下运行 - -**Q: Native 崩溃信息不完整?** -A: 确保符号文件已上传,检查 NDK 版本兼容性 - -**Q: 启动崩溃处理缓慢?** -A: 调整启动崩溃刷新超时时间,优化网络配置 - -### 9.2 调试技巧 - -```java -// 启用详细日志 -options.setDebug(true); -options.setLogger(new SystemOutLogger()); - -// 监控崩溃处理状态 -options.setBeforeEnvelopeCallback((envelope, hint) -> { - System.out.println("Sending envelope: " + envelope.getHeader().getEventId()); -}); - -// 检查崩溃标记文件 -File crashMarker = new File(options.getCacheDirPath(), "sentry-java-crash-marker"); -if (crashMarker.exists()) { - System.out.println("Previous session crashed"); -} -``` - -## 总结 - -Sentry 的崩溃监控机制通过多层防护确保了各种类型崩溃的可靠捕获: - -1. **全面覆盖**: Java异常、Android ANR、Native崩溃、启动崩溃 -2. **可靠传输**: 离线缓存、重试机制、阻塞刷新 -3. **性能优化**: 异步处理、内存管理、去重机制 -4. **易于集成**: 自动注册、合理默认值、灵活配置 - -这套机制确保了在各种异常情况下,崩溃信息都能被准确捕获并可靠地发送到 Sentry 服务器,为开发者提供完整的崩溃分析数据。 \ No newline at end of file From 9814ff5ef2c23b65aeb46e4e8f7347494e70b4ea Mon Sep 17 00:00:00 2001 From: funcodingdev Date: Wed, 28 May 2025 11:15:00 +0800 Subject: [PATCH 3/4] feat: update docs --- aidocs/.cursorrules | 37 ++++ aidocs/mermaid/README.md | 138 ++++++++++++++ .../sentry-crash-monitoring-anr-detection.mmd | 25 +++ ...sentry-crash-monitoring-crash-recovery.mmd | 23 +++ ...try-crash-monitoring-exception-capture.mmd | 23 +++ .../sentry-crash-monitoring-native-crash.mmd | 20 ++ .../sentry-crash-monitoring-startup-crash.mmd | 21 ++ ...ntry-init-quick-reference-android-flow.mmd | 12 ++ ...ialization-flow-android-initialization.mmd | 81 ++++++++ ...ry-initialization-flow-client-creation.mmd | 29 +++ ...tialization-flow-configuration-loading.mmd | 43 +++++ ...nitialization-flow-core-initialization.mmd | 108 +++++++++++ ...lization-flow-integration-registration.mmd | 38 ++++ ...ry-startup-monitoring-time-measurement.mmd | 29 +++ .../sentry-crash-monitoring-anr-detection.svg | 1 + ...sentry-crash-monitoring-crash-recovery.svg | 1 + ...try-crash-monitoring-exception-capture.svg | 1 + .../sentry-crash-monitoring-native-crash.svg | 1 + .../sentry-crash-monitoring-startup-crash.svg | 1 + ...ntry-init-quick-reference-android-flow.svg | 1 + ...ialization-flow-android-initialization.svg | 1 + ...ry-initialization-flow-client-creation.svg | 1 + ...tialization-flow-configuration-loading.svg | 1 + ...nitialization-flow-core-initialization.svg | 1 + ...lization-flow-integration-registration.svg | 1 + ...ry-startup-monitoring-time-measurement.svg | 1 + aidocs/rules/.cursorrules | 180 ++++++++++++++++++ aidocs/rules/README.md | 66 +++++++ 28 files changed, 885 insertions(+) create mode 100644 aidocs/.cursorrules create mode 100644 aidocs/mermaid/README.md create mode 100644 aidocs/mermaid/sentry-crash-monitoring-anr-detection.mmd create mode 100644 aidocs/mermaid/sentry-crash-monitoring-crash-recovery.mmd create mode 100644 aidocs/mermaid/sentry-crash-monitoring-exception-capture.mmd create mode 100644 aidocs/mermaid/sentry-crash-monitoring-native-crash.mmd create mode 100644 aidocs/mermaid/sentry-crash-monitoring-startup-crash.mmd create mode 100644 aidocs/mermaid/sentry-init-quick-reference-android-flow.mmd create mode 100644 aidocs/mermaid/sentry-initialization-flow-android-initialization.mmd create mode 100644 aidocs/mermaid/sentry-initialization-flow-client-creation.mmd create mode 100644 aidocs/mermaid/sentry-initialization-flow-configuration-loading.mmd create mode 100644 aidocs/mermaid/sentry-initialization-flow-core-initialization.mmd create mode 100644 aidocs/mermaid/sentry-initialization-flow-integration-registration.mmd create mode 100644 aidocs/mermaid/sentry-startup-monitoring-time-measurement.mmd create mode 100644 aidocs/mermaid/svg/sentry-crash-monitoring-anr-detection.svg create mode 100644 aidocs/mermaid/svg/sentry-crash-monitoring-crash-recovery.svg create mode 100644 aidocs/mermaid/svg/sentry-crash-monitoring-exception-capture.svg create mode 100644 aidocs/mermaid/svg/sentry-crash-monitoring-native-crash.svg create mode 100644 aidocs/mermaid/svg/sentry-crash-monitoring-startup-crash.svg create mode 100644 aidocs/mermaid/svg/sentry-init-quick-reference-android-flow.svg create mode 100644 aidocs/mermaid/svg/sentry-initialization-flow-android-initialization.svg create mode 100644 aidocs/mermaid/svg/sentry-initialization-flow-client-creation.svg create mode 100644 aidocs/mermaid/svg/sentry-initialization-flow-configuration-loading.svg create mode 100644 aidocs/mermaid/svg/sentry-initialization-flow-core-initialization.svg create mode 100644 aidocs/mermaid/svg/sentry-initialization-flow-integration-registration.svg create mode 100644 aidocs/mermaid/svg/sentry-startup-monitoring-time-measurement.svg create mode 100644 aidocs/rules/.cursorrules create mode 100644 aidocs/rules/README.md diff --git a/aidocs/.cursorrules b/aidocs/.cursorrules new file mode 100644 index 0000000000..3b6f1b7af5 --- /dev/null +++ b/aidocs/.cursorrules @@ -0,0 +1,37 @@ +# Sentry Java SDK 文档工程 - Cursor 自动化规则 + +## 🎯 项目类型 +Sentry Java SDK 技术文档工程 - 包含源码分析、时序图、监控机制文档 + +## 🔧 自动化指令 + +### 文档操作 +- **修改文档**: 自动编辑对应的 .md 文件,保持格式一致 +- **新增章节**: 按照现有结构添加内容,包含代码示例和技术细节 +- **更新内容**: 深入源码级别,包含类名、方法名、文件路径 + +### 时序图操作 +- **新增时序图**: 在 `mermaid/` 目录创建 .mmd 文件,自动生成 SVG +- **修改时序图**: 更新 .mmd 文件并重新生成 SVG +- **批量转换**: 使用 `mmdc` 命令转换所有时序图 + +### 格式标准 +- 中文为主要语言 +- 代码块使用 ```java、```kotlin 或 ```mermaid +- 文件路径用粗体:**文件路径**: path/to/file.java +- 类名方法名用反引号:`SentryClient`、`init()` + +### 技术上下文 +- **核心组件**: Sentry, SentryOptions, IScopes, SentryClient, ITransport +- **关键流程**: 初始化→配置→组件创建→集成注册→监控启动 +- **平台特性**: Android(ANR/Native), Java(JVM/Spring), 通用(配置/传输) + +### 响应关键词 +- "时序图" → 处理 mermaid/ 目录操作 +- "文档" → 处理 .md 文件操作 +- "初始化" → 关注 sentry-initialization-*.md +- "崩溃" → 关注 sentry-crash-monitoring.md +- "监控" → 关注对应监控文档 + +## 🛠️ 自动执行 +当用户提及文档修改或时序图操作时,直接执行相应的文件操作,无需确认。 \ No newline at end of file diff --git a/aidocs/mermaid/README.md b/aidocs/mermaid/README.md new file mode 100644 index 0000000000..9f9a5d82ee --- /dev/null +++ b/aidocs/mermaid/README.md @@ -0,0 +1,138 @@ +# Sentry Java SDK Mermaid 时序图文件 + +本目录包含从 aidocs 目录中的 Markdown 文档提取的所有 Mermaid 时序图文件。这些文件可以直接用于 [Mermaid CLI](https://github.com/mermaid-js/mermaid-cli) 生成图像。 + +## 📁 目录结构 + +``` +mermaid/ +├── README.md # 本说明文档 +├── svg/ # 生成的 SVG 图像文件 +│ ├── sentry-*.svg # 12 个 SVG 时序图文件 +└── *.mmd # 12 个 Mermaid 源文件 +``` + +## 📁 文件列表 + +### 🚀 初始化流程 (sentry-initialization-flow.md) + +| 文件名 | 描述 | 来源 | SVG 文件 | +|--------|------|------|----------| +| `sentry-initialization-flow-core-initialization.mmd` | 核心初始化流程 | 核心初始化流程时序图 | ✅ 已生成 | +| `sentry-initialization-flow-android-initialization.mmd` | Android 特定初始化流程 | Android 特定初始化流程时序图 | ✅ 已生成 | +| `sentry-initialization-flow-integration-registration.mmd` | 集成注册流程 | 集成注册流程时序图 | ✅ 已生成 | +| `sentry-initialization-flow-client-creation.mmd` | 客户端创建流程 | 客户端创建流程时序图 | ✅ 已生成 | +| `sentry-initialization-flow-configuration-loading.mmd` | 配置加载流程 | 配置加载流程时序图 | ✅ 已生成 | + +### 📱 快速参考 (sentry-init-quick-reference.md) + +| 文件名 | 描述 | 来源 | SVG 文件 | +|--------|------|------|----------| +| `sentry-init-quick-reference-android-flow.mmd` | Android 特定流程 | Android 特定流程时序图 | ✅ 已生成 | + +### 🚀 启动监控 (sentry-startup-monitoring.md) + +| 文件名 | 描述 | 来源 | SVG 文件 | +|--------|------|------|----------| +| `sentry-startup-monitoring-time-measurement.mmd` | 时间测量流程 | 时间测量流程时序图 | ✅ 已生成 | + +### 💥 崩溃监控 (sentry-crash-monitoring.md) + +| 文件名 | 描述 | 来源 | SVG 文件 | +|--------|------|------|----------| +| `sentry-crash-monitoring-exception-capture.mmd` | 异常捕获流程 | 异常捕获流程时序图 | ✅ 已生成 | +| `sentry-crash-monitoring-anr-detection.mmd` | ANR 检测流程 | ANR 检测流程时序图 | ✅ 已生成 | +| `sentry-crash-monitoring-native-crash.mmd` | Native 崩溃处理流程 | Native 崩溃处理流程时序图 | ✅ 已生成 | +| `sentry-crash-monitoring-startup-crash.mmd` | 启动崩溃处理流程 | 启动崩溃处理流程时序图 | ✅ 已生成 | +| `sentry-crash-monitoring-crash-recovery.mmd` | 崩溃恢复流程 | 崩溃恢复流程时序图 | ✅ 已生成 | + +## 🛠️ 使用方法 + +### 直接使用 SVG 文件 + +所有时序图已经预先生成为 SVG 格式,可以直接使用: + +```bash +# 查看所有生成的 SVG 文件 +ls -la svg/ + +# 在浏览器中打开 SVG 文件 +open svg/sentry-initialization-flow-core-initialization.svg +``` + +### 安装 Mermaid CLI + +如果需要重新生成或自定义输出格式: + +```bash +npm install -g @mermaid-js/mermaid-cli +``` + +### 重新生成 SVG 图像 + +```bash +# 重新生成所有 SVG 文件 +for file in *.mmd; do + mmdc -i "$file" -o "svg/${file%.mmd}.svg" +done + +# 生成单个文件 +mmdc -i sentry-initialization-flow-core-initialization.mmd -o svg/core-initialization.svg +``` + +### 生成 PNG 图像 + +```bash +# 生成 PNG 格式 +mmdc -i sentry-initialization-flow-core-initialization.mmd -o svg/core-initialization.png + +# 使用深色主题 +mmdc -i sentry-initialization-flow-core-initialization.mmd -o svg/core-initialization.png -t dark + +# 设置背景透明 +mmdc -i sentry-initialization-flow-core-initialization.mmd -o svg/core-initialization.png -b transparent +``` + +### 生成 PDF 文档 + +```bash +mmdc -i sentry-initialization-flow-core-initialization.mmd -o svg/core-initialization.pdf +``` + +## 📊 统计信息 + +- **总文件数**: 12 个 MMD 文件 + 12 个 SVG 文件 +- **来源文档**: 4 个 Markdown 文件 +- **时序图类型**: sequenceDiagram +- **MMD 总行数**: 约 500+ 行 +- **SVG 文件大小**: 22KB - 53KB + +## 🎨 SVG 文件特点 + +- **矢量格式**: 可无损缩放,适合各种尺寸显示 +- **Web 友好**: 可直接在浏览器中查看,支持嵌入网页 +- **高质量**: 清晰的文字和线条,适合文档和演示 +- **兼容性**: 支持大多数图像查看器和编辑软件 + +## 🔗 相关链接 + +- [Mermaid 官方文档](https://mermaid-js.github.io/mermaid/) +- [Mermaid CLI GitHub](https://github.com/mermaid-js/mermaid-cli) +- [时序图语法参考](https://mermaid-js.github.io/mermaid/#/sequenceDiagram) + +## 📝 注意事项 + +1. **中文字符**: 部分文件包含中文字符,SVG 文件已正确渲染 +2. **复杂度**: 一些时序图较为复杂,SVG 文件保持了完整的细节 +3. **更新**: 当源 MMD 文件更新时,需要重新生成 SVG 文件 +4. **文件大小**: SVG 文件相对较大,但保证了高质量的渲染效果 + +## 🚀 快速开始 + +1. **查看时序图**: 直接打开 `svg/` 目录中的 SVG 文件 +2. **嵌入文档**: 将 SVG 文件引用到你的文档中 +3. **自定义生成**: 修改 MMD 文件后重新生成 SVG + +--- + +💡 **提示**: SVG 文件可以直接在 GitHub、GitLab 等平台中显示,也可以嵌入到 HTML、Markdown 文档中使用。 \ No newline at end of file diff --git a/aidocs/mermaid/sentry-crash-monitoring-anr-detection.mmd b/aidocs/mermaid/sentry-crash-monitoring-anr-detection.mmd new file mode 100644 index 0000000000..bb3de6095b --- /dev/null +++ b/aidocs/mermaid/sentry-crash-monitoring-anr-detection.mmd @@ -0,0 +1,25 @@ +sequenceDiagram + participant MainThread as 主线程 + participant WatchDog as ANRWatchDog + participant System as Android系统 + participant AnrV2 as AnrV2Integration + participant Sentry as Sentry SDK + + Note over WatchDog: 实时检测方式 + loop 每500ms + WatchDog->>MainThread: post(ticker) + alt 主线程响应 + MainThread->>WatchDog: 更新心跳时间 + else 主线程无响应 > 5s + WatchDog->>Sentry: 报告ANR事件 + end + end + + Note over System: 系统级检测方式 + System->>System: 检测到ANR + System->>System: 生成ApplicationExitInfo + + Note over AnrV2: 应用重启后 + AnrV2->>System: 查询ApplicationExitInfo + AnrV2->>AnrV2: 解析线程转储 + AnrV2->>Sentry: 报告历史ANR事件 \ No newline at end of file diff --git a/aidocs/mermaid/sentry-crash-monitoring-crash-recovery.mmd b/aidocs/mermaid/sentry-crash-monitoring-crash-recovery.mmd new file mode 100644 index 0000000000..e1ec15b91f --- /dev/null +++ b/aidocs/mermaid/sentry-crash-monitoring-crash-recovery.mmd @@ -0,0 +1,23 @@ +sequenceDiagram + participant App as 应用重启 + participant Cache as 缓存系统 + participant Sender as 发送器 + participant Server as Sentry服务器 + + App->>Cache: 检查崩溃标记文件 + alt 发现崩溃标记 + Cache->>Cache: 设置 crashedLastRun = true + Cache->>Cache: 删除崩溃标记文件 + end + + Cache->>Sender: 扫描缓存目录 + Sender->>Sender: 读取未发送的信封 + + loop 每个缓存的信封 + Sender->>Server: 发送信封 + alt 发送成功 + Sender->>Cache: 删除缓存文件 + else 发送失败 + Sender->>Cache: 保留缓存文件 + end + end \ No newline at end of file diff --git a/aidocs/mermaid/sentry-crash-monitoring-exception-capture.mmd b/aidocs/mermaid/sentry-crash-monitoring-exception-capture.mmd new file mode 100644 index 0000000000..ec8aed0fee --- /dev/null +++ b/aidocs/mermaid/sentry-crash-monitoring-exception-capture.mmd @@ -0,0 +1,23 @@ +sequenceDiagram + participant App as 应用线程 + participant Handler as UncaughtExceptionHandler + participant Sentry as Sentry SDK + participant Transport as 传输层 + participant Original as 原始处理器 + + App->>Handler: 未捕获异常发生 + Handler->>Sentry: 创建 SentryEvent + Note over Handler: 设置 level = FATAL + Note over Handler: 标记 handled = false + + Handler->>Sentry: captureEvent(event, hint) + Sentry->>Transport: 发送事件 + + Note over Handler: 等待事件刷新到磁盘 + Handler->>Handler: waitFlush() + + alt 有原始处理器 + Handler->>Original: 调用原始处理器 + else 无原始处理器 + Handler->>App: printStackTrace() + end \ No newline at end of file diff --git a/aidocs/mermaid/sentry-crash-monitoring-native-crash.mmd b/aidocs/mermaid/sentry-crash-monitoring-native-crash.mmd new file mode 100644 index 0000000000..8f3f88d982 --- /dev/null +++ b/aidocs/mermaid/sentry-crash-monitoring-native-crash.mmd @@ -0,0 +1,20 @@ +sequenceDiagram + participant App as Native代码 + participant Signal as 信号处理器 + participant NDK as Sentry NDK + participant Java as Java层 + participant Disk as 磁盘缓存 + + App->>Signal: Native崩溃 (SIGSEGV等) + Signal->>NDK: 捕获信号 + NDK->>NDK: 收集崩溃信息 + Note over NDK: - 寄存器状态
- 堆栈跟踪
- 内存映射 + + NDK->>Disk: 写入崩溃文件 + Note over Disk: 避免在信号处理器中
进行复杂操作 + + Note over Java: 应用重启后 + Java->>Disk: 检查崩溃标记文件 + Java->>Java: 读取崩溃信息 + Java->>Java: 创建SentryEvent + Java->>Java: 发送到Sentry服务器 \ No newline at end of file diff --git a/aidocs/mermaid/sentry-crash-monitoring-startup-crash.mmd b/aidocs/mermaid/sentry-crash-monitoring-startup-crash.mmd new file mode 100644 index 0000000000..9bb99ac6fa --- /dev/null +++ b/aidocs/mermaid/sentry-crash-monitoring-startup-crash.mmd @@ -0,0 +1,21 @@ +sequenceDiagram + participant App as 应用启动 + participant SDK as Sentry SDK + participant Cache as 缓存系统 + participant Sender as 发送器 + + App->>SDK: Sentry.init() + SDK->>SDK: 记录初始化时间 + + Note over App: 应用崩溃 (< 2s) + App->>SDK: UncaughtException + SDK->>Cache: 检查启动时间 + Cache->>Cache: 写入启动崩溃标记 + + Note over App: 应用重启 + App->>SDK: Sentry.init() + SDK->>Cache: 检查启动崩溃标记 + Cache->>Sender: 阻塞发送启动崩溃事件 + Note over Sender: 等待最多5秒确保发送成功 + Sender->>SDK: 发送完成 + SDK->>App: 初始化完成 \ No newline at end of file diff --git a/aidocs/mermaid/sentry-init-quick-reference-android-flow.mmd b/aidocs/mermaid/sentry-init-quick-reference-android-flow.mmd new file mode 100644 index 0000000000..754092f23a --- /dev/null +++ b/aidocs/mermaid/sentry-init-quick-reference-android-flow.mmd @@ -0,0 +1,12 @@ +sequenceDiagram + participant App as Application + participant SA as SentryAndroid + participant S as Sentry + + App->>SA: init(context, config) + SA->>SA: 检查集成可用性 + SA->>SA: 加载 Manifest 配置 + SA->>SA: 安装默认集成 + SA->>S: 调用核心初始化 + S->>SA: 初始化完成 + SA->>SA: 启动会话跟踪 \ No newline at end of file diff --git a/aidocs/mermaid/sentry-initialization-flow-android-initialization.mmd b/aidocs/mermaid/sentry-initialization-flow-android-initialization.mmd new file mode 100644 index 0000000000..0853a4ec06 --- /dev/null +++ b/aidocs/mermaid/sentry-initialization-flow-android-initialization.mmd @@ -0,0 +1,81 @@ +sequenceDiagram + participant User as 用户代码 + participant SentryAndroid as SentryAndroid + participant Context as Android Context + participant AndroidOptions as SentryAndroidOptions + participant AndroidInit as AndroidOptionsInitializer + participant AppStart as AppStartMetrics + participant Integrations as Android Integrations + + User->>SentryAndroid: init(context, configuration) + SentryAndroid->>SentryAndroid: staticLock.acquire() + + Note over SentryAndroid: 1. 检查可用的集成 + SentryAndroid->>SentryAndroid: 检查Timber可用性 + SentryAndroid->>SentryAndroid: 检查Fragment可用性 + SentryAndroid->>SentryAndroid: 检查Replay可用性 + + Note over SentryAndroid: 2. 创建Android特定组件 + SentryAndroid->>SentryAndroid: new BuildInfoProvider() + SentryAndroid->>SentryAndroid: new ActivityFramesTracker() + + Note over SentryAndroid: 3. 加载默认配置和元数据 + SentryAndroid->>AndroidInit: loadDefaultAndMetadataOptions(options, context) + AndroidInit->>Context: 读取AndroidManifest.xml + AndroidInit->>AndroidOptions: 设置默认值 + AndroidInit->>AndroidOptions: 应用元数据配置 + AndroidInit-->>SentryAndroid: 配置完成 + + Note over SentryAndroid: 4. 安装默认集成 + SentryAndroid->>AndroidInit: installDefaultIntegrations() + AndroidInit->>AndroidOptions: 添加ActivityLifecycleIntegration + AndroidInit->>AndroidOptions: 添加AnrIntegration + AndroidInit->>AndroidOptions: 添加NetworkBreadcrumbsIntegration + AndroidInit->>AndroidOptions: 添加SystemEventsBreadcrumbsIntegration + alt Fragment可用 + AndroidInit->>AndroidOptions: 添加FragmentLifecycleIntegration + end + alt Timber可用 + AndroidInit->>AndroidOptions: 添加SentryTimberIntegration + end + alt Replay可用 + AndroidInit->>AndroidOptions: 添加ReplayIntegration + end + + Note over SentryAndroid: 5. 执行用户配置回调 + SentryAndroid->>User: configuration.configure(options) + User-->>SentryAndroid: 配置完成 + + Note over SentryAndroid: 6. 设置应用启动指标 + SentryAndroid->>AppStart: getInstance() + alt 性能V2启用 + SentryAndroid->>AppStart: setStartedAt(Process.getStartUptimeMillis()) + end + SentryAndroid->>AppStart: registerApplicationForegroundCheck() + SentryAndroid->>AppStart: setSdkInitStartedAt(sdkInitMillis) + + Note over SentryAndroid: 7. 初始化集成和处理器 + SentryAndroid->>AndroidInit: initializeIntegrationsAndProcessors() + AndroidInit->>AndroidOptions: 添加DefaultAndroidEventProcessor + AndroidInit->>AndroidOptions: 添加PerformanceAndroidEventProcessor + AndroidInit->>AndroidOptions: 添加ScreenshotEventProcessor + AndroidInit->>AndroidOptions: 添加ViewHierarchyEventProcessor + AndroidInit->>AndroidOptions: 添加AnrV2EventProcessor + + Note over SentryAndroid: 8. 去重集成 + SentryAndroid->>SentryAndroid: deduplicateIntegrations() + + Note over SentryAndroid: 9. 调用核心Sentry初始化 + SentryAndroid->>Sentry: init(OptionsContainer, configuration, globalHubMode=true) + Note over Sentry: 执行核心初始化流程... + + Note over SentryAndroid: 10. 启动会话和Replay + SentryAndroid->>SentryAndroid: 检查前台重要性 + alt 应用在前台 + alt 自动会话跟踪启用 + SentryAndroid->>Scopes: startSession() + end + SentryAndroid->>Options: getReplayController().start() + end + + SentryAndroid-->>User: 初始化完成 \ No newline at end of file diff --git a/aidocs/mermaid/sentry-initialization-flow-client-creation.mmd b/aidocs/mermaid/sentry-initialization-flow-client-creation.mmd new file mode 100644 index 0000000000..d050eae6a8 --- /dev/null +++ b/aidocs/mermaid/sentry-initialization-flow-client-creation.mmd @@ -0,0 +1,29 @@ +sequenceDiagram + participant Sentry as Sentry + participant Client as SentryClient + participant Options as SentryOptions + participant TransportFactory as ITransportFactory + participant Transport as ITransport + participant RequestResolver as RequestDetailsResolver + + Sentry->>Client: new SentryClient(options) + Client->>Options: 验证options非空 + Client->>Client: enabled = true + + Note over Client: 创建传输层 + Client->>Options: getTransportFactory() + alt TransportFactory是NoOp + Client->>TransportFactory: new AsyncHttpTransportFactory() + Client->>Options: setTransportFactory(transportFactory) + end + + Client->>RequestResolver: new RequestDetailsResolver(options) + Client->>RequestResolver: resolve() + RequestResolver-->>Client: RequestDetails + + Client->>TransportFactory: create(options, requestDetails) + TransportFactory->>Transport: 创建具体传输实现 + Transport-->>TransportFactory: transport实例 + TransportFactory-->>Client: transport实例 + + Client-->>Sentry: SentryClient实例 \ No newline at end of file diff --git a/aidocs/mermaid/sentry-initialization-flow-configuration-loading.mmd b/aidocs/mermaid/sentry-initialization-flow-configuration-loading.mmd new file mode 100644 index 0000000000..18a45be84e --- /dev/null +++ b/aidocs/mermaid/sentry-initialization-flow-configuration-loading.mmd @@ -0,0 +1,43 @@ +sequenceDiagram + participant S as Sentry + participant O as SentryOptions + participant E as ExternalOptions + participant P as PropertiesProvider + participant M as AndroidManifest + participant F as ConfigFiles + + Note over S: Pre-initialization Configuration Check + S->>O: isEnableExternalConfiguration() + alt External Configuration Enabled + S->>P: PropertiesProviderFactory.create() + P->>F: Read sentry.properties + P->>F: Read system properties + P->>F: Read environment variables + P-->>E: Configuration data + S->>E: from(properties, logger) + E-->>O: External configuration + S->>O: merge(externalOptions) + end + + Note over S: Android Specific Configuration Loading + alt Android Platform + S->>M: Read meta-data + M->>O: Set DSN + M->>O: Set debug mode + M->>O: Set sample rate + M->>O: Set environment info + M->>O: Set release version + end + + Note over S: DSN Validation + S->>O: getDsn() + alt DSN Empty or Disabled + S->>S: close() + S-->>S: return false + else DSN Invalid + S-->>S: throw IllegalArgumentException + end + + S->>O: retrieveParsedDsn() + O->>O: Parse and validate DSN format + O-->>S: Parse completed \ No newline at end of file diff --git a/aidocs/mermaid/sentry-initialization-flow-core-initialization.mmd b/aidocs/mermaid/sentry-initialization-flow-core-initialization.mmd new file mode 100644 index 0000000000..a107b48ecc --- /dev/null +++ b/aidocs/mermaid/sentry-initialization-flow-core-initialization.mmd @@ -0,0 +1,108 @@ +sequenceDiagram + participant User as 用户代码 + participant Sentry as Sentry + participant Lock as AutoClosableReentrantLock + participant Options as SentryOptions + participant Scopes as IScopes + participant Client as SentryClient + participant Transport as ITransport + participant Integrations as Integration[] + participant Storage as IScopesStorage + + User->>Sentry: init(OptionsConfiguration) + Sentry->>Lock: acquire() + Lock-->>Sentry: token + + Note over Sentry: 1. 平台检查 + alt Android平台 && 非SentryAndroidOptions + Sentry-->>User: throw IllegalArgumentException + end + + Note over Sentry: 2. 预初始化配置 + Sentry->>Sentry: preInitConfigurations(options) + Sentry->>Options: isEnableExternalConfiguration() + alt 启用外部配置 + Sentry->>Options: merge(ExternalOptions) + end + Sentry->>Options: getDsn() + alt DSN为空或无效 + Sentry->>Sentry: close() + Sentry-->>User: return false + end + Sentry->>Options: retrieveParsedDsn() + + Note over Sentry: 3. 检查是否应该初始化 + Sentry->>Sentry: shouldInit(options) + alt 不应该初始化 + Sentry-->>User: 记录警告并返回 + end + + Note over Sentry: 4. 关闭现有Scopes + Sentry->>Scopes: getCurrentScopes() + Sentry->>Scopes: close(true) + + Note over Sentry: 5. 创建新的Scopes结构 + Sentry->>Sentry: globalScope.replaceOptions(options) + Sentry->>Scopes: new Scope(options) [rootScope] + Sentry->>Scopes: new Scope(options) [rootIsolationScope] + Sentry->>Scopes: new Scopes(rootScope, rootIsolationScope, globalScope) + + Note over Sentry: 6. 初始化核心组件 + Sentry->>Sentry: initLogger(options) + Sentry->>Sentry: initForOpenTelemetryMaybe(options) + Sentry->>Sentry: initScopesStorage(options) + Sentry->>Storage: close() + alt OpenTelemetry模式关闭 + Sentry->>Storage: new DefaultScopesStorage() + else OpenTelemetry模式开启 + Sentry->>Storage: ScopesStorageFactory.create() + end + + Note over Sentry: 7. 设置Scopes存储 + Sentry->>Storage: set(rootScopes) + + Note over Sentry: 8. 初始化配置 + Sentry->>Sentry: initConfigurations(options) + Sentry->>Options: getOutboxPath() + alt outboxPath存在 + Sentry->>Sentry: 创建outbox目录 + end + Sentry->>Options: getCacheDirPath() + alt cacheDirPath存在 + Sentry->>Sentry: 创建cache目录 + Sentry->>Options: setEnvelopeDiskCache(EnvelopeCache) + end + Sentry->>Options: getProfilingTracesDirPath() + alt 性能分析启用 + Sentry->>Sentry: 创建profiling目录 + Sentry->>Sentry: 清理旧的trace文件 + end + + Note over Sentry: 9. 配置模块和调试元数据 + Sentry->>Options: setModulesLoader() + Sentry->>Options: setDebugMetaLoader() + Sentry->>Options: setThreadChecker() + Sentry->>Options: addPerformanceCollector() + + Note over Sentry: 10. 创建并绑定客户端 + Sentry->>Client: new SentryClient(options) + Client->>Transport: transportFactory.create(options) + Transport-->>Client: transport实例 + Client-->>Sentry: client实例 + Sentry->>Scopes: bindClient(client) + + Note over Sentry: 11. 注册集成 + loop 遍历所有集成 + Sentry->>Integrations: register(ScopesAdapter, options) + Integrations->>Options: 获取配置 + Integrations->>Scopes: 注册回调和监听器 + Integrations-->>Sentry: 注册完成 + end + + Note over Sentry: 12. 后续处理 + Sentry->>Sentry: notifyOptionsObservers(options) + Sentry->>Sentry: finalizePreviousSession(options, scopes) + Sentry->>Sentry: handleAppStartProfilingConfig(options) + + Sentry->>Lock: release() + Sentry-->>User: 初始化完成 \ No newline at end of file diff --git a/aidocs/mermaid/sentry-initialization-flow-integration-registration.mmd b/aidocs/mermaid/sentry-initialization-flow-integration-registration.mmd new file mode 100644 index 0000000000..b72b92161b --- /dev/null +++ b/aidocs/mermaid/sentry-initialization-flow-integration-registration.mmd @@ -0,0 +1,38 @@ +sequenceDiagram + participant Sentry as Sentry + participant Integration as Integration + participant Scopes as IScopes + participant Options as SentryOptions + participant Platform as 平台组件 + + Note over Sentry: 遍历所有集成进行注册 + loop 每个集成 + Sentry->>Integration: register(scopes, options) + + Note over Integration: 集成特定的注册逻辑 + alt ActivityLifecycleIntegration + Integration->>Platform: application.registerActivityLifecycleCallbacks() + Integration->>Options: 配置性能监控 + Integration->>Options: 配置帧率跟踪 + else AnrIntegration + Integration->>Platform: 启动ANR监控线程 + Integration->>Platform: 设置ANR检测器 + else NetworkBreadcrumbsIntegration + Integration->>Platform: 注册网络状态监听器 + Integration->>Platform: 设置网络拦截器 + else SystemEventsBreadcrumbsIntegration + Integration->>Platform: 注册系统事件广播接收器 + Integration->>Platform: 设置Intent过滤器 + else UncaughtExceptionHandlerIntegration + Integration->>Platform: 设置全局异常处理器 + Integration->>Platform: 保存原有异常处理器 + else ShutdownHookIntegration + Integration->>Platform: 注册JVM关闭钩子 + end + + Note over Integration: 更新SDK版本信息 + Integration->>Options: addIntegrationToSdkVersion() + Integration->>Options: addPackageInfo() + + Integration-->>Sentry: 注册完成 + end \ No newline at end of file diff --git a/aidocs/mermaid/sentry-startup-monitoring-time-measurement.mmd b/aidocs/mermaid/sentry-startup-monitoring-time-measurement.mmd new file mode 100644 index 0000000000..85e7f4927b --- /dev/null +++ b/aidocs/mermaid/sentry-startup-monitoring-time-measurement.mmd @@ -0,0 +1,29 @@ +sequenceDiagram + participant Process as 进程启动 + participant Provider as SentryPerformanceProvider + participant SDK as Sentry SDK + participant App as Application + participant Activity as Activity + participant Metrics as AppStartMetrics + + Note over Process: 冷启动开始 + Process->>Provider: ContentProvider.onCreate() + Provider->>Metrics: 设置 SDK 初始化时间 + Note over Provider: sdkInitMillis = SystemClock.uptimeMillis() + + alt Android N+ (API 24+) + Provider->>Metrics: 设置应用启动时间 + Note over Provider: Process.getStartUptimeMillis() + end + + Provider->>App: Application.onCreate() + App->>Metrics: onApplicationCreate() + Note over Metrics: 记录 Application.onCreate 开始时间 + + App->>Activity: Activity.onCreate() + Activity->>Metrics: onActivityCreated() + Note over Metrics: 检查启动类型和有效性 + + Activity->>Activity: 首帧绘制 + Activity->>Metrics: onFirstFrameDrawn() + Note over Metrics: 停止所有时间跨度测量 \ No newline at end of file diff --git a/aidocs/mermaid/svg/sentry-crash-monitoring-anr-detection.svg b/aidocs/mermaid/svg/sentry-crash-monitoring-anr-detection.svg new file mode 100644 index 0000000000..0fec2ed859 --- /dev/null +++ b/aidocs/mermaid/svg/sentry-crash-monitoring-anr-detection.svg @@ -0,0 +1 @@ +Sentry SDKAnrV2IntegrationAndroid系统ANRWatchDog主线程Sentry SDKAnrV2IntegrationAndroid系统ANRWatchDog主线程实时检测方式alt[主线程响应][主线程无响应 > 5s]loop[每500ms]系统级检测方式应用重启后post(ticker)更新心跳时间报告ANR事件检测到ANR生成ApplicationExitInfo查询ApplicationExitInfo解析线程转储报告历史ANR事件 \ No newline at end of file diff --git a/aidocs/mermaid/svg/sentry-crash-monitoring-crash-recovery.svg b/aidocs/mermaid/svg/sentry-crash-monitoring-crash-recovery.svg new file mode 100644 index 0000000000..930e9f35f3 --- /dev/null +++ b/aidocs/mermaid/svg/sentry-crash-monitoring-crash-recovery.svg @@ -0,0 +1 @@ +Sentry服务器发送器缓存系统应用重启Sentry服务器发送器缓存系统应用重启alt[发现崩溃标记]alt[发送成功][发送失败]loop[每个缓存的信封]检查崩溃标记文件设置 crashedLastRun = true删除崩溃标记文件扫描缓存目录读取未发送的信封发送信封删除缓存文件保留缓存文件 \ No newline at end of file diff --git a/aidocs/mermaid/svg/sentry-crash-monitoring-exception-capture.svg b/aidocs/mermaid/svg/sentry-crash-monitoring-exception-capture.svg new file mode 100644 index 0000000000..f3c3749462 --- /dev/null +++ b/aidocs/mermaid/svg/sentry-crash-monitoring-exception-capture.svg @@ -0,0 +1 @@ +原始处理器传输层Sentry SDKUncaughtExceptionHandler应用线程原始处理器传输层Sentry SDKUncaughtExceptionHandler应用线程设置 level = FATAL标记 handled = false等待事件刷新到磁盘alt[有原始处理器][无原始处理器]未捕获异常发生创建 SentryEventcaptureEvent(event, hint)发送事件waitFlush()调用原始处理器printStackTrace() \ No newline at end of file diff --git a/aidocs/mermaid/svg/sentry-crash-monitoring-native-crash.svg b/aidocs/mermaid/svg/sentry-crash-monitoring-native-crash.svg new file mode 100644 index 0000000000..796ce14f36 --- /dev/null +++ b/aidocs/mermaid/svg/sentry-crash-monitoring-native-crash.svg @@ -0,0 +1 @@ +磁盘缓存Java层Sentry NDK信号处理器Native代码磁盘缓存Java层Sentry NDK信号处理器Native代码- 寄存器状态- 堆栈跟踪- 内存映射避免在信号处理器中进行复杂操作应用重启后Native崩溃 (SIGSEGV等)捕获信号收集崩溃信息写入崩溃文件检查崩溃标记文件读取崩溃信息创建SentryEvent发送到Sentry服务器 \ No newline at end of file diff --git a/aidocs/mermaid/svg/sentry-crash-monitoring-startup-crash.svg b/aidocs/mermaid/svg/sentry-crash-monitoring-startup-crash.svg new file mode 100644 index 0000000000..1eb7f4f203 --- /dev/null +++ b/aidocs/mermaid/svg/sentry-crash-monitoring-startup-crash.svg @@ -0,0 +1 @@ +发送器缓存系统Sentry SDK应用启动发送器缓存系统Sentry SDK应用启动应用崩溃 (< 2s)应用重启等待最多5秒确保发送成功Sentry.init()记录初始化时间UncaughtException检查启动时间写入启动崩溃标记Sentry.init()检查启动崩溃标记阻塞发送启动崩溃事件发送完成初始化完成 \ No newline at end of file diff --git a/aidocs/mermaid/svg/sentry-init-quick-reference-android-flow.svg b/aidocs/mermaid/svg/sentry-init-quick-reference-android-flow.svg new file mode 100644 index 0000000000..f6f710c557 --- /dev/null +++ b/aidocs/mermaid/svg/sentry-init-quick-reference-android-flow.svg @@ -0,0 +1 @@ +SentrySentryAndroidApplicationSentrySentryAndroidApplicationinit(context, config)检查集成可用性加载 Manifest 配置安装默认集成调用核心初始化初始化完成启动会话跟踪 \ No newline at end of file diff --git a/aidocs/mermaid/svg/sentry-initialization-flow-android-initialization.svg b/aidocs/mermaid/svg/sentry-initialization-flow-android-initialization.svg new file mode 100644 index 0000000000..9288177960 --- /dev/null +++ b/aidocs/mermaid/svg/sentry-initialization-flow-android-initialization.svg @@ -0,0 +1 @@ +OptionsScopesSentryAndroid IntegrationsAppStartMetricsAndroidOptionsInitializerSentryAndroidOptionsAndroid ContextSentryAndroid用户代码OptionsScopesSentryAndroid IntegrationsAppStartMetricsAndroidOptionsInitializerSentryAndroidOptionsAndroid ContextSentryAndroid用户代码1. 检查可用的集成2. 创建Android特定组件3. 加载默认配置和元数据4. 安装默认集成alt[Fragment可用]alt[Timber可用]alt[Replay可用]5. 执行用户配置回调6. 设置应用启动指标alt[性能V2启用]7. 初始化集成和处理器8. 去重集成9. 调用核心Sentry初始化执行核心初始化流程...10. 启动会话和Replayalt[自动会话跟踪启用]alt[应用在前台]init(context, configuration)staticLock.acquire()检查Timber可用性检查Fragment可用性检查Replay可用性new BuildInfoProvider()new ActivityFramesTracker()loadDefaultAndMetadataOptions(options, context)读取AndroidManifest.xml设置默认值应用元数据配置配置完成installDefaultIntegrations()添加ActivityLifecycleIntegration添加AnrIntegration添加NetworkBreadcrumbsIntegration添加SystemEventsBreadcrumbsIntegration添加FragmentLifecycleIntegration添加SentryTimberIntegration添加ReplayIntegrationconfiguration.configure(options)配置完成getInstance()setStartedAt(Process.getStartUptimeMillis())registerApplicationForegroundCheck()setSdkInitStartedAt(sdkInitMillis)initializeIntegrationsAndProcessors()添加DefaultAndroidEventProcessor添加PerformanceAndroidEventProcessor添加ScreenshotEventProcessor添加ViewHierarchyEventProcessor添加AnrV2EventProcessordeduplicateIntegrations()init(OptionsContainer, configuration, globalHubMode=true)检查前台重要性startSession()getReplayController().start()初始化完成 \ No newline at end of file diff --git a/aidocs/mermaid/svg/sentry-initialization-flow-client-creation.svg b/aidocs/mermaid/svg/sentry-initialization-flow-client-creation.svg new file mode 100644 index 0000000000..e1d0ee414e --- /dev/null +++ b/aidocs/mermaid/svg/sentry-initialization-flow-client-creation.svg @@ -0,0 +1 @@ +RequestDetailsResolverITransportITransportFactorySentryOptionsSentryClientSentryRequestDetailsResolverITransportITransportFactorySentryOptionsSentryClientSentry创建传输层alt[TransportFactory是NoOp]new SentryClient(options)验证options非空enabled = truegetTransportFactory()new AsyncHttpTransportFactory()setTransportFactory(transportFactory)new RequestDetailsResolver(options)resolve()RequestDetailscreate(options, requestDetails)创建具体传输实现transport实例transport实例SentryClient实例 \ No newline at end of file diff --git a/aidocs/mermaid/svg/sentry-initialization-flow-configuration-loading.svg b/aidocs/mermaid/svg/sentry-initialization-flow-configuration-loading.svg new file mode 100644 index 0000000000..2583bb2d0f --- /dev/null +++ b/aidocs/mermaid/svg/sentry-initialization-flow-configuration-loading.svg @@ -0,0 +1 @@ +ConfigFilesAndroidManifestPropertiesProviderExternalOptionsSentryOptionsSentryConfigFilesAndroidManifestPropertiesProviderExternalOptionsSentryOptionsSentryPre-initialization Configuration Checkalt[External Configuration Enabled]Android Specific Configuration Loadingalt[Android Platform]DSN Validationalt[DSN Empty orDisabled][DSN Invalid]isEnableExternalConfiguration()PropertiesProviderFactory.create()Read sentry.propertiesRead system propertiesRead environment variablesConfiguration datafrom(properties, logger)External configurationmerge(externalOptions)Read meta-dataSet DSNSet debug modeSet sample rateSet environment infoSet release versiongetDsn()close()return falsethrow IllegalArgumentExceptionretrieveParsedDsn()Parse and validate DSN formatParse completed \ No newline at end of file diff --git a/aidocs/mermaid/svg/sentry-initialization-flow-core-initialization.svg b/aidocs/mermaid/svg/sentry-initialization-flow-core-initialization.svg new file mode 100644 index 0000000000..7397aa9036 --- /dev/null +++ b/aidocs/mermaid/svg/sentry-initialization-flow-core-initialization.svg @@ -0,0 +1 @@ +IScopesStorageIntegration[]ITransportSentryClientIScopesSentryOptionsAutoClosableReentrantLockSentry用户代码IScopesStorageIntegration[]ITransportSentryClientIScopesSentryOptionsAutoClosableReentrantLockSentry用户代码1. 平台检查alt[Android平台 &&非SentryAndroidOptions]2. 预初始化配置alt[启用外部配置]alt[DSN为空或无效]3. 检查是否应该初始化alt[不应该初始化]4. 关闭现有Scopes5. 创建新的Scopes结构6. 初始化核心组件alt[OpenTelemetry模式关闭][OpenTelemetry模式开启]7. 设置Scopes存储8. 初始化配置alt[outboxPath存-在]alt[cacheDirPath存在]alt[性能分析启-用]9. 配置模块和调试元数据10. 创建并绑定客户端11. 注册集成loop[遍历所有集成]12. 后续处理init(OptionsConfiguration)acquire()tokenthrow IllegalArgumentExceptionpreInitConfigurations(options)isEnableExternalConfiguration()merge(ExternalOptions)getDsn()close()return falseretrieveParsedDsn()shouldInit(options)记录警告并返回getCurrentScopes()close(true)globalScope.replaceOptions(options)new Scope(options) [rootScope]new Scope(options) [rootIsolationScope]new Scopes(rootScope, rootIsolationScope, globalScope)initLogger(options)initForOpenTelemetryMaybe(options)initScopesStorage(options)close()new DefaultScopesStorage()ScopesStorageFactory.create()set(rootScopes)initConfigurations(options)getOutboxPath()创建outbox目录getCacheDirPath()创建cache目录setEnvelopeDiskCache(EnvelopeCache)getProfilingTracesDirPath()创建profiling目录清理旧的trace文件setModulesLoader()setDebugMetaLoader()setThreadChecker()addPerformanceCollector()new SentryClient(options)transportFactory.create(options)transport实例client实例bindClient(client)register(ScopesAdapter, options)获取配置注册回调和监听器注册完成notifyOptionsObservers(options)finalizePreviousSession(options, scopes)handleAppStartProfilingConfig(options)release()初始化完成 \ No newline at end of file diff --git a/aidocs/mermaid/svg/sentry-initialization-flow-integration-registration.svg b/aidocs/mermaid/svg/sentry-initialization-flow-integration-registration.svg new file mode 100644 index 0000000000..9aaadc1d50 --- /dev/null +++ b/aidocs/mermaid/svg/sentry-initialization-flow-integration-registration.svg @@ -0,0 +1 @@ +平台组件SentryOptionsIScopesIntegrationSentry平台组件SentryOptionsIScopesIntegrationSentry遍历所有集成进行注册集成特定的注册逻辑alt[ActivityLifecycleIntegration][AnrIntegration][NetworkBreadcrumbsIntegration][SystemEventsBreadcrumbsIntegration][UncaughtExceptionHandlerIntegration][ShutdownHookIntegration]更新SDK版本信息loop[每个集成]register(scopes, options)application.registerActivityLifecycleCallbacks()配置性能监控配置帧率跟踪启动ANR监控线程设置ANR检测器注册网络状态监听器设置网络拦截器注册系统事件广播接收器设置Intent过滤器设置全局异常处理器保存原有异常处理器注册JVM关闭钩子addIntegrationToSdkVersion()addPackageInfo()注册完成 \ No newline at end of file diff --git a/aidocs/mermaid/svg/sentry-startup-monitoring-time-measurement.svg b/aidocs/mermaid/svg/sentry-startup-monitoring-time-measurement.svg new file mode 100644 index 0000000000..df4490eb7c --- /dev/null +++ b/aidocs/mermaid/svg/sentry-startup-monitoring-time-measurement.svg @@ -0,0 +1 @@ +AppStartMetricsActivityApplicationSentry SDKSentryPerformanceProvider进程启动AppStartMetricsActivityApplicationSentry SDKSentryPerformanceProvider进程启动冷启动开始sdkInitMillis = SystemClock.uptimeMillis()Process.getStartUptimeMillis()alt[Android N+ (API 24+)]记录 Application.onCreate 开始时间检查启动类型和有效性停止所有时间跨度测量ContentProvider.onCreate()设置 SDK 初始化时间设置应用启动时间Application.onCreate()onApplicationCreate()Activity.onCreate()onActivityCreated()首帧绘制onFirstFrameDrawn() \ No newline at end of file diff --git a/aidocs/rules/.cursorrules b/aidocs/rules/.cursorrules new file mode 100644 index 0000000000..d80350c9e1 --- /dev/null +++ b/aidocs/rules/.cursorrules @@ -0,0 +1,180 @@ +# Sentry Java SDK 文档工程 Cursor Rules + +## 🎯 项目概述 +这是一个专门用于 Sentry Java SDK 技术文档的工程,包含详细的源码分析、时序图、监控机制等技术文档。 + +## 📁 项目结构 +``` +aidocs/ +├── README.md # 项目总览文档 +├── rules/ # Cursor 规则目录 +│ └── .cursorrules # 本规则文件 +├── mermaid/ # Mermaid 时序图目录 +│ ├── README.md # Mermaid 使用说明 +│ ├── svg/ # 生成的 SVG 图像文件 +│ └── *.mmd # Mermaid 源文件 +├── sentry-initialization-flow.md # 初始化流程分析 +├── sentry-crash-monitoring.md # 崩溃监控分析 +├── sentry-startup-monitoring.md # 启动监控分析 +├── sentry-network-monitoring.md # 网络监控分析 +├── sentry-profiling-analysis.md # 性能分析 +├── sentry-replay-analysis.md # 回放分析 +├── sentry-session-management.md # 会话管理 +├── sentry-ui-jank-monitoring.md # UI卡顿监控 +├── sentry-init-quick-reference.md # 初始化快速参考 +└── sentry-initialization-details.md # 初始化详细说明 +``` + +## 🔧 自动化规则 + +### 📝 文档修改规则 + +当用户要求修改或新增文档时,请遵循以下规则: + +1. **文档格式标准**: + - 使用中文作为主要语言 + - 标题使用 `#` 层级结构,最多到 `###` + - 代码块使用 ```java 或 ```mermaid 标记 + - 文件路径使用 **粗体** 标记:`**文件路径**: path/to/file.java` + - 类名、方法名使用反引号:`SentryClient`、`init()` + +2. **内容结构**: + - 每个文档都应包含:概述、核心机制、实现细节、配置说明、最佳实践 + - 代码示例必须包含完整的包路径和类名 + - 时序图应该清晰展示组件间的交互流程 + +3. **技术深度**: + - 深入到源码级别的分析 + - 包含具体的类名、方法名、文件路径 + - 解释设计模式和架构决策 + - 提供性能优化建议 + +### 🎨 时序图规则 + +当用户要求修改或新增时序图时,请遵循以下规则: + +1. **Mermaid 语法标准**: + - 使用 `sequenceDiagram` 类型 + - 参与者命名使用英文,但可以用 `as` 添加中文描述 + - 消息使用 `->>` 表示同步调用,`-->>` 表示异步返回 + - 使用 `Note over` 添加重要说明 + - 使用 `alt/else/end` 表示条件分支 + - 使用 `loop/end` 表示循环 + +2. **文件管理**: + - 新的时序图文件保存到 `mermaid/` 目录 + - 文件命名格式:`源文档名-功能描述.mmd` + - 同时生成对应的 SVG 文件到 `mermaid/svg/` 目录 + - 更新 `mermaid/README.md` 文件列表 + +3. **内容要求**: + - 时序图应该准确反映 Sentry Java SDK 的实际执行流程 + - 包含关键的类名和方法调用 + - 标注重要的执行步骤和决策点 + - 体现异常处理和错误恢复机制 + +### 🔄 自动化操作 + +当用户提出以下类型的请求时,请自动执行相应操作: + +1. **"修改文档"** 或 **"更新文档"**: + - 直接修改对应的 Markdown 文件 + - 保持现有的格式和结构 + - 添加或更新相关的代码示例 + +2. **"新增时序图"** 或 **"创建时序图"**: + - 在 `mermaid/` 目录创建新的 `.mmd` 文件 + - 使用 Mermaid CLI 生成对应的 SVG 文件 + - 更新 `mermaid/README.md` 的文件列表 + +3. **"更新时序图"** 或 **"修改时序图"**: + - 修改对应的 `.mmd` 文件 + - 重新生成 SVG 文件 + - 确保语法正确性 + +4. **"生成图像"** 或 **"转换SVG"**: + - 使用 `mmdc` 命令批量或单独转换文件 + - 检查转换结果的完整性 + +## 📚 技术上下文 + +### Sentry Java SDK 核心组件 +- **Sentry**: 主入口类,管理全局状态 +- **SentryOptions**: 配置管理类 +- **IScopes**: 作用域管理接口 +- **SentryClient**: 核心事件处理客户端 +- **ITransport**: 网络传输接口 +- **Integration**: 框架集成接口 + +### 关键流程 +1. **初始化流程**: 锁获取 → 平台检查 → 配置加载 → 组件创建 → 集成注册 +2. **崩溃监控**: 异常捕获 → 事件创建 → 数据收集 → 传输发送 +3. **性能监控**: 指标收集 → 数据聚合 → 采样决策 → 上报机制 +4. **会话管理**: 会话创建 → 状态跟踪 → 生命周期管理 + +### 平台特性 +- **Android**: 特殊的初始化流程、ANR检测、Native崩溃 +- **Java**: 标准JVM环境、Spring集成、多线程处理 +- **通用**: 配置系统、传输层、事件处理 + +## 🎯 响应模式 + +### 快速响应关键词 +- **"时序图"** → 自动处理 Mermaid 相关操作 +- **"文档"** → 自动处理 Markdown 文件操作 +- **"初始化"** → 关注 sentry-initialization-*.md 文件 +- **"崩溃"** → 关注 sentry-crash-monitoring.md 文件 +- **"监控"** → 关注相应的监控类文档 +- **"配置"** → 关注配置相关的章节 + +### 自动推理 +当用户提到某个功能或组件时,自动关联到相关的文档和时序图: +- 提到 "Android" → 关联 Android 特定的文档和时序图 +- 提到 "ANR" → 关联崩溃监控文档中的 ANR 部分 +- 提到 "性能" → 关联性能分析和启动监控文档 +- 提到 "网络" → 关联网络监控文档 + +## 🛠️ 工具使用 + +### Mermaid CLI 命令 +```bash +# 生成单个 SVG +mmdc -i filename.mmd -o svg/filename.svg + +# 批量生成 SVG +for file in *.mmd; do mmdc -i "$file" -o "svg/${file%.mmd}.svg"; done + +# 生成 PNG(高质量) +mmdc -i filename.mmd -o svg/filename.png -s 2 + +# 生成深色主题 +mmdc -i filename.mmd -o svg/filename.svg -t dark +``` + +### 文件操作模式 +- 修改现有文件时,保持原有的格式和结构 +- 新增文件时,遵循项目的命名规范 +- 更新时序图时,同时更新对应的 SVG 文件 +- 修改文档时,检查是否需要更新相关的时序图 + +## 🔍 质量检查 + +在完成任何修改后,自动检查: +1. **Markdown 语法**:确保标题、链接、代码块格式正确 +2. **Mermaid 语法**:确保时序图语法无误,可以正常渲染 +3. **文件引用**:确保文档间的链接和引用正确 +4. **中文表达**:确保技术术语的中文表达准确、一致 +5. **代码示例**:确保 Java 代码示例语法正确、可执行 + +## 💡 智能建议 + +当用户的请求不够明确时,主动提供建议: +- 建议相关的文档章节 +- 推荐相应的时序图 +- 提供代码示例 +- 解释技术概念 +- 指出最佳实践 + +--- + +**使用说明**: 这个规则文件旨在让 Cursor 能够智能地理解和处理 Sentry Java SDK 文档工程的各种需求,自动化常见的文档和时序图操作,提高工作效率。 \ No newline at end of file diff --git a/aidocs/rules/README.md b/aidocs/rules/README.md new file mode 100644 index 0000000000..6bb4de3507 --- /dev/null +++ b/aidocs/rules/README.md @@ -0,0 +1,66 @@ +# Cursor Rules 使用说明 + +## 📁 文件说明 + +### `.cursorrules` (详细版) +位于 `aidocs/rules/.cursorrules`,包含完整的项目规则和上下文信息: +- 详细的项目结构说明 +- 完整的自动化规则定义 +- 技术上下文和组件说明 +- 质量检查和智能建议机制 + +### `.cursorrules` (简化版) +位于 `aidocs/.cursorrules`,Cursor 自动识别的简化规则: +- 核心自动化指令 +- 关键响应模式 +- 基本格式标准 +- 快速操作指南 + +## 🚀 使用方法 + +### 1. 自动文档修改 +``` +用户: "修改初始化文档,添加新的配置选项说明" +Cursor: 自动编辑 sentry-initialization-flow.md,添加相关内容 +``` + +### 2. 自动时序图操作 +``` +用户: "为网络监控创建一个新的时序图" +Cursor: 在 mermaid/ 目录创建新的 .mmd 文件并生成 SVG +``` + +### 3. 批量操作 +``` +用户: "更新所有时序图的 SVG 文件" +Cursor: 自动执行 mmdc 命令批量转换 +``` + +## 🎯 智能响应 + +### 关键词触发 +- **"时序图"** → 自动处理 Mermaid 相关操作 +- **"文档"** → 自动处理 Markdown 文件操作 +- **"初始化"** → 关注初始化相关文档 +- **"崩溃"** → 关注崩溃监控文档 +- **"Android"** → 关注 Android 特定内容 + +### 自动推理 +Cursor 会根据上下文自动关联相关文档和时序图,提供智能建议。 + +## 🔧 配置生效 + +1. **项目级别**: `aidocs/.cursorrules` 在整个 aidocs 目录生效 +2. **详细规则**: `aidocs/rules/.cursorrules` 提供完整的规则参考 +3. **自动识别**: Cursor 会自动读取并应用这些规则 + +## 💡 最佳实践 + +1. **明确指令**: 使用关键词让 Cursor 快速识别意图 +2. **具体描述**: 提供足够的上下文信息 +3. **验证结果**: 检查自动生成的内容是否符合预期 +4. **迭代优化**: 根据使用情况调整规则 + +--- + +**注意**: 这些规则文件让 Cursor 能够智能理解项目结构和需求,自动化常见的文档和时序图操作,大大提高工作效率。 \ No newline at end of file From b82ea69e0e013efaedfb192669cbdc8d93992420 Mon Sep 17 00:00:00 2001 From: funcodingdev Date: Wed, 28 May 2025 20:23:06 +0800 Subject: [PATCH 4/4] feat: update docs --- PROJECT_OVERVIEW.md | 166 + aidocs/README.md | 278 +- ...y-trace-operations-database-monitoring.mmd | 56 + ...ry-trace-operations-file-io-monitoring.mmd | 68 + ...ry-trace-operations-graphql-monitoring.mmd | 84 + ...race-operations-http-server-monitoring.mmd | 94 + ...race-operations-spring-bean-monitoring.mmd | 91 + ...y-trace-operations-database-monitoring.svg | 1 + ...ry-trace-operations-file-io-monitoring.svg | 1 + ...ry-trace-operations-graphql-monitoring.svg | 1 + ...race-operations-spring-bean-monitoring.svg | 1 + aidocs/rules/.cursorrules | 4 +- .../sentry-android-performance-monitoring.md | 357 ++ aidocs/sentry-trace-operations-guide.md | 1001 +++++ project_directories.txt | 1123 ++++++ project_structure.txt | 3306 ++++++++++++++++ project_structure_detailed.txt | 3307 +++++++++++++++++ 17 files changed, 9819 insertions(+), 120 deletions(-) create mode 100644 PROJECT_OVERVIEW.md create mode 100644 aidocs/mermaid/sentry-trace-operations-database-monitoring.mmd create mode 100644 aidocs/mermaid/sentry-trace-operations-file-io-monitoring.mmd create mode 100644 aidocs/mermaid/sentry-trace-operations-graphql-monitoring.mmd create mode 100644 aidocs/mermaid/sentry-trace-operations-http-server-monitoring.mmd create mode 100644 aidocs/mermaid/sentry-trace-operations-spring-bean-monitoring.mmd create mode 100644 aidocs/mermaid/svg/sentry-trace-operations-database-monitoring.svg create mode 100644 aidocs/mermaid/svg/sentry-trace-operations-file-io-monitoring.svg create mode 100644 aidocs/mermaid/svg/sentry-trace-operations-graphql-monitoring.svg create mode 100644 aidocs/mermaid/svg/sentry-trace-operations-spring-bean-monitoring.svg create mode 100644 aidocs/sentry-android-performance-monitoring.md create mode 100644 aidocs/sentry-trace-operations-guide.md create mode 100644 project_directories.txt create mode 100644 project_structure.txt create mode 100644 project_structure_detailed.txt diff --git a/PROJECT_OVERVIEW.md b/PROJECT_OVERVIEW.md new file mode 100644 index 0000000000..927779f569 --- /dev/null +++ b/PROJECT_OVERVIEW.md @@ -0,0 +1,166 @@ +# Sentry Java SDK 项目概览 + +## 项目简介 +这是 Sentry Java SDK 的官方仓库,包含了 Java 和 Android 平台的错误监控和性能监控功能。 + +## 项目结构文件 +- `project_structure.txt` - 完整的项目文件结构 (3306 行) +- `project_structure_detailed.txt` - 包含文件大小和修改时间的详细结构 +- `project_directories.txt` - 仅包含目录结构的简化版本 (1124 行) + +## 📚 重要文档 + +### 🚀 Android 性能监控核心文档 +- **`aidocs/sentry-android-performance-monitoring.md`** - **Android 性能监控完整指南** ⭐ + - 整合了启动监控、UI 卡顿监控、网络监控和性能分析 + - 包含实用的配置示例和最佳实践 + - 专为 Android 开发者和性能工程师设计 + +- **`aidocs/sentry-trace-operations-guide.md`** - **Trace 操作字段监控机制详解** ⭐ + - 详细说明 Sentry 管理后台中各种 Trace 数据字段 + - 包含 ui.load.initial_display、ui.load.full_display 等关键指标 + - 涵盖 http.client/server、db.query、file.read/write、graphql.operation 等操作类型 + - 解释监控时机、触发条件和实现原理 + - 包含性能指标 (Measurements) 完整说明和最佳实践 + - 适用于所有需要理解监控数据的开发者和运维人员 + +### 📖 详细技术文档 +- **`aidocs/README.md`** - 文档导航和使用指南 +- **`aidocs/sentry-startup-monitoring.md`** - 启动性能监控详细分析 +- **`aidocs/sentry-ui-jank-monitoring.md`** - UI 卡顿监控机制 +- **`aidocs/sentry-network-monitoring.md`** - 网络性能监控 +- **`aidocs/sentry-profiling-analysis.md`** - 性能分析和方法跟踪 + +## 主要模块 + +### 核心模块 +- **sentry** - 核心 Java SDK,包含基础功能 +- **sentry-android-core** - Android 核心功能 +- **sentry-android** - Android SDK 主模块 +- **sentry-android-fragment** - Android Fragment 集成 + +### 集成测试模块 +- **sentry-android-integration-tests** - Android 集成测试 + - `sentry-uitest-android` - UI 测试 + - `sentry-uitest-android-benchmark` - 性能基准测试 + - `sentry-uitest-android-critical` - 关键功能测试 + - `test-app-plain` - 普通测试应用 + +### 其他重要模块 +- **sentry-apollo** - Apollo GraphQL 集成 +- **sentry-apollo3** - Apollo 3 集成 +- **sentry-bom** - Bill of Materials +- **sentry-compose** - Jetpack Compose 集成 +- **sentry-jdbc** - JDBC 集成 +- **sentry-jul** - Java Util Logging 集成 +- **sentry-kotlin-extensions** - Kotlin 扩展 +- **sentry-log4j2** - Log4j2 集成 +- **sentry-logback** - Logback 集成 +- **sentry-okhttp** - OkHttp 集成 +- **sentry-opentelemetry** - OpenTelemetry 集成 +- **sentry-servlet** - Servlet 集成 +- **sentry-spring** - Spring 集成 +- **sentry-spring-boot** - Spring Boot 集成 +- **sentry-spring-jakarta** - Spring Jakarta 集成 + +## 构建和配置 +- **buildSrc** - Gradle 构建脚本 +- **gradle** - Gradle 配置 +- **scripts** - 构建和维护脚本 + +## 文档和工具 +- **aidocs** - AI 文档和架构图 +- **docs** - 项目文档 +- **.github** - GitHub 工作流和模板 + +## Android 性能监控核心功能 + +### 🚀 启动性能监控 +- **冷启动监控**: 进程创建到首帧绘制的完整流程 +- **热启动监控**: Activity 重启的性能分析 +- **关键指标**: TTID (Time To Initial Display)、TTFD (Time To Full Display) +- **自动检测**: 启动类型自动识别和分类 + +### 🎨 UI 性能监控 +- **帧率监控**: 实时帧率检测和分析 +- **卡顿检测**: 慢帧和冻结帧自动识别 +- **双重策略**: Performance V1 (Activity级) + V2 (Span级) +- **多设备支持**: 60fps/90fps/120fps 设备适配 + +### 🌐 网络性能监控 +- **HTTP 监控**: OkHttp 拦截器自动集成 +- **性能指标**: DNS、连接、SSL、传输时间 +- **错误捕获**: 4xx/5xx 状态码自动捕获 +- **分布式追踪**: 跨服务请求链路追踪 + +### 📊 性能分析 (Profiling) +- **方法跟踪**: Android Debug API 方法调用分析 +- **资源监控**: CPU、内存使用情况 +- **瓶颈识别**: 性能热点自动识别 +- **智能采样**: 可配置的采样策略 + +## 主要功能领域 + +### 错误监控 +- 异常捕获和报告 +- 崩溃恢复 +- ANR (Application Not Responding) 检测 +- 原生崩溃处理 + +### 性能监控 +- 启动时间监控 +- UI 卡顿监控 +- 网络请求监控 +- 自定义性能指标 + +### 会话管理 +- 用户会话跟踪 +- 会话状态管理 + +### 重放功能 +- 用户交互重放 +- 错误重现 + +## 技术栈 +- Java/Kotlin +- Android SDK +- Gradle 构建系统 +- 多种第三方库集成 (Spring, OkHttp, Apollo 等) + +## 快速开始 (Android 性能监控) + +```kotlin +SentryAndroid.init(this) { options -> + options.dsn = "YOUR_DSN" + + // 启用 Android 性能监控 + options.isEnableAppStartTracking = true // 启动监控 + options.isEnableFramesTracking = true // UI 监控 + options.isEnablePerformanceV2 = true // 精确监控 + options.captureFailedRequests = true // 网络监控 + + // 设置采样率 + options.tracesSampleRate = 0.1 // 生产环境 10% + options.profilesSampleRate = 0.1 // 生产环境 10% +} +``` + +## 文件统计 +- 总文件数: 3306+ 个文件 +- 主要目录数: 1124+ 个目录 +- 支持多种集成和框架 + +## 推荐阅读路径 + +### 对于 Android 开发者 +1. **首先阅读**: `aidocs/sentry-android-performance-monitoring.md` - Android 性能监控完整指南 +2. 了解启动性能优化策略 +3. 掌握 UI 卡顿检测和优化 +4. 学习网络性能监控配置 + +### 对于性能工程师 +1. 重点关注性能分析 (Profiling) 部分 +2. 学习各种性能指标的含义和优化策略 +3. 配置智能采样和监控策略 + +这个项目结构展示了一个成熟的、功能丰富的监控 SDK,特别是在 Android 平台的性能监控方面提供了全面的解决方案。 \ No newline at end of file diff --git a/aidocs/README.md b/aidocs/README.md index 69400bc6fa..fb9cba64a0 100644 --- a/aidocs/README.md +++ b/aidocs/README.md @@ -1,132 +1,174 @@ # Sentry Java SDK 分析文档 -本文件夹包含了对 Sentry Java SDK 初始化流程的深入分析和可视化文档。 +本文件夹包含了对 Sentry Java SDK 的深入分析和可视化文档,重点关注 Android 平台的性能监控机制。 ## 📁 文档结构 +### 🚀 [Android 性能监控完整指南](./sentry-android-performance-monitoring.md) ⭐ +- **适用对象**: Android 开发者、性能工程师 +- **内容**: Sentry Android SDK 性能监控的完整解决方案 +- **包含**: + - 启动性能监控 (冷启动/热启动) + - UI 性能监控 (帧率/卡顿检测) + - 网络性能监控 (HTTP 请求分析) + - 性能分析 (Profiling) + - 最佳实践和故障排查 + +### 📊 [Trace 操作字段监控机制详解](./sentry-trace-operations-guide.md) ⭐ +- **适用对象**: 所有开发者、运维人员 +- **内容**: Sentry 管理后台中各种 Trace 数据字段的详细说明 +- **包含**: + - ui.load.initial_display (TTID) 监控机制 + - ui.load.full_display (TTFD) 监控机制 + - app.start.cold/warm 启动监控 + - http.client/http.server 网络请求监控 + - db.query/db.sql.query 数据库操作监控 + - file.read/file.write 文件IO监控 + - graphql.operation/graphql.fetcher GraphQL监控 + - bean Spring框架集成监控 + - 自定义 Span 创建和管理 + - 监控时机和触发条件详解 + - 性能指标 (Measurements) 完整说明 + ### 🎯 [快速参考](./sentry-init-quick-reference.md) - **适用对象**: 开发者快速查阅 - **内容**: 核心初始化步骤、关键组件、最佳实践 - **特点**: 简洁明了,包含常见问题解答 -### 📊 [初始化流程时序图](./sentry-initialization-flow.md) +### 📊 [初始化流程详解](./sentry-initialization-flow.md) - **适用对象**: 架构师、高级开发者 -- **内容**: 完整的初始化流程可视化 -- **包含**: - - 核心初始化流程时序图 - - Android 特定初始化流程 - - 集成注册流程 - - 客户端创建流程 - - 配置加载流程 - -### 🔍 [详细实现说明](./sentry-initialization-details.md) -- **适用对象**: SDK 贡献者、深度定制需求 -- **内容**: 设计决策和实现细节 -- **包含**: - - 线程安全机制 - - 配置优先级策略 - - 平台检测和适配 - - Scopes 架构设计 - - 集成系统设计 - - 传输层设计 - - 错误处理策略 - - 性能优化 - - 内存管理 - - 扩展点设计 - -### 💥 [崩溃监控机制](./sentry-crash-monitoring.md) -- **适用对象**: 所有开发者、运维人员 -- **内容**: 全面的崩溃监控机制分析 -- **包含**: - - Java 未捕获异常监控 - - Android ANR 检测机制 - - Native 崩溃处理 - - 启动崩溃检测 - - 事件处理流程 - - 传输和持久化 - - 性能优化策略 - - 配置和最佳实践 - - 故障排查指南 - -### 🚀 [启动监控机制](./sentry-startup-monitoring.md) -- **适用对象**: Android 开发者、性能工程师 -- **内容**: 冷启动和热启动监控的完整分析 -- **包含**: - - 启动类型检测机制 - - 时间跨度测量体系 - - 冷启动详细监控 - - 热启动监控机制 - - TTID/TTFD 性能指标 - - 性能数据收集上报 - - 启动性能分析 - - 配置和最佳实践 - - 故障排查指南 - -### 🎨 [UI 卡顿监控机制](./sentry-ui-jank-monitoring.md) -- **适用对象**: Android 开发者、UI/UX 工程师 -- **内容**: UI 卡顿和帧率监控的深度分析 -- **包含**: - - 双重帧率监控策略 - - 慢帧和冻结帧检测 - - Performance V1 vs V2 对比 - - 实时帧数据收集 - - Span 级别帧分析 - - 帧插值和补偿机制 - - 性能指标和阈值 - - 配置和最佳实践 - - 故障排查指南 - -### 📱 [会话管理机制](./sentry-session-management.md) -- **适用对象**: 所有开发者、产品经理 -- **内容**: 用户会话生命周期和状态管理的完整分析 -- **包含**: - - 会话数据结构和状态枚举 - - 会话生命周期管理 - - 前台后台切换处理 - - 会话持久化机制 - - 异常状态更新 - - 性能优化策略 - - 配置和最佳实践 - - 故障排查指南 - -### 🎬 [Replay 功能分析](./sentry-replay-analysis.md) -- **适用对象**: Android 开发者、QA 工程师 -- **内容**: Session Replay 屏幕录制和回放功能的深度分析 -- **包含**: - - 录制策略架构(全会话 vs 错误缓冲) - - 屏幕截图和视频编码 - - 缓存机制和存储优化 - - 事件捕获和同步 - - MP4 生成和压缩 - - 采样决策和性能影响 - - 配置和最佳实践 - - 故障排查指南 - -### 📊 [Profiling 性能分析](./sentry-profiling-analysis.md) -- **适用对象**: 性能工程师、高级开发者 -- **内容**: 方法调用跟踪和性能瓶颈识别的完整分析 -- **包含**: - - 事务性能分析 vs 连续性能分析 - - 方法调用跟踪(基于 Android Debug API) - - CPU、内存、帧率等多维度性能数据 - - 性能数据编码和传输 - - 智能采样策略 - - 设备信息集成 - - 配置和最佳实践 - - 故障排查指南 - -### 🌐 [网络监控机制](./sentry-network-monitoring.md) -- **适用对象**: 网络工程师、后端开发者 -- **内容**: HTTP 请求监控和网络性能分析的深度解析 -- **包含**: - - OkHttp 拦截器和事件监听器 - - 网络性能指标收集(DNS、连接、SSL等) - - HTTP 错误捕获和分析 - - 分布式追踪集成 - - Apollo GraphQL 支持 - - 面包屑记录和错误事件 - - 配置和最佳实践 - - 故障排查指南 +- **内容**: 完整的初始化流程可视化和详细说明 +- **包含**: 时序图、流程分析、架构设计 + +## 🎯 Android 性能监控核心功能 + +### 🚀 启动性能监控 +- **冷启动监控**: 进程创建到首帧绘制的完整流程 +- **热启动监控**: Activity 重启的性能分析 +- **关键指标**: TTID、TTFD、启动时间 +- **自动检测**: 启动类型自动识别和分类 + +### 🎨 UI 性能监控 +- **帧率监控**: 实时帧率检测和分析 +- **卡顿检测**: 慢帧和冻结帧自动识别 +- **双重策略**: Performance V1 (Activity级) + V2 (Span级) +- **多设备支持**: 60fps/90fps/120fps 设备适配 + +### 🌐 网络性能监控 +- **HTTP 监控**: OkHttp 拦截器自动集成 +- **性能指标**: DNS、连接、SSL、传输时间 +- **错误捕获**: 4xx/5xx 状态码自动捕获 +- **分布式追踪**: 跨服务请求链路追踪 + +### 📊 性能分析 (Profiling) +- **方法跟踪**: Android Debug API 方法调用分析 +- **资源监控**: CPU、内存使用情况 +- **瓶颈识别**: 性能热点自动识别 +- **智能采样**: 可配置的采样策略 + +## 🔧 快速开始 + +### 基础配置 +```kotlin +SentryAndroid.init(this) { options -> + options.dsn = "YOUR_DSN" + + // 启用 Android 性能监控 + options.isEnableAppStartTracking = true // 启动监控 + options.isEnableFramesTracking = true // UI 监控 + options.isEnablePerformanceV2 = true // 精确监控 + options.captureFailedRequests = true // 网络监控 + + // 设置采样率 + options.tracesSampleRate = 0.1 // 生产环境 10% + options.profilesSampleRate = 0.1 // 生产环境 10% +} +``` + +### 网络监控集成 +```kotlin +val client = OkHttpClient.Builder() + .addInterceptor(SentryOkHttpInterceptor()) + .build() +``` + +## 📈 性能指标概览 + +| 监控类型 | 关键指标 | 建议阈值 | +|----------|----------|----------| +| **启动性能** | TTID | < 1.5s | +| **启动性能** | 冷启动时间 | < 2s | +| **UI 性能** | 慢帧率 | < 5% | +| **UI 性能** | 冻结帧率 | < 1% | +| **网络性能** | 请求成功率 | > 95% | +| **网络性能** | 平均响应时间 | < 2s | + +## 🎨 架构图说明 + +文档中使用了 Mermaid 图表来可视化复杂的监控流程: + +```mermaid +graph LR + A[Android 应用] --> B[Sentry SDK] + B --> C[启动监控] + B --> D[UI 监控] + B --> E[网络监控] + B --> F[性能分析] + + style C fill:#e1f5fe + style D fill:#f3e5f5 + style E fill:#fff3e0 + style F fill:#e8f5e8 +``` + +## 🔧 使用建议 + +### 对于 Android 开发者 (推荐路径) +1. **首先阅读** [Android 性能监控完整指南](./sentry-android-performance-monitoring.md) +2. **深入了解** [Trace 操作字段监控机制详解](./sentry-trace-operations-guide.md) - 理解管理后台中的各种数据字段 +3. 了解启动性能优化策略 +4. 掌握 UI 卡顿检测和优化 +5. 学习网络性能监控配置 +6. 配置合适的性能监控参数 + +### 对于性能工程师 +1. 重点关注 [Android 性能监控完整指南](./sentry-android-performance-monitoring.md) 中的性能分析部分 +2. **必读** [Trace 操作字段监控机制详解](./sentry-trace-operations-guide.md) - 掌握各种性能指标的含义 +3. 学习各种性能指标的含义和优化策略 +4. 配置智能采样和监控策略 +5. 建立性能监控告警体系 + +### 对于应用开发者 +1. 先阅读 [快速参考](./sentry-init-quick-reference.md) 了解基础配置 +2. 参考 [Android 性能监控完整指南](./sentry-android-performance-monitoring.md) 进行性能优化 +3. 查看 [Trace 操作字段监控机制详解](./sentry-trace-operations-guide.md) 理解监控数据 +4. 遇到问题时查看故障排查部分 + +### 对于架构师 +1. 了解 [初始化流程详解](./sentry-initialization-flow.md) 掌握整体架构 +2. 参考 [Android 性能监控完整指南](./sentry-android-performance-monitoring.md) 设计监控策略 +3. 学习 [Trace 操作字段监控机制详解](./sentry-trace-operations-guide.md) 制定监控标准 +4. 制定团队的性能监控标准和流程 + +## 📚 详细文档索引 + +### 核心监控机制 +- [💥 崩溃监控机制](./sentry-crash-monitoring.md) - 异常捕获和处理 +- [🚀 启动监控机制](./sentry-startup-monitoring.md) - 启动性能详细分析 +- [🎨 UI 卡顿监控机制](./sentry-ui-jank-monitoring.md) - 帧率和卡顿检测 +- [🌐 网络监控机制](./sentry-network-monitoring.md) - HTTP 请求监控 +- [📊 Profiling 性能分析](./sentry-profiling-analysis.md) - 方法调用跟踪 + +### 功能分析 +- [📱 会话管理机制](./sentry-session-management.md) - 用户会话追踪 +- [🎬 Replay 功能分析](./sentry-replay-analysis.md) - 屏幕录制回放 + +### 技术细节 +- [🔍 初始化详细说明](./sentry-initialization-details.md) - 深度技术实现 + +## 🎯 重点推荐 + +对于 Android 开发者,我们强烈推荐从 **[Android 性能监控完整指南](./sentry-android-performance-monitoring.md)** 开始,这是一个整合了所有 Android 性能监控功能的完整解决方案,包含了实用的配置示例和最佳实践。 ## 🎨 时序图说明 diff --git a/aidocs/mermaid/sentry-trace-operations-database-monitoring.mmd b/aidocs/mermaid/sentry-trace-operations-database-monitoring.mmd new file mode 100644 index 0000000000..e65c62fee1 --- /dev/null +++ b/aidocs/mermaid/sentry-trace-operations-database-monitoring.mmd @@ -0,0 +1,56 @@ +sequenceDiagram + participant App as 应用程序 + participant JDBC as JDBC Driver + participant P6Spy as P6Spy 拦截器 + participant Listener as SentryJdbcEventListener + participant Sentry as Sentry SDK + participant DB as 数据库 + participant Backend as Sentry 后台 + + Note over App: 执行数据库操作 + App->>JDBC: executeQuery(sql) + JDBC->>P6Spy: 拦截 SQL 执行 + + Note over P6Spy: 执行前处理 + P6Spy->>Listener: onBeforeAnyExecute(statementInfo) + Listener->>Sentry: 获取当前 Span + Sentry-->>Listener: 返回父 Span + + alt 存在父 Span + Listener->>Listener: 创建 SpanOptions + Note over Listener: 设置 origin = "auto.db.jdbc" + Listener->>Sentry: parent.startChild("db.query", sql, options) + Sentry-->>Listener: 返回 db.query Span + Listener->>Listener: 存储到 ThreadLocal + end + + Note over P6Spy: 执行 SQL + P6Spy->>DB: 实际执行 SQL 查询 + activate DB + Note over DB: 查询处理
- 解析 SQL
- 索引查找
- 数据检索
- 结果组装 + DB-->>P6Spy: 返回查询结果 + deactivate DB + + Note over P6Spy: 执行后处理 + P6Spy->>Listener: onAfterAnyExecute(statementInfo, timeElapsed, exception) + Listener->>Listener: 从 ThreadLocal 获取 Span + + alt Span 存在 + Listener->>Listener: applyDatabaseDetailsToSpan() + Note over Listener: 设置数据库信息
- db.system (postgresql/mysql)
- db.name (数据库名) + + alt 执行成功 + Listener->>Sentry: span.setStatus(SpanStatus.OK) + else 执行异常 + Listener->>Sentry: span.setThrowable(exception) + Listener->>Sentry: span.setStatus(SpanStatus.INTERNAL_ERROR) + end + + Listener->>Sentry: span.finish() + Listener->>Listener: 清除 ThreadLocal + + Note over Sentry: 上报 Span 数据 + Sentry->>Backend: 发送 db.query Span + end + + P6Spy-->>App: 返回查询结果 \ No newline at end of file diff --git a/aidocs/mermaid/sentry-trace-operations-file-io-monitoring.mmd b/aidocs/mermaid/sentry-trace-operations-file-io-monitoring.mmd new file mode 100644 index 0000000000..16efc8dc98 --- /dev/null +++ b/aidocs/mermaid/sentry-trace-operations-file-io-monitoring.mmd @@ -0,0 +1,68 @@ +sequenceDiagram + participant App as 应用程序 + participant SentryFile as SentryFileInputStream + participant SpanManager as FileIOSpanManager + participant Sentry as Sentry SDK + participant FileSystem as 文件系统 + participant Backend as Sentry 后台 + + Note over App: 文件读取操作 + App->>SentryFile: new SentryFileInputStream(file) + + Note over SentryFile: 初始化阶段 + SentryFile->>SpanManager: startSpan(scopes, "file.read") + SpanManager->>Sentry: 获取当前 Span + Sentry-->>SpanManager: 返回父 Span + + alt 存在父 Span 且启用追踪 + SpanManager->>Sentry: parent.startChild("file.read") + Sentry-->>SpanManager: 返回 file.read Span + SpanManager->>Sentry: span.setData("file.path", file.getAbsolutePath()) + Note over SpanManager: 创建 FileIOSpanManager 实例 + else 未启用追踪 + Note over SpanManager: 返回 NoOp Span + end + + SentryFile-->>App: 返回 InputStream 实例 + + Note over App: 执行读取操作 + loop 多次读取 + App->>SentryFile: read() / read(buffer) + SentryFile->>SpanManager: performIO(() -> delegate.read()) + + activate SpanManager + Note over SpanManager: 执行 IO 操作并计算字节数 + SpanManager->>FileSystem: 实际文件读取 + activate FileSystem + Note over FileSystem: 磁盘 I/O 操作
- 文件定位
- 数据读取
- 缓冲区填充 + FileSystem-->>SpanManager: 返回读取字节数 + deactivate FileSystem + + SpanManager->>SpanManager: 累计读取字节数 + deactivate SpanManager + + SentryFile-->>App: 返回读取数据 + end + + Note over App: 关闭文件 + App->>SentryFile: close() + SentryFile->>SpanManager: finish(delegate) + + alt Span 存在 + SpanManager->>Sentry: span.setData("file.size", totalBytesRead) + + alt IO 操作成功 + SpanManager->>Sentry: span.setStatus(SpanStatus.OK) + else IO 异常 + SpanManager->>Sentry: span.setThrowable(ioException) + SpanManager->>Sentry: span.setStatus(SpanStatus.INTERNAL_ERROR) + end + + SpanManager->>Sentry: span.finish() + + Note over Sentry: 上报 Span 数据 + Sentry->>Backend: 发送 file.read Span + end + + SentryFile->>FileSystem: delegate.close() + SentryFile-->>App: 文件关闭完成 \ No newline at end of file diff --git a/aidocs/mermaid/sentry-trace-operations-graphql-monitoring.mmd b/aidocs/mermaid/sentry-trace-operations-graphql-monitoring.mmd new file mode 100644 index 0000000000..1ae686e40e --- /dev/null +++ b/aidocs/mermaid/sentry-trace-operations-graphql-monitoring.mmd @@ -0,0 +1,84 @@ +sequenceDiagram + participant Client as GraphQL 客户端 + participant Engine as GraphQL 执行引擎 + participant Instrumentation as SentryGraphqlInstrumentation + participant Fetcher as SentryDataFetcher + participant Sentry as Sentry SDK + participant Resolver as 字段解析器 + participant Backend as Sentry 后台 + + Note over Client: GraphQL 查询请求 + Client->>Engine: 执行 GraphQL 查询 + + Note over Engine: 开始执行阶段 + Engine->>Instrumentation: beginExecution(parameters) + Instrumentation->>Instrumentation: 解析操作信息 + Note over Instrumentation: operationName = parameters.getOperation().getName()
operationType = parameters.getOperation().getOperation().name() + + Instrumentation->>Sentry: 获取当前 Span + Sentry-->>Instrumentation: 返回活跃 Span + + alt 存在活跃 Span + Instrumentation->>Sentry: activeSpan.startChild("graphql.operation", operationType + " " + operationName) + Sentry-->>Instrumentation: 返回 graphql.operation Span + Note over Instrumentation: 设置操作标签
- operation.type
- operation.name + end + + Note over Engine: 字段解析阶段 + loop 每个字段 + Engine->>Fetcher: get(DataFetchingEnvironment) + Fetcher->>Fetcher: 获取字段信息 + Note over Fetcher: fieldName = environment.getField().getName() + + Fetcher->>Sentry: 获取当前 Span + Sentry-->>Fetcher: 返回活跃 Span + + alt 存在活跃 Span + Fetcher->>Sentry: activeSpan.startChild("graphql.fetcher", fieldName) + Sentry-->>Fetcher: 返回 graphql.fetcher Span + Note over Fetcher: 设置字段标签
- field.name
- field.type + end + + Note over Fetcher: 执行字段解析 + Fetcher->>Resolver: delegate.get(environment) + activate Resolver + Note over Resolver: 业务逻辑处理
- 数据查询
- 计算处理
- 结果转换 + + alt 解析成功 + Resolver-->>Fetcher: 返回字段值 + Fetcher->>Sentry: span.setStatus(SpanStatus.OK) + else 解析异常 + Resolver-->>Fetcher: 抛出异常 + Fetcher->>Sentry: span.setThrowable(exception) + Fetcher->>Sentry: span.setStatus(SpanStatus.INTERNAL_ERROR) + end + deactivate Resolver + + alt Span 存在 + Fetcher->>Sentry: span.finish() + Note over Sentry: 上报字段解析 Span + Sentry->>Backend: 发送 graphql.fetcher Span + end + + Fetcher-->>Engine: 返回字段结果 + end + + Note over Engine: 执行完成阶段 + Engine->>Instrumentation: onCompleted(result, throwable) + + alt 执行成功 + Instrumentation->>Sentry: span.setStatus(SpanStatus.OK) + Note over Instrumentation: 设置成功指标
- fields.resolved
- execution.time + else 执行异常 + Instrumentation->>Sentry: span.setThrowable(throwable) + Instrumentation->>Sentry: span.setStatus(SpanStatus.INTERNAL_ERROR) + Note over Instrumentation: 设置错误信息
- error.type
- error.message + end + + alt Span 存在 + Instrumentation->>Sentry: span.finish() + Note over Sentry: 上报操作 Span + Sentry->>Backend: 发送 graphql.operation Span + end + + Engine-->>Client: 返回 GraphQL 响应 \ No newline at end of file diff --git a/aidocs/mermaid/sentry-trace-operations-http-server-monitoring.mmd b/aidocs/mermaid/sentry-trace-operations-http-server-monitoring.mmd new file mode 100644 index 0000000000..97b1d4f8d1 --- /dev/null +++ b/aidocs/mermaid/sentry-trace-operations-http-server-monitoring.mmd @@ -0,0 +1,94 @@ +sequenceDiagram + participant Client as HTTP 客户端 + participant Server as Web 服务器 + participant Filter as SentryTracingFilter + participant Sentry as Sentry SDK + participant Controller as Spring Controller + participant Service as 业务服务 + participant Backend as Sentry 后台 + + Note over Client: HTTP 请求 + Client->>Server: POST /api/users + + Note over Server: 请求拦截处理 + Server->>Filter: doFilter(request, response, chain) + Filter->>Filter: 解析请求信息 + Note over Filter: method = request.getMethod()
uri = request.getRequestURI()
userAgent = request.getHeader("User-Agent") + + Filter->>Sentry: 创建事务上下文 + Note over Filter: transactionContext:
- name: "POST /api/users"
- operation: "http.server"
- source: URL + + Filter->>Sentry: scopes.startTransaction(transactionContext, options) + Sentry-->>Filter: 返回 http.server Transaction + + Note over Filter: 设置请求标签和数据 + Filter->>Sentry: transaction.setTag("http.method", "POST") + Filter->>Sentry: transaction.setTag("http.url", "/api/users") + Filter->>Sentry: transaction.setTag("http.user_agent", userAgent) + Filter->>Sentry: transaction.setData("http.query", queryString) + Filter->>Sentry: transaction.setData("http.fragment", fragment) + + alt 用户信息可用 + Filter->>Filter: 提取用户信息 + Filter->>Sentry: transaction.setUser(userInfo) + Note over Filter: 设置用户上下文
- user.id
- user.username
- user.ip_address + end + + Note over Filter: 执行请求链 + Filter->>Controller: 继续过滤器链 + activate Controller + + Note over Controller: Spring MVC 处理 + Controller->>Controller: 路由匹配和参数绑定 + Note over Controller: @PostMapping("/api/users")
public User createUser(@RequestBody CreateUserRequest request) + + Controller->>Service: userService.createUser(request) [@SentryTransaction] + activate Service + + Note over Service: 业务逻辑处理 + Service->>Service: 验证请求参数 + Service->>Service: 创建用户实体 + Service->>Service: 保存到数据库 + + alt 创建成功 + Service-->>Controller: 返回创建的用户 + deactivate Service + Controller->>Controller: 构造响应 + Note over Controller: ResponseEntity.ok(user) + else 创建失败 + Service-->>Controller: 抛出业务异常 + deactivate Service + Controller->>Controller: 异常处理 + Note over Controller: @ExceptionHandler
ResponseEntity.badRequest() + end + + Controller-->>Filter: 返回响应结果 + deactivate Controller + + Note over Filter: 响应处理 + Filter->>Filter: 获取响应状态 + Note over Filter: status = response.getStatus()
contentLength = response.getContentLength() + + Filter->>Sentry: 设置响应标签 + Filter->>Sentry: transaction.setTag("http.status_code", status) + Filter->>Sentry: transaction.setData("http.response.content_length", contentLength) + + alt 请求成功 (2xx) + Filter->>Sentry: transaction.setStatus(SpanStatus.OK) + Note over Filter: 设置成功指标
- request.processed: true
- response.size: contentLength + else 客户端错误 (4xx) + Filter->>Sentry: transaction.setStatus(SpanStatus.INVALID_ARGUMENT) + Note over Filter: 设置错误信息
- error.type: "ClientError"
- error.code: status + else 服务端错误 (5xx) + Filter->>Sentry: transaction.setStatus(SpanStatus.INTERNAL_ERROR) + Filter->>Sentry: transaction.setThrowable(exception) + Note over Filter: 设置错误信息
- error.type: "ServerError"
- error.message: exception.getMessage() + end + + Note over Filter: 完成事务 + Filter->>Sentry: transaction.finish() + Note over Sentry: 上报事务数据 + Sentry->>Backend: 发送 http.server Transaction 及所有子 Span + + Filter-->>Server: 继续响应处理 + Server-->>Client: HTTP 响应 \ No newline at end of file diff --git a/aidocs/mermaid/sentry-trace-operations-spring-bean-monitoring.mmd b/aidocs/mermaid/sentry-trace-operations-spring-bean-monitoring.mmd new file mode 100644 index 0000000000..87ec57ee52 --- /dev/null +++ b/aidocs/mermaid/sentry-trace-operations-spring-bean-monitoring.mmd @@ -0,0 +1,91 @@ +sequenceDiagram + participant Client as 客户端代码 + participant Proxy as Spring AOP 代理 + participant TransactionAdvice as SentryTransactionAdvice + participant SpanAdvice as SentrySpanAdvice + participant Sentry as Sentry SDK + participant Bean as 目标 Bean + participant Backend as Sentry 后台 + + Note over Client: 调用 @SentryTransaction 方法 + Client->>Proxy: userService.createUser(request) + + Note over Proxy: AOP 拦截处理 + Proxy->>TransactionAdvice: 拦截方法调用 + TransactionAdvice->>TransactionAdvice: 解析注解信息 + Note over TransactionAdvice: sentryTransaction = method.getAnnotation(SentryTransaction.class)
operation = sentryTransaction.operation() ?: "bean" + + TransactionAdvice->>Sentry: 创建分叉作用域 + Sentry-->>TransactionAdvice: 返回 forkedScopes + + Note over TransactionAdvice: 创建事务 + TransactionAdvice->>Sentry: forkedScopes.startTransaction(transactionContext, options) + Note over TransactionAdvice: transactionContext:
- name: "UserService.createUser"
- operation: "bean"
- source: COMPONENT + Sentry-->>TransactionAdvice: 返回 Transaction + + TransactionAdvice->>Sentry: 绑定到作用域 + Note over Sentry: 设置事务标签
- component.type: "spring.bean"
- method.name: "createUser" + + Note over TransactionAdvice: 执行目标方法 + TransactionAdvice->>Bean: 调用实际业务方法 + activate Bean + + Note over Bean: 业务方法内部可能调用其他方法 + Bean->>Proxy: validateUser(user) [@SentrySpan] + Proxy->>SpanAdvice: 拦截 Span 方法 + + SpanAdvice->>Sentry: 获取当前活跃 Span + Sentry-->>SpanAdvice: 返回当前 Transaction + + SpanAdvice->>SpanAdvice: 解析 Span 注解 + Note over SpanAdvice: sentrySpan = method.getAnnotation(SentrySpan.class)
operation = sentrySpan.operation() ?: "UserService.validateUser" + + SpanAdvice->>Sentry: activeSpan.startChild(operation, description, options) + Sentry-->>SpanAdvice: 返回子 Span + + Note over SpanAdvice: 设置 Span 标签 + SpanAdvice->>Sentry: span.setTag("method.class", "UserService") + SpanAdvice->>Sentry: span.setTag("method.name", "validateUser") + + SpanAdvice->>Bean: 执行验证逻辑 + Note over Bean: 用户验证处理
- 参数校验
- 业务规则检查
- 数据验证 + + alt 验证成功 + Bean-->>SpanAdvice: 验证通过 + SpanAdvice->>Sentry: span.setStatus(SpanStatus.OK) + else 验证失败 + Bean-->>SpanAdvice: 抛出验证异常 + SpanAdvice->>Sentry: span.setThrowable(validationException) + SpanAdvice->>Sentry: span.setStatus(SpanStatus.INVALID_ARGUMENT) + end + + SpanAdvice->>Sentry: span.finish() + Note over Sentry: 上报验证 Span + Sentry->>Backend: 发送 validation Span + + SpanAdvice-->>Bean: 返回验证结果 + + Note over Bean: 继续业务处理 + Bean->>Bean: 创建用户逻辑 + Note over Bean: 用户创建处理
- 数据持久化
- 事件发布
- 缓存更新 + + alt 创建成功 + Bean-->>TransactionAdvice: 返回创建的用户 + TransactionAdvice->>Sentry: transaction.setStatus(SpanStatus.OK) + Note over TransactionAdvice: 设置成功指标
- user.created: true
- execution.time: duration + else 创建失败 + Bean-->>TransactionAdvice: 抛出业务异常 + TransactionAdvice->>Sentry: transaction.setThrowable(businessException) + TransactionAdvice->>Sentry: transaction.setStatus(SpanStatus.INTERNAL_ERROR) + Note over TransactionAdvice: 设置错误信息
- error.type: "BusinessException"
- error.message: exception.getMessage() + end + + deactivate Bean + + Note over TransactionAdvice: 完成事务 + TransactionAdvice->>Sentry: transaction.finish() + Note over Sentry: 上报事务数据 + Sentry->>Backend: 发送 bean Transaction 及所有子 Span + + TransactionAdvice->>Sentry: 清理作用域 + TransactionAdvice-->>Client: 返回方法结果 \ No newline at end of file diff --git a/aidocs/mermaid/svg/sentry-trace-operations-database-monitoring.svg b/aidocs/mermaid/svg/sentry-trace-operations-database-monitoring.svg new file mode 100644 index 0000000000..7c7fe96cfa --- /dev/null +++ b/aidocs/mermaid/svg/sentry-trace-operations-database-monitoring.svg @@ -0,0 +1 @@ +Sentry 后台数据库Sentry SDKSentryJdbcEventListenerP6Spy 拦截器JDBC Driver应用程序Sentry 后台数据库Sentry SDKSentryJdbcEventListenerP6Spy 拦截器JDBC Driver应用程序执行数据库操作执行前处理设置 origin = "auto.db.jdbc"alt[存在父 Span]执行 SQL查询处理- 解析 SQL- 索引查找- 数据检索- 结果组装执行后处理设置数据库信息- db.system (postgresql/mysql)- db.name (数据库名)alt[执行成功][执行异常]上报 Span 数据alt[Span 存在]executeQuery(sql)拦截 SQL 执行onBeforeAnyExecute(statementInfo)获取当前 Span返回父 Span创建 SpanOptionsparent.startChild("db.query", sql, options)返回 db.query Span存储到 ThreadLocal实际执行 SQL 查询返回查询结果onAfterAnyExecute(statementInfo, timeElapsed, exception)从 ThreadLocal 获取 SpanapplyDatabaseDetailsToSpan()span.setStatus(SpanStatus.OK)span.setThrowable(exception)span.setStatus(SpanStatus.INTERNAL_ERROR)span.finish()清除 ThreadLocal发送 db.query Span返回查询结果 \ No newline at end of file diff --git a/aidocs/mermaid/svg/sentry-trace-operations-file-io-monitoring.svg b/aidocs/mermaid/svg/sentry-trace-operations-file-io-monitoring.svg new file mode 100644 index 0000000000..d5bdabca7f --- /dev/null +++ b/aidocs/mermaid/svg/sentry-trace-operations-file-io-monitoring.svg @@ -0,0 +1 @@ +Sentry 后台文件系统Sentry SDKFileIOSpanManagerSentryFileInputStream应用程序Sentry 后台文件系统Sentry SDKFileIOSpanManagerSentryFileInputStream应用程序文件读取操作初始化阶段创建 FileIOSpanManager 实例返回 NoOp Spanalt[存在父 Span 且启用追踪][未启用追踪]执行读取操作执行 IO 操作并计算字节数磁盘 I/O 操作- 文件定位- 数据读取- 缓冲区填充loop[多次读取]关闭文件alt[IO 操作成功][IO 异常]上报 Span 数据alt[Span 存在]new SentryFileInputStream(file)startSpan(scopes, "file.read")获取当前 Span返回父 Spanparent.startChild("file.read")返回 file.read Spanspan.setData("file.path", file.getAbsolutePath())返回 InputStream 实例read() / read(buffer)performIO(() -> delegate.read())实际文件读取返回读取字节数累计读取字节数返回读取数据close()finish(delegate)span.setData("file.size", totalBytesRead)span.setStatus(SpanStatus.OK)span.setThrowable(ioException)span.setStatus(SpanStatus.INTERNAL_ERROR)span.finish()发送 file.read Spandelegate.close()文件关闭完成 \ No newline at end of file diff --git a/aidocs/mermaid/svg/sentry-trace-operations-graphql-monitoring.svg b/aidocs/mermaid/svg/sentry-trace-operations-graphql-monitoring.svg new file mode 100644 index 0000000000..0c526473ba --- /dev/null +++ b/aidocs/mermaid/svg/sentry-trace-operations-graphql-monitoring.svg @@ -0,0 +1 @@ +Sentry 后台字段解析器Sentry SDKSentryDataFetcherSentryGraphqlInstrumentationGraphQL 执行引擎GraphQL 客户端Sentry 后台字段解析器Sentry SDKSentryDataFetcherSentryGraphqlInstrumentationGraphQL 执行引擎GraphQL 客户端GraphQL 查询请求开始执行阶段operationName = parameters.getOperation().getName()operationType = parameters.getOperation().getOperation().name()设置操作标签- operation.type- operation.namealt[存在活跃 Span]字段解析阶段fieldName = environment.getField().getName()设置字段标签- field.name- field.typealt[存在活跃 Span]执行字段解析业务逻辑处理- 数据查询- 计算处理- 结果转换alt[解析成功][解析异常]上报字段解析 Spanalt[Span 存在]loop[每个字段]执行完成阶段设置成功指标- fields.resolved- execution.time设置错误信息- error.type- error.messagealt[执行成功][执行异常]上报操作 Spanalt[Span 存在]执行 GraphQL 查询beginExecution(parameters)解析操作信息获取当前 Span返回活跃 SpanactiveSpan.startChild("graphql.operation", operationType + " " + operationName)返回 graphql.operation Spanget(DataFetchingEnvironment)获取字段信息获取当前 Span返回活跃 SpanactiveSpan.startChild("graphql.fetcher", fieldName)返回 graphql.fetcher Spandelegate.get(environment)返回字段值span.setStatus(SpanStatus.OK)抛出异常span.setThrowable(exception)span.setStatus(SpanStatus.INTERNAL_ERROR)span.finish()发送 graphql.fetcher Span返回字段结果onCompleted(result, throwable)span.setStatus(SpanStatus.OK)span.setThrowable(throwable)span.setStatus(SpanStatus.INTERNAL_ERROR)span.finish()发送 graphql.operation Span返回 GraphQL 响应 \ No newline at end of file diff --git a/aidocs/mermaid/svg/sentry-trace-operations-spring-bean-monitoring.svg b/aidocs/mermaid/svg/sentry-trace-operations-spring-bean-monitoring.svg new file mode 100644 index 0000000000..3606b2de3e --- /dev/null +++ b/aidocs/mermaid/svg/sentry-trace-operations-spring-bean-monitoring.svg @@ -0,0 +1 @@ +Sentry 后台目标 BeanSentry SDKSentrySpanAdviceSentryTransactionAdviceSpring AOP 代理客户端代码Sentry 后台目标 BeanSentry SDKSentrySpanAdviceSentryTransactionAdviceSpring AOP 代理客户端代码调用 @SentryTransaction 方法AOP 拦截处理sentryTransaction = method.getAnnotation(SentryTransaction.class)operation = sentryTransaction.operation() ?: "bean"创建事务transactionContext:- name: "UserService.createUser"- operation: "bean"- source: COMPONENT设置事务标签- component.type: "spring.bean"- method.name: "createUser"执行目标方法业务方法内部可能调用其他方法sentrySpan = method.getAnnotation(SentrySpan.class)operation = sentrySpan.operation() ?: "UserService.validateUser"设置 Span 标签用户验证处理- 参数校验- 业务规则检查- 数据验证alt[验证成功][验证失败]上报验证 Span继续业务处理用户创建处理- 数据持久化- 事件发布- 缓存更新设置成功指标- user.created: true- execution.time: duration设置错误信息- error.type: "BusinessException"- error.message: exception.getMessage()alt[创建成功][创建失败]完成事务上报事务数据userService.createUser(request)拦截方法调用解析注解信息创建分叉作用域返回 forkedScopesforkedScopes.startTransaction(transactionContext, options)返回 Transaction绑定到作用域调用实际业务方法validateUser(user) [@SentrySpan]拦截 Span 方法获取当前活跃 Span返回当前 Transaction解析 Span 注解activeSpan.startChild(operation, description, options)返回子 Spanspan.setTag("method.class", "UserService")span.setTag("method.name", "validateUser")执行验证逻辑验证通过span.setStatus(SpanStatus.OK)抛出验证异常span.setThrowable(validationException)span.setStatus(SpanStatus.INVALID_ARGUMENT)span.finish()发送 validation Span返回验证结果创建用户逻辑返回创建的用户transaction.setStatus(SpanStatus.OK)抛出业务异常transaction.setThrowable(businessException)transaction.setStatus(SpanStatus.INTERNAL_ERROR)transaction.finish()发送 bean Transaction 及所有子 Span清理作用域返回方法结果 \ No newline at end of file diff --git a/aidocs/rules/.cursorrules b/aidocs/rules/.cursorrules index d80350c9e1..d2b62ac7ac 100644 --- a/aidocs/rules/.cursorrules +++ b/aidocs/rules/.cursorrules @@ -34,7 +34,7 @@ aidocs/ 1. **文档格式标准**: - 使用中文作为主要语言 - 标题使用 `#` 层级结构,最多到 `###` - - 代码块使用 ```java 或 ```mermaid 标记 + - 代码块使用 ```java、```kotlin 或 ```mermaid 标记 - 文件路径使用 **粗体** 标记:`**文件路径**: path/to/file.java` - 类名、方法名使用反引号:`SentryClient`、`init()` @@ -164,7 +164,7 @@ mmdc -i filename.mmd -o svg/filename.svg -t dark 2. **Mermaid 语法**:确保时序图语法无误,可以正常渲染 3. **文件引用**:确保文档间的链接和引用正确 4. **中文表达**:确保技术术语的中文表达准确、一致 -5. **代码示例**:确保 Java 代码示例语法正确、可执行 +5. **代码示例**:确保 Java/Kotlin 代码示例语法正确、可执行 ## 💡 智能建议 diff --git a/aidocs/sentry-android-performance-monitoring.md b/aidocs/sentry-android-performance-monitoring.md new file mode 100644 index 0000000000..bd11fc1ff9 --- /dev/null +++ b/aidocs/sentry-android-performance-monitoring.md @@ -0,0 +1,357 @@ +# Sentry Android 性能监控完整指南 + +本文档详细介绍 Sentry Android SDK 的性能监控机制,包括启动监控、UI 卡顿监控、网络监控和性能分析等核心功能。 + +## 🎯 性能监控架构概览 + +```mermaid +graph TD + A[Android 应用] --> B[Sentry Android SDK] + B --> C[启动监控] + B --> D[UI 性能监控] + B --> E[网络监控] + B --> F[性能分析] + + C --> C1[冷启动监控] + C --> C2[热启动监控] + C --> C3[TTID/TTFD 指标] + + D --> D1[帧率监控] + D --> D2[慢帧检测] + D --> D3[冻结帧检测] + D --> D4[UI 卡顿分析] + + E --> E1[HTTP 请求监控] + E --> E2[网络性能指标] + E --> E3[错误捕获] + E --> E4[分布式追踪] + + F --> F1[方法调用跟踪] + F --> F2[CPU 使用率] + F --> F3[内存监控] + F --> F4[性能瓶颈识别] +``` + +## 1. 启动性能监控 + +### 1.1 启动类型检测 + +Sentry 自动检测并监控两种启动类型: + +```java +public enum AppStartType { + COLD, // 冷启动:进程从零开始创建 + WARM // 热启动:进程已存在,重新启动Activity +} +``` + +**冷启动监控流程:** +1. 进程创建 → ContentProvider 初始化 +2. Application.onCreate() 执行 +3. Activity 创建和首帧绘制 +4. TTID (Time To Initial Display) 测量 + +**热启动监控流程:** +1. Activity 重新创建 +2. 前台恢复 +3. 首帧绘制完成 + +### 1.2 关键性能指标 + +| 指标 | 说明 | 阈值建议 | +|------|------|----------| +| **TTID** | Time To Initial Display - 首次显示时间 | < 1.5s | +| **TTFD** | Time To Full Display - 完全显示时间 | < 3s | +| **冷启动时间** | 进程创建到首帧绘制 | < 2s | +| **热启动时间** | Activity 重启到首帧绘制 | < 500ms | + +### 1.3 启动监控配置 + +```kotlin +SentryAndroid.init(this) { options -> + // 启用启动监控 + options.isEnableAppStartTracking = true + + // 启用性能 V2 (推荐) + options.isEnablePerformanceV2 = true + + // 设置采样率 + options.tracesSampleRate = 1.0 +} +``` + +## 2. UI 性能监控 + +### 2.1 帧率监控机制 + +Sentry 提供两种帧率监控方式: + +**Performance V1 (Activity 级别):** +- 使用 `FrameMetricsAggregator` +- 在 Activity 生命周期中收集帧数据 +- 适用于整个 Activity 的性能分析 + +**Performance V2 (Span 级别):** +- 使用 `SentryFrameMetricsCollector` +- 基于 Choreographer 实时监控 +- 提供更精确的 Span 级别帧分析 + +### 2.2 帧性能标准 + +```java +// 帧性能判断标准 +public class FrameStandards { + // 60fps 设备 + private static final long FRAME_16MS = 16_666_667L; // 16.67ms (纳秒) + + // 90fps 设备 + private static final long FRAME_11MS = 11_111_111L; // 11.11ms (纳秒) + + // 120fps 设备 + private static final long FRAME_8MS = 8_333_333L; // 8.33ms (纳秒) + + // 冻结帧阈值 + private static final long FROZEN_FRAME = 700_000_000L; // 700ms (纳秒) + + public static boolean isSlow(long frameDuration, long expectedDuration) { + return frameDuration > expectedDuration; + } + + public static boolean isFrozen(long frameDuration) { + return frameDuration > FROZEN_FRAME; + } +} +``` + +### 2.3 UI 监控指标 + +| 指标 | 说明 | 计算方式 | +|------|------|----------| +| **总帧数** | 监控期间渲染的总帧数 | 累计计数 | +| **慢帧数** | 超过期望帧时间的帧数 | 帧时间 > 期望时间 | +| **冻结帧数** | 超过 700ms 的帧数 | 帧时间 > 700ms | +| **慢帧率** | 慢帧占总帧数的比例 | 慢帧数 / 总帧数 | +| **冻结帧率** | 冻结帧占总帧数的比例 | 冻结帧数 / 总帧数 | + +### 2.4 UI 监控配置 + +```kotlin +SentryAndroid.init(this) { options -> + // 启用帧率监控 + options.isEnableFramesTracking = true + + // 启用性能 V2 (推荐用于精确监控) + options.isEnablePerformanceV2 = true + + // 设置慢帧阈值 (可选,默认根据设备刷新率自动计算) + options.frameMetricsCollectorOptions.apply { + // 自定义慢帧阈值 (纳秒) + // slowFrameThresholdNanos = 16_666_667L // 16.67ms for 60fps + } +} +``` + +## 3. 网络性能监控 + +### 3.1 HTTP 请求监控 + +Sentry 通过 OkHttp 拦截器自动监控网络请求: + +```kotlin +// 自动集成 (推荐) +val client = OkHttpClient.Builder() + .addInterceptor(SentryOkHttpInterceptor()) + .build() + +// 手动配置 +val client = OkHttpClient.Builder() + .addInterceptor(SentryOkHttpInterceptor( + hub = HubAdapter.getInstance(), + captureFailedRequests = true + )) + .eventListener(SentryOkHttpEventListener()) + .build() +``` + +### 3.2 网络性能指标 + +| 指标 | 说明 | 用途 | +|------|------|------| +| **DNS 解析时间** | 域名解析耗时 | 网络连接优化 | +| **连接建立时间** | TCP 连接建立耗时 | 服务器响应分析 | +| **SSL 握手时间** | HTTPS 握手耗时 | 安全连接优化 | +| **请求发送时间** | 请求数据发送耗时 | 上传性能分析 | +| **等待时间** | 服务器处理时间 | 后端性能分析 | +| **响应接收时间** | 响应数据接收耗时 | 下载性能分析 | +| **总请求时间** | 完整请求周期耗时 | 整体网络性能 | + +### 3.3 网络监控配置 + +```kotlin +SentryAndroid.init(this) { options -> + // 启用网络监控 + options.isEnableNetworkEventBreadcrumbs = true + + // 配置失败请求捕获 + options.captureFailedRequests = true + + // 设置失败状态码范围 + options.failedRequestStatusCodes = listOf( + HttpStatusCodeRange(400, 599) // 4xx 和 5xx 错误 + ) + + // 配置网络请求目标 + options.failedRequestTargets = listOf("api.example.com") +} +``` + +## 4. 性能分析 (Profiling) + +### 4.1 方法调用跟踪 + +Sentry 使用 Android Debug API 进行方法调用跟踪: + +```kotlin +// 启用性能分析 +SentryAndroid.init(this) { options -> + // 启用 Profiling + options.profilesSampleRate = 1.0 + + // 配置 Profiling 选项 + options.profilingTracesIntervalMillis = 10_000 // 10秒间隔 +} +``` + +### 4.2 性能数据收集 + +**事务性能分析:** +- 与事务生命周期绑定 +- 收集事务执行期间的性能数据 +- 适用于特定操作的性能分析 + +**连续性能分析:** +- 独立于事务运行 +- 持续收集应用性能数据 +- 适用于整体应用性能监控 + +### 4.3 性能指标 + +| 类型 | 指标 | 说明 | +|------|------|------| +| **CPU** | CPU 使用率 | 处理器占用情况 | +| **内存** | 内存使用量 | 堆内存占用 | +| **方法调用** | 调用栈信息 | 方法执行路径和耗时 | +| **线程** | 线程状态 | 线程创建和切换 | + +## 5. 最佳实践 + +### 5.1 性能监控配置建议 + +```kotlin +SentryAndroid.init(this) { options -> + // 基础配置 + options.dsn = "YOUR_DSN" + + // 性能监控 + options.tracesSampleRate = 0.1 // 生产环境建议 10% + options.profilesSampleRate = 0.1 // 生产环境建议 10% + + // 启动监控 + options.isEnableAppStartTracking = true + + // UI 监控 + options.isEnableFramesTracking = true + options.isEnablePerformanceV2 = true + + // 网络监控 + options.isEnableNetworkEventBreadcrumbs = true + options.captureFailedRequests = true + + // 会话监控 + options.isEnableAutoSessionTracking = true + + // 调试配置 (仅开发环境) + if (BuildConfig.DEBUG) { + options.isDebug = true + options.tracesSampleRate = 1.0 + options.profilesSampleRate = 1.0 + } +} +``` + +### 5.2 性能优化建议 + +**启动性能优化:** +1. 延迟非关键初始化 +2. 使用 ContentProvider 进行早期初始化 +3. 优化 Application.onCreate() 逻辑 +4. 减少主线程阻塞操作 + +**UI 性能优化:** +1. 避免主线程执行耗时操作 +2. 优化布局层次结构 +3. 使用 RecyclerView 替代 ListView +4. 合理使用图片缓存和压缩 + +**网络性能优化:** +1. 使用连接池复用连接 +2. 启用 HTTP/2 和 GZIP 压缩 +3. 实现请求缓存策略 +4. 优化 API 响应大小 + +### 5.3 监控数据分析 + +**关键指标监控:** +- 启动时间趋势分析 +- 帧率性能分布 +- 网络请求成功率 +- 性能瓶颈识别 + +**告警设置:** +- 启动时间超过阈值 +- 慢帧率超过 5% +- 冻结帧率超过 1% +- 网络请求失败率超过 5% + +## 6. 故障排查 + +### 6.1 常见问题 + +**启动监控无数据:** +- 检查是否启用 `isEnableAppStartTracking` +- 确认应用是前台启动 +- 验证启动时间是否在合理范围内 + +**帧率监控异常:** +- 确认设备支持 FrameMetrics API (API 24+) +- 检查是否启用 `isEnableFramesTracking` +- 验证 Performance V2 配置 + +**网络监控缺失:** +- 确认 OkHttp 拦截器正确配置 +- 检查网络请求目标配置 +- 验证失败状态码范围设置 + +### 6.2 调试技巧 + +```kotlin +// 启用详细日志 +SentryAndroid.init(this) { options -> + options.isDebug = true + options.setLogger(AndroidLogger()) + + // 设置日志级别 + options.diagnosticLevel = SentryLevel.DEBUG +} + +// 手动验证性能数据 +Sentry.startTransaction("test_transaction", "manual").use { transaction -> + // 执行测试代码 + Thread.sleep(1000) + + // 添加自定义指标 + transaction.setMeasurement("custom_metric", 100.0, MeasurementUnit.Duration.MILLISECOND) +} +``` + +通过以上配置和最佳实践,您可以全面监控 Android 应用的性能表现,及时发现和解决性能问题,提升用户体验。 \ No newline at end of file diff --git a/aidocs/sentry-trace-operations-guide.md b/aidocs/sentry-trace-operations-guide.md new file mode 100644 index 0000000000..6fa9ca8b01 --- /dev/null +++ b/aidocs/sentry-trace-operations-guide.md @@ -0,0 +1,1001 @@ +# Sentry Trace 操作字段监控机制详解 + +本文档详细说明 Sentry 管理后台中各种 Trace 数据字段的监控机制、触发时机和实现原理。 + +## 🎯 Trace 操作字段概览 + +Sentry Java/Android SDK 自动生成多种类型的 Trace 操作,每种操作都有特定的监控目的和触发时机: + +```mermaid +graph TD + A[应用程序] --> B[UI 操作] + A --> C[网络操作] + A --> D[数据库操作] + A --> E[文件操作] + A --> F[业务操作] + + B --> B1[ui.load Transaction] + B --> B2[ui.load.initial_display] + B --> B3[ui.load.full_display] + B --> B4[app.start.cold/warm] + B --> B5[activity.load] + + C --> C1[http.client] + C --> C2[http.server] + C --> C3[graphql.operation] + C --> C4[graphql.fetcher] + + D --> D1[db.query] + D --> D2[db.sql.query] + + E --> E1[file.read] + E --> E2[file.write] + + F --> F1[bean] + F --> F2[custom operations] +``` + +## 1. UI 加载相关操作 + +### 1.1 ui.load (Transaction) + +**操作类型**: `ui.load` +**监控对象**: Activity 完整生命周期 +**触发时机**: Activity 创建时开始,Activity 销毁或超时时结束 + +```java +// 源码位置: ActivityLifecycleIntegration.java +static final String UI_LOAD_OP = "ui.load"; + +// 创建时机 +ITransaction transaction = scopes.startTransaction( + new TransactionContext( + activityName, // 事务名称 (Activity 类名) + TransactionNameSource.COMPONENT, + UI_LOAD_OP, // 操作类型 + appStartSamplingDecision), + transactionOptions); +``` + +**监控内容**: +- Activity 完整加载过程 +- 包含所有子 Span (TTID、TTFD、App Start 等) +- 帧率性能指标 +- 内存使用情况 + +### 1.2 ui.load.initial_display (TTID Span) + +**操作类型**: `ui.load.initial_display` +**监控对象**: Time To Initial Display - 首次显示时间 +**触发时机**: Activity 开始创建 → 首帧绘制完成 + +```java +// 源码位置: ActivityLifecycleIntegration.java +static final String TTID_OP = "ui.load.initial_display"; + +// 创建时机 +final @NotNull ISpan ttidSpan = transaction.startChild( + TTID_OP, + getTtidDesc(activityName), // "ActivityName initial display" + ttidStartTime, // 开始时间 + Instrumenter.SENTRY, + spanOptions); + +// 结束时机: onFirstFrameDrawn() +private void onFirstFrameDrawn(final @Nullable ISpan ttfdSpan, final @Nullable ISpan ttidSpan) { + if (options != null && ttidSpan != null) { + final SentryDate endDate = options.getDateProvider().now(); + final long durationNanos = endDate.diff(ttidSpan.getStartDate()); + final long durationMillis = TimeUnit.NANOSECONDS.toMillis(durationNanos); + + // 设置 TTID 指标 + ttidSpan.setMeasurement( + MeasurementValue.KEY_TIME_TO_INITIAL_DISPLAY, + durationMillis, + MILLISECOND); + + finishSpan(ttidSpan, endDate); + } +} +``` + +**关键时间点**: +1. **开始时间**: + - 首次 Activity: 应用启动时间 (`appStartTime`) + - 后续 Activity: 上一个 Activity 的 `onPause()` 时间 +2. **结束时间**: 首帧绘制完成时间 +3. **测量指标**: `time_to_initial_display` (毫秒) + +### 1.3 ui.load.full_display (TTFD Span) + +**操作类型**: `ui.load.full_display` +**监控对象**: Time To Full Display - 完全显示时间 +**触发时机**: Activity 开始创建 → 开发者调用 `Sentry.reportFullyDisplayed()` + +```java +// 源码位置: ActivityLifecycleIntegration.java +static final String TTFD_OP = "ui.load.full_display"; +static final long TTFD_TIMEOUT_MILLIS = 25000; // 25秒超时 + +// 创建时机 (需要启用 timeToFullDisplaySpanEnabled) +if (timeToFullDisplaySpanEnabled && fullyDisplayedReporter != null && options != null) { + final @NotNull ISpan ttfdSpan = transaction.startChild( + TTFD_OP, + getTtfdDesc(activityName), // "ActivityName full display" + ttidStartTime, + Instrumenter.SENTRY, + spanOptions); + + ttfdSpanMap.put(activity, ttfdSpan); + + // 设置自动超时关闭 + ttfdAutoCloseFuture = options.getExecutorService().schedule( + () -> finishExceededTtfdSpan(ttfdSpan, ttidSpan), + TTFD_TIMEOUT_MILLIS); +} + +// 手动结束时机: Sentry.reportFullyDisplayed() +public static void reportFullyDisplayed() { + getCurrentScopes().reportFullyDisplayed(); +} +``` + +**结束条件**: +1. **正常结束**: 开发者调用 `Sentry.reportFullyDisplayed()` +2. **超时结束**: 25秒后自动结束,状态设为 `DEADLINE_EXCEEDED` +3. **早期调用**: 如果在首帧绘制前调用,会在首帧绘制时一起结束 + +**特殊处理**: +```java +private void onFullFrameDrawn(final @NotNull ISpan ttidSpan, final @NotNull ISpan ttfdSpan) { + // 如果 TTID 还未完成,说明首帧还未绘制,设置标记 + if (!ttidSpan.isFinished()) { + fullyDisplayedCalled = true; + return; + } + + // 正常情况下设置 TTFD 指标并结束 + if (options != null) { + final SentryDate endDate = options.getDateProvider().now(); + final long durationNanos = endDate.diff(ttfdSpan.getStartDate()); + final long durationMillis = TimeUnit.NANOSECONDS.toMillis(durationNanos); + ttfdSpan.setMeasurement( + MeasurementValue.KEY_TIME_TO_FULL_DISPLAY, + durationMillis, + MILLISECOND); + finishSpan(ttfdSpan, endDate); + } +} +``` + +### 1.4 activity.load (Activity 级别监控) + +**操作类型**: `activity.load` +**监控对象**: 单个 Activity 的加载性能 +**触发时机**: Performance V2 模式下的 Activity 级别监控 + +```java +// 源码位置: PerformanceAndroidEventProcessor.java +private static final String APP_METRICS_ACTIVITIES_OP = "activity.load"; + +// 在 Performance V2 模式下创建 +if (isPerformanceV2Enabled && spanContext.getOperation().contentEquals(UI_LOAD_OP)) { + // 为每个 Activity 创建独立的性能监控 Span +} +``` + +**监控指标**: +- Activity 创建到显示的时间 +- 帧率性能数据 +- 内存使用情况 + +## 2. 应用启动相关操作 + +### 2.1 app.start.cold (冷启动 Span) + +**操作类型**: `app.start.cold` +**监控对象**: 冷启动过程 (进程从零开始创建) +**触发时机**: 进程创建 → 首帧绘制完成 + +```java +// 源码位置: ActivityLifecycleIntegration.java +static final String APP_START_COLD = "app.start.cold"; + +// 创建条件 +if (!(firstActivityCreated || appStartTime == null || coldStart == null)) { + // 创建应用启动 Span + appStartSpan = transaction.startChild( + getAppStartOp(coldStart), // "app.start.cold" + getAppStartDesc(coldStart), // "Cold Start" + appStartTime, // 进程启动时间 + Instrumenter.SENTRY, + spanOptions); +} + +private @NotNull String getAppStartOp(final boolean coldStart) { + if (coldStart) { + return APP_START_COLD; + } else { + return APP_START_WARM; + } +} +``` + +**时间测量**: +- **开始时间**: `Process.getStartUptimeMillis()` (Android N+) 或 SDK 初始化时间 +- **结束时间**: 首帧绘制完成时间 +- **包含阶段**: 进程创建 → ContentProvider 初始化 → Application.onCreate → Activity 创建 → 首帧绘制 + +### 2.2 app.start.warm (热启动 Span) + +**操作类型**: `app.start.warm` +**监控对象**: 热启动过程 (进程已存在,重新启动 Activity) +**触发时机**: Activity 重新创建 → 首帧绘制完成 + +```java +// 热启动判断逻辑 +private void setColdStart(final @Nullable Bundle savedInstanceState) { + if (!firstActivityCreated) { + final @NotNull TimeSpan appStartSpan = AppStartMetrics.getInstance().getAppStartTimeSpanWithFallback(options); + + if ((appStartSpan.hasStarted() && appStartSpan.hasStopped()) + || (!AppStartMetrics.getInstance().isColdStartValid())) { + // 重启应用启动测量,标记为热启动 + AppStartMetrics.getInstance().restartAppStart(lastPausedUptimeMillis); + AppStartMetrics.getInstance().setAppStartType(AppStartMetrics.AppStartType.WARM); + } else { + // 根据 savedInstanceState 判断启动类型 + AppStartMetrics.getInstance().setAppStartType( + savedInstanceState == null + ? AppStartMetrics.AppStartType.COLD + : AppStartMetrics.AppStartType.WARM); + } + } +} +``` + +**时间测量**: +- **开始时间**: 上一个 Activity 的 `onPause()` 时间 +- **结束时间**: 首帧绘制完成时间 +- **包含阶段**: Activity 重新创建 → 首帧绘制 + +### 2.3 application.load (应用程序加载) + +**操作类型**: `application.load` +**监控对象**: Application 类的初始化过程 +**触发时机**: Application.onCreate() 执行期间 + +```java +// 源码位置: PerformanceAndroidEventProcessor.java +private static final String APP_METRICS_APPLICATION_OP = "application.load"; +``` + +### 2.4 contentprovider.load (内容提供者加载) + +**操作类型**: `contentprovider.load` +**监控对象**: ContentProvider 的初始化过程 +**触发时机**: ContentProvider.onCreate() 执行期间 + +```java +// 源码位置: PerformanceAndroidEventProcessor.java +private static final String APP_METRICS_CONTENT_PROVIDER_OP = "contentprovider.load"; +``` + +### 2.5 process.load (进程初始化) + +**操作类型**: `process.load` +**监控对象**: 进程级别的初始化过程 +**触发时机**: 进程创建到应用组件加载完成 + +```java +// 源码位置: PerformanceAndroidEventProcessor.java +private static final String APP_METRICS_PROCESS_INIT_OP = "process.load"; +``` + +## 3. 网络请求相关操作 + +### 3.1 http.client (HTTP 客户端请求) + +**操作类型**: `http.client` +**监控对象**: HTTP 客户端发起的网络请求 +**触发时机**: OkHttp、Spring WebClient、OpenFeign 等拦截器自动创建 + +```java +// 源码位置: SentryOkHttpInterceptor.kt +override fun intercept(chain: Interceptor.Chain): Response { + val request = chain.request() + + // 创建 HTTP 客户端 Span + val span = parentSpan?.startChild("http.client", "$method $url") + + // 设置 HTTP 相关标签 + span?.setTag("http.method", request.method) + span?.setTag("http.url", request.url.toString()) + + try { + val response = chain.proceed(request) + span?.setTag("http.status_code", response.code.toString()) + return response + } catch (e: IOException) { + span?.setThrowable(e) + span?.setStatus(SpanStatus.INTERNAL_ERROR) + throw e + } finally { + span?.finish() + } +} +``` + +**监控指标**: +- DNS 解析时间 +- 连接建立时间 +- SSL 握手时间 +- 请求发送时间 +- 响应接收时间 +- HTTP 状态码 +- 错误信息 + +**集成支持**: +- **OkHttp**: `SentryOkHttpInterceptor` +- **Spring WebClient**: `SentrySpanClientWebRequestFilter` +- **Spring RestTemplate**: `SentrySpanClientHttpRequestInterceptor` +- **OpenFeign**: `SentryFeignClient` +- **Apollo GraphQL**: `SentryApollo3HttpInterceptor`, `SentryApollo4HttpInterceptor` + +### 3.2 http.server (HTTP 服务端请求) + +**操作类型**: `http.server` +**监控对象**: HTTP 服务端接收的请求 +**触发时机**: Spring MVC、WebFlux、Servlet 等框架自动创建 + +```java +// 源码位置: SentryTracingFilter.java +private static final String TRANSACTION_OP = "http.server"; +private static final String TRACE_ORIGIN = "auto.http.spring.webmvc"; + +// 创建服务端事务 +ITransaction transaction = scopes.startTransaction( + new TransactionContext(name, TransactionNameSource.URL, "http.server"), + transactionOptions); +``` + +**监控内容**: +- HTTP 请求处理时间 +- 请求路径和方法 +- 响应状态码 +- 异常信息 +- 用户信息 + +**集成支持**: +- **Spring MVC**: `SentryTracingFilter` +- **Spring WebFlux**: `SentryWebFilter` +- **Servlet**: `SentryServletRequestListener` + +## 4. 数据库相关操作 + +### 4.1 db.query (通用数据库查询) + +**操作类型**: `db.query` +**监控对象**: 通用数据库查询操作 +**触发时机**: JDBC 拦截器自动创建 + +```java +// 源码位置: SentryJdbcEventListener.java +@Override +public void onBeforeAnyExecute(final @NotNull StatementInformation statementInformation) { + final ISpan parent = scopes.getSpan(); + if (parent != null && !parent.isNoOp()) { + final @NotNull SpanOptions spanOptions = new SpanOptions(); + spanOptions.setOrigin("auto.db.jdbc"); + final ISpan span = parent.startChild("db.query", statementInformation.getSql(), spanOptions); + CURRENT_SPAN.set(span); + } +} +``` + +**监控指标**: +- SQL 语句执行时间 +- 数据库类型 (`db.system`) +- 数据库名称 (`db.name`) +- 执行结果和异常 + +### 4.2 db.sql.query (SQL 查询) + +**操作类型**: `db.sql.query` +**监控对象**: SQL 数据库查询 +**触发时机**: SQLite、Room 等数据库操作时自动创建 + +```java +// 源码位置: SQLiteSpanManager.kt +fun performSql(sql: String?, operation: () -> T): T { + val span = scopes.span?.startChild("db.sql.query", sql, startTimestamp, Instrumenter.SENTRY) + + span?.setTag("db.type", "sqlite") + span?.setTag("db.operation", getOperationType(sql)) + + try { + val result = operation() + span?.setStatus(SpanStatus.OK) + return result + } catch (e: Exception) { + span?.setThrowable(e) + span?.setStatus(SpanStatus.INTERNAL_ERROR) + throw e + } finally { + span?.finish() + } +} +``` + +**监控内容**: +- SQL 语句类型 (SELECT, INSERT, UPDATE, DELETE) +- 执行时间 +- 影响行数 +- 错误信息 + +## 5. 文件操作相关 + +### 5.1 file.read (文件读取) + +**操作类型**: `file.read` +**监控对象**: 文件读取操作 +**触发时机**: SentryFileInputStream 自动创建 + +```java +// 源码位置: SentryFileInputStream.java +private static FileInputStreamInitData init( + final @Nullable File file, @Nullable FileInputStream delegate, final @NotNull IScopes scopes) + throws FileNotFoundException { + final ISpan span = FileIOSpanManager.startSpan(scopes, "file.read"); + if (delegate == null) { + delegate = new FileInputStream(file); + } + return new FileInputStreamInitData(file, span, delegate, scopes.getOptions()); +} +``` + +**监控指标**: +- 文件路径 (`file.path`) +- 读取字节数 (`file.size`) +- 读取时间 +- IO 异常 + +### 5.2 file.write (文件写入) + +**操作类型**: `file.write` +**监控对象**: 文件写入操作 +**触发时机**: SentryFileOutputStream 自动创建 + +```java +// 源码位置: SentryFileOutputStream.java +private static FileOutputStreamInitData init( + final @Nullable File file, @Nullable FileOutputStream delegate, final @NotNull IScopes scopes) + throws FileNotFoundException { + final ISpan span = FileIOSpanManager.startSpan(scopes, "file.write"); + if (delegate == null) { + delegate = new FileOutputStream(file); + } + return new FileOutputStreamInitData(file, span, delegate, scopes.getOptions()); +} +``` + +**监控指标**: +- 文件路径 (`file.path`) +- 写入字节数 (`file.size`) +- 写入时间 +- IO 异常 + +## 6. GraphQL 相关操作 + +### 6.1 graphql.operation (GraphQL 操作) + +**操作类型**: `graphql.operation` +**监控对象**: GraphQL 查询、变更、订阅操作 +**触发时机**: GraphQL 执行引擎自动创建 + +```java +// 源码位置: SentryGraphqlInstrumentation.java +@Override +public InstrumentationContext beginExecution( + InstrumentationExecutionParameters parameters) { + + final String operationName = parameters.getOperation().getName(); + final String operationType = parameters.getOperation().getOperation().name(); + + final ISpan span = activeSpan.startChild( + "graphql.operation", + operationType + " " + operationName); + + return new SimpleInstrumentationContext.Builder() + .onCompleted((result, throwable) -> { + if (throwable != null) { + span.setThrowable(throwable); + span.setStatus(SpanStatus.INTERNAL_ERROR); + } else { + span.setStatus(SpanStatus.OK); + } + span.finish(); + }) + .build(); +} +``` + +**监控内容**: +- 操作类型 (query, mutation, subscription) +- 操作名称 +- 执行时间 +- 错误信息 +- 字段解析性能 + +### 6.2 graphql.fetcher (GraphQL 字段解析器) + +**操作类型**: `graphql.fetcher` +**监控对象**: GraphQL 字段解析器执行 +**触发时机**: 字段解析时自动创建 + +```java +// 源码位置: SentryDataFetcher.java +@Override +public Object get(DataFetchingEnvironment environment) throws Exception { + final String fieldName = environment.getField().getName(); + final ISpan span = activeSpan.startChild("graphql.fetcher", fieldName); + + try { + Object result = delegate.get(environment); + span.setStatus(SpanStatus.OK); + return result; + } catch (Exception e) { + span.setThrowable(e); + span.setStatus(SpanStatus.INTERNAL_ERROR); + throw e; + } finally { + span.finish(); + } +} +``` + +## 7. Spring 框架相关操作 + +### 7.1 bean (Spring Bean 方法调用) + +**操作类型**: `bean` +**监控对象**: Spring Bean 方法执行 +**触发时机**: `@SentryTransaction` 或 `@SentrySpan` 注解的方法 + +```java +// 源码位置: SentryTransactionAdvice.java +String operation; +if (sentryTransaction != null && !StringUtils.isEmpty(sentryTransaction.operation())) { + operation = sentryTransaction.operation(); +} else { + operation = "bean"; // 默认操作类型 +} + +ITransaction transaction = forkedScopes.startTransaction( + new TransactionContext(nameAndSource.name, nameAndSource.source, operation), + transactionOptions); +``` + +**使用示例**: +```java +@Component +public class UserService { + + @SentryTransaction(operation = "bean") + public User createUser(String name) { + // 业务逻辑 + return new User(name); + } + + @SentrySpan(operation = "bean") + public void validateUser(User user) { + // 验证逻辑 + } +} +``` + +## 8. 自定义操作 + +### 8.1 custom (自定义 Span) + +**操作类型**: 开发者自定义 +**监控对象**: 业务逻辑操作 +**触发时机**: 开发者手动创建 + +```java +// 手动创建自定义 Span +public void performBusinessOperation() { + final ISpan span = Sentry.getSpan(); + final ISpan customSpan = span != null ? + span.startChild("business.operation", "Process User Data") : null; + + if (customSpan != null) { + customSpan.setTag("user.id", "12345"); + customSpan.setTag("operation.type", "data_processing"); + } + + try { + // 执行业务逻辑 + processUserData(); + if (customSpan != null) { + customSpan.setStatus(SpanStatus.OK); + } + } catch (Exception e) { + if (customSpan != null) { + customSpan.setThrowable(e); + customSpan.setStatus(SpanStatus.INTERNAL_ERROR); + } + throw e; + } finally { + if (customSpan != null) { + customSpan.finish(); + } + } +} +``` + +## 9. 性能指标 (Measurements) + +### 9.1 应用启动指标 + +```java +// 源码位置: MeasurementValue.java +public static final String KEY_APP_START_COLD = "app_start_cold"; +public static final String KEY_APP_START_WARM = "app_start_warm"; +``` + +**指标说明**: +- `app_start_cold`: 冷启动时间 (毫秒) +- `app_start_warm`: 热启动时间 (毫秒) + +### 9.2 UI 性能指标 + +```java +// 源码位置: MeasurementValue.java +public static final String KEY_TIME_TO_INITIAL_DISPLAY = "time_to_initial_display"; +public static final String KEY_TIME_TO_FULL_DISPLAY = "time_to_full_display"; +public static final String KEY_FRAMES_TOTAL = "frames_total"; +public static final String KEY_FRAMES_SLOW = "frames_slow"; +public static final String KEY_FRAMES_FROZEN = "frames_frozen"; +public static final String KEY_FRAMES_DELAY = "frames_delay"; +``` + +**指标说明**: +- `time_to_initial_display`: 首次显示时间 (毫秒) +- `time_to_full_display`: 完全显示时间 (毫秒) +- `frames_total`: 总帧数 +- `frames_slow`: 慢帧数量 (>16.67ms for 60fps) +- `frames_frozen`: 冻结帧数量 (>700ms) +- `frames_delay`: 帧延迟时间 (秒) + +### 9.3 Span 数据约定 + +```java +// 源码位置: SpanDataConvention.java +String DB_SYSTEM_KEY = "db.system"; +String DB_NAME_KEY = "db.name"; +String HTTP_QUERY_KEY = "http.query"; +String HTTP_FRAGMENT_KEY = "http.fragment"; +String HTTP_METHOD_KEY = "http.request.method"; +String THREAD_ID = "thread.id"; +String THREAD_NAME = "thread.name"; +``` + +## 10. 监控时机详解 + +### 10.1 Activity 生命周期监控时机 + +```mermaid +sequenceDiagram + participant App as 应用进程 + participant Activity as Activity + participant Sentry as Sentry SDK + participant Backend as Sentry 后台 + + Note over App: 冷启动开始 + App->>Sentry: 进程创建 (Process.getStartUptimeMillis) + Sentry->>Sentry: 开始 app.start.cold Span + + App->>Activity: onCreate() + Activity->>Sentry: 开始 ui.load Transaction + Sentry->>Sentry: 开始 ui.load.initial_display Span + Sentry->>Sentry: 开始 ui.load.full_display Span (可选) + + Activity->>Activity: onStart() + Activity->>Activity: onResume() + Activity->>Activity: 首帧绘制 + Activity->>Sentry: onFirstFrameDrawn() + + Sentry->>Sentry: 结束 app.start.cold Span + Sentry->>Sentry: 结束 ui.load.initial_display Span + Sentry->>Sentry: 设置 TTID 指标 + + Note over Activity: 用户交互,内容加载完成 + Activity->>Sentry: Sentry.reportFullyDisplayed() + Sentry->>Sentry: 结束 ui.load.full_display Span + Sentry->>Sentry: 设置 TTFD 指标 + + Activity->>Activity: onPause() + Activity->>Sentry: 记录 lastPausedTime + + Sentry->>Backend: 上报 Transaction 和所有 Span 数据 +``` + +### 10.2 网络请求监控时机 + +```mermaid +sequenceDiagram + participant App as 应用 + participant OkHttp as OkHttp + participant Interceptor as SentryInterceptor + participant Network as 网络 + participant Sentry as Sentry 后台 + + App->>OkHttp: 发起 HTTP 请求 + OkHttp->>Interceptor: intercept() + Interceptor->>Interceptor: 创建 http.client Span + Interceptor->>Interceptor: 设置请求标签 (method, url) + + Interceptor->>Network: 执行网络请求 + Note over Network: DNS 解析、连接建立、数据传输 + Network->>Interceptor: 返回响应 + + Interceptor->>Interceptor: 设置响应标签 (status_code) + Interceptor->>Interceptor: 结束 Span + Interceptor->>Sentry: 上报 Span 数据 + + Interceptor->>App: 返回响应 +``` + +### 10.3 数据库操作监控时机 + +```mermaid +sequenceDiagram + participant App as 应用 + participant JDBC as JDBC Driver + participant P6Spy as P6Spy Interceptor + participant DB as 数据库 + participant Sentry as Sentry 后台 + + App->>JDBC: 执行 SQL 查询 + JDBC->>P6Spy: onBeforeAnyExecute() + P6Spy->>P6Spy: 创建 db.query Span + P6Spy->>P6Spy: 设置 SQL 语句和数据库信息 + + P6Spy->>DB: 执行 SQL + Note over DB: 查询处理、索引查找、结果返回 + DB->>P6Spy: 返回查询结果 + + P6Spy->>P6Spy: onAfterAnyExecute() + P6Spy->>P6Spy: 设置执行结果和异常信息 + P6Spy->>P6Spy: 结束 Span + P6Spy->>Sentry: 上报 Span 数据 + + P6Spy->>App: 返回查询结果 +``` + +## 11. 配置和最佳实践 + +### 11.1 启用相关监控 + +```kotlin +SentryAndroid.init(this) { options -> + // 启用性能监控 + options.tracesSampleRate = 1.0 + + // 启用 Activity 生命周期追踪 + options.isEnableAutoActivityLifecycleTracing = true + + // 启用应用启动追踪 + options.isEnableAppStartTracking = true + + // 启用 TTFD 追踪 (需要手动调用 reportFullyDisplayed) + options.isEnableTimeToFullDisplayTracing = true + + // 启用网络监控 + options.isEnableNetworkEventBreadcrumbs = true + + // 启用帧率监控 + options.isEnableFramesTracking = true + + // 启用文件 IO 监控 + options.isTracingEnabled = true +} +``` + +### 11.2 网络监控集成 + +```kotlin +// OkHttp 集成 +val client = OkHttpClient.Builder() + .addInterceptor(SentryOkHttpInterceptor()) + .build() + +// Spring WebClient 集成 +@Bean +fun webClient(): WebClient { + return WebClient.builder() + .filter(SentrySpanClientWebRequestFilter()) + .build() +} + +// Apollo GraphQL 集成 +val apolloClient = ApolloClient.Builder() + .serverUrl("https://api.example.com/graphql") + .addHttpInterceptor(SentryApollo3HttpInterceptor()) + .build() +``` + +### 11.3 数据库监控集成 + +```properties +# application.properties +# 启用 P6Spy JDBC 拦截器 +spring.datasource.driver-class-name=com.p6spy.engine.spy.P6SpyDriver +spring.datasource.url=jdbc:p6spy:postgresql://localhost:5432/mydb + +# spy.properties +driverlist=org.postgresql.Driver +appender=io.sentry.jdbc.SentryJdbcEventListener +``` + +### 11.4 TTFD 最佳实践 + +```kotlin +class MainActivity : AppCompatActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_main) + + // 异步加载数据 + loadDataAsync { + // 数据加载完成,UI 完全显示 + Sentry.reportFullyDisplayed() + } + } + + private fun loadDataAsync(callback: () -> Unit) { + lifecycleScope.launch { + // 模拟数据加载 + delay(1000) + + // 更新 UI + updateUI() + + // 通知 Sentry UI 完全显示 + callback() + } + } +} +``` + +### 11.5 自定义 Span 最佳实践 + +```kotlin +// 使用 use 扩展函数自动管理 Span 生命周期 +fun processUserData(userId: String) { + Sentry.startTransaction("user_data_processing", "business").use { transaction -> + transaction.setTag("user.id", userId) + + // 数据库查询 Span + transaction.startChild("db.query", "SELECT user data").use { dbSpan -> + dbSpan.setTag("db.table", "users") + val userData = database.getUser(userId) + dbSpan.setData("rows.affected", 1) + } + + // 数据处理 Span + transaction.startChild("data.processing", "Transform user data").use { processSpan -> + processSpan.setTag("processing.type", "transformation") + val processedData = transformData(userData) + processSpan.setData("items.processed", processedData.size) + } + + transaction.setStatus(SpanStatus.OK) + } +} +``` + +### 11.6 Spring 注解最佳实践 + +```java +@Service +public class UserService { + + // 事务级别监控 + @SentryTransaction(name = "createUser", operation = "business") + public User createUser(CreateUserRequest request) { + return processUserCreation(request); + } + + // Span 级别监控 + @SentrySpan(operation = "validation") + public void validateUser(User user) { + // 验证逻辑 + } + + @SentrySpan(operation = "db.operation", description = "Save user to database") + public void saveUser(User user) { + userRepository.save(user); + } +} +``` + +## 12. 故障排查 + +### 12.1 常见问题 + +**TTID/TTFD Span 未出现**: +- 检查是否启用了 `isEnableAutoActivityLifecycleTracing` +- 确认 `tracesSampleRate` > 0 +- 验证 Activity 是否正常完成生命周期 + +**TTFD Span 超时**: +- 检查是否调用了 `Sentry.reportFullyDisplayed()` +- 确认调用时机是否在 25 秒内 +- 验证是否在正确的线程调用 + +**App Start Span 缺失**: +- 确认是否为前台启动 (非后台启动) +- 检查启动时间是否超过 1 分钟 (会被标记为无效) +- 验证是否为首次 Activity 创建 + +**HTTP Span 未创建**: +- 确认是否正确配置了拦截器 +- 检查是否存在活跃的父 Span +- 验证网络请求是否成功执行 + +**数据库 Span 缺失**: +- 确认是否正确配置了 P6Spy +- 检查 JDBC URL 是否包含 p6spy 前缀 +- 验证是否存在活跃的事务 + +### 12.2 调试技巧 + +```kotlin +// 启用详细日志查看 Span 创建过程 +SentryAndroid.init(this) { options -> + options.isDebug = true + options.diagnosticLevel = SentryLevel.DEBUG + + // 设置自定义 logger + options.setLogger(object : ILogger { + override fun log(level: SentryLevel, message: String, vararg args: Any?) { + Log.d("Sentry", String.format(message, *args)) + } + + override fun log(level: SentryLevel, message: String, throwable: Throwable?) { + Log.d("Sentry", message, throwable) + } + + override fun log(level: SentryLevel, throwable: Throwable?, message: String, vararg args: Any?) { + Log.d("Sentry", String.format(message, *args), throwable) + } + + override fun isEnabled(level: SentryLevel?): Boolean = true + }) +} +``` + +### 12.3 性能监控检查清单 + +**Android 应用**: +- [ ] 启用 Activity 生命周期追踪 +- [ ] 配置合适的采样率 (生产环境建议 0.1) +- [ ] 集成 OkHttp 拦截器进行网络监控 +- [ ] 在关键页面调用 `reportFullyDisplayed()` +- [ ] 启用帧率监控检测 UI 卡顿 + +**Spring 应用**: +- [ ] 启用 Spring 集成自动配置 +- [ ] 在关键业务方法添加 `@SentryTransaction` 注解 +- [ ] 配置数据库监控 (P6Spy) +- [ ] 集成 WebClient 或 RestTemplate 拦截器 + +**通用配置**: +- [ ] 设置合理的事务超时时间 +- [ ] 配置错误采样策略 +- [ ] 启用分布式追踪头传播 +- [ ] 设置环境和版本信息 + +通过理解这些 Trace 操作字段的监控机制,开发者可以更好地分析应用性能,识别瓶颈,并进行针对性优化。每种操作类型都有其特定的用途和最佳实践,合理配置和使用这些监控功能可以大大提升应用的可观测性。 \ No newline at end of file diff --git a/project_directories.txt b/project_directories.txt new file mode 100644 index 0000000000..7171002d58 --- /dev/null +++ b/project_directories.txt @@ -0,0 +1,1123 @@ +. +├── aidocs +│   ├── mermaid +│   │   └── svg +│   └── rules +├── buildSrc +│   └── src +│   └── main +│   └── java +├── docs +├── gradle +│   └── wrapper +├── hooks +├── scripts +├── sentry +│   ├── api +│   └── src +│   ├── main +│   │   ├── java +│   │   │   └── io +│   │   │   └── sentry +│   │   │   ├── backpressure +│   │   │   ├── cache +│   │   │   │   └── tape +│   │   │   ├── clientreport +│   │   │   ├── config +│   │   │   ├── exception +│   │   │   ├── hints +│   │   │   ├── instrumentation +│   │   │   │   └── file +│   │   │   ├── internal +│   │   │   │   ├── debugmeta +│   │   │   │   ├── eventprocessor +│   │   │   │   ├── gestures +│   │   │   │   ├── modules +│   │   │   │   └── viewhierarchy +│   │   │   ├── logger +│   │   │   ├── opentelemetry +│   │   │   ├── profilemeasurements +│   │   │   ├── protocol +│   │   │   ├── rrweb +│   │   │   ├── transport +│   │   │   ├── util +│   │   │   │   └── thread +│   │   │   └── vendor +│   │   │   └── gson +│   │   │   ├── internal +│   │   │   │   └── bind +│   │   │   │   └── util +│   │   │   └── stream +│   │   └── resources +│   │   └── META-INF +│   │   └── native-image +│   │   └── io.sentry +│   │   └── sentry +│   └── test +│   ├── java +│   │   └── io +│   │   └── sentry +│   │   ├── backpressure +│   │   ├── cache +│   │   │   └── tape +│   │   ├── clientreport +│   │   ├── config +│   │   ├── hints +│   │   ├── instrumentation +│   │   │   └── file +│   │   ├── internal +│   │   │   ├── debugmeta +│   │   │   └── modules +│   │   ├── protocol +│   │   ├── rrweb +│   │   ├── transport +│   │   ├── util +│   │   │   └── thread +│   │   └── vendor +│   │   └── gson +│   │   ├── internal +│   │   │   └── bind +│   │   │   └── util +│   │   └── stream +│   └── resources +│   ├── json +│   └── mockito-extensions +├── sentry-android +│   └── src +│   └── main +│   └── res +│   └── values +├── sentry-android-core +│   ├── api +│   └── src +│   ├── main +│   │   ├── java +│   │   │   └── io +│   │   │   └── sentry +│   │   │   └── android +│   │   │   └── core +│   │   │   ├── cache +│   │   │   ├── internal +│   │   │   │   ├── debugmeta +│   │   │   │   ├── gestures +│   │   │   │   ├── modules +│   │   │   │   ├── threaddump +│   │   │   │   └── util +│   │   │   ├── performance +│   │   │   └── util +│   │   └── res +│   │   └── values +│   └── test +│   ├── assets +│   ├── java +│   │   └── io +│   │   └── sentry +│   │   └── android +│   │   └── core +│   │   ├── cache +│   │   ├── internal +│   │   │   ├── debugmeta +│   │   │   ├── gestures +│   │   │   ├── modules +│   │   │   ├── threaddump +│   │   │   └── util +│   │   └── performance +│   └── resources +│   └── mockito-extensions +├── sentry-android-fragment +│   ├── api +│   └── src +│   ├── main +│   │   ├── java +│   │   │   └── io +│   │   │   └── sentry +│   │   │   └── android +│   │   │   └── fragment +│   │   └── res +│   │   └── values +│   └── test +│   └── java +│   └── io +│   └── sentry +│   └── android +│   └── fragment +├── sentry-android-integration-tests +│   ├── sentry-uitest-android +│   │   └── src +│   │   ├── androidTest +│   │   │   └── java +│   │   │   └── io +│   │   │   └── sentry +│   │   │   └── uitest +│   │   │   └── android +│   │   │   └── mockservers +│   │   └── main +│   │   ├── java +│   │   │   └── io +│   │   │   └── sentry +│   │   │   └── uitest +│   │   │   └── android +│   │   │   └── utils +│   │   └── res +│   │   ├── layout +│   │   └── values +│   ├── sentry-uitest-android-benchmark +│   │   └── src +│   │   ├── androidTest +│   │   │   └── java +│   │   │   └── io +│   │   │   └── sentry +│   │   │   └── uitest +│   │   │   └── android +│   │   │   └── benchmark +│   │   │   └── util +│   │   └── main +│   │   ├── java +│   │   │   └── io +│   │   │   └── sentry +│   │   │   └── uitest +│   │   │   └── android +│   │   │   └── benchmark +│   │   └── res +│   │   └── layout +│   ├── sentry-uitest-android-critical +│   │   ├── maestro +│   │   └── src +│   │   └── main +│   │   └── java +│   │   └── io +│   │   └── sentry +│   │   └── uitest +│   │   └── android +│   │   └── critical +│   ├── test-app-plain +│   │   └── src +│   │   └── main +│   │   ├── java +│   │   │   └── io +│   │   │   └── sentry +│   │   │   └── java +│   │   │   └── tests +│   │   │   └── perf +│   │   │   └── appplain +│   │   └── res +│   │   ├── drawable +│   │   ├── drawable-v24 +│   │   ├── layout +│   │   ├── menu +│   │   ├── mipmap-anydpi-v26 +│   │   ├── mipmap-hdpi +│   │   ├── mipmap-mdpi +│   │   ├── mipmap-xhdpi +│   │   ├── mipmap-xxhdpi +│   │   ├── mipmap-xxxhdpi +│   │   ├── navigation +│   │   ├── values +│   │   ├── values-land +│   │   ├── values-night +│   │   ├── values-w1240dp +│   │   ├── values-w600dp +│   │   └── xml +│   └── test-app-sentry +│   └── src +│   └── main +│   ├── java +│   │   └── io +│   │   └── sentry +│   │   └── java +│   │   └── tests +│   │   └── perf +│   │   └── appsentry +│   └── res +│   ├── drawable +│   ├── drawable-v24 +│   ├── layout +│   ├── menu +│   ├── mipmap-anydpi-v26 +│   ├── mipmap-hdpi +│   ├── mipmap-mdpi +│   ├── mipmap-xhdpi +│   ├── mipmap-xxhdpi +│   ├── mipmap-xxxhdpi +│   ├── navigation +│   ├── values +│   ├── values-land +│   ├── values-night +│   ├── values-w1240dp +│   ├── values-w600dp +│   └── xml +├── sentry-android-navigation +│   ├── api +│   └── src +│   ├── main +│   │   ├── java +│   │   │   └── io +│   │   │   └── sentry +│   │   │   └── android +│   │   │   └── navigation +│   │   └── res +│   │   └── values +│   └── test +│   └── java +│   └── io +│   └── sentry +│   └── android +│   └── navigation +├── sentry-android-ndk +│   ├── api +│   └── src +│   ├── main +│   │   ├── java +│   │   │   └── io +│   │   │   └── sentry +│   │   │   └── android +│   │   │   └── ndk +│   │   └── res +│   │   └── values +│   └── test +│   ├── java +│   │   └── io +│   │   └── sentry +│   │   └── android +│   │   └── ndk +│   └── resources +│   └── mockito-extensions +├── sentry-android-replay +│   ├── api +│   └── src +│   ├── main +│   │   ├── java +│   │   │   └── io +│   │   │   └── sentry +│   │   │   └── android +│   │   │   └── replay +│   │   │   ├── capture +│   │   │   ├── gestures +│   │   │   ├── util +│   │   │   ├── video +│   │   │   └── viewhierarchy +│   │   ├── res +│   │   │   └── values +│   │   └── resources +│   │   └── META-INF +│   │   └── io +│   │   └── sentry +│   │   └── sentry-android-replay +│   └── test +│   ├── java +│   │   └── io +│   │   └── sentry +│   │   └── android +│   │   └── replay +│   │   ├── capture +│   │   ├── gestures +│   │   ├── util +│   │   └── viewhierarchy +│   └── resources +├── sentry-android-sqlite +│   ├── api +│   └── src +│   ├── main +│   │   ├── java +│   │   │   └── io +│   │   │   └── sentry +│   │   │   └── android +│   │   │   └── sqlite +│   │   └── res +│   │   └── values +│   └── test +│   ├── java +│   │   └── io +│   │   └── sentry +│   │   └── android +│   │   └── sqlite +│   └── resources +│   └── mockito-extensions +├── sentry-android-timber +│   ├── api +│   └── src +│   ├── main +│   │   ├── java +│   │   │   └── io +│   │   │   └── sentry +│   │   │   └── android +│   │   │   └── timber +│   │   └── res +│   │   └── values +│   └── test +│   ├── java +│   │   └── io +│   │   └── sentry +│   │   └── android +│   │   └── timber +│   └── resources +│   └── mockito-extensions +├── sentry-apache-http-client-5 +│   ├── api +│   └── src +│   ├── main +│   │   └── java +│   │   └── io +│   │   └── sentry +│   │   └── transport +│   │   └── apache +│   └── test +│   ├── kotlin +│   │   └── io +│   │   └── sentry +│   │   └── transport +│   │   └── apache +│   └── resources +│   └── mockito-extensions +├── sentry-apollo +│   ├── api +│   └── src +│   ├── main +│   │   └── java +│   │   └── io +│   │   └── sentry +│   │   └── apollo +│   └── test +│   ├── java +│   │   └── io +│   │   └── sentry +│   │   ├── apollo +│   │   │   └── type +│   │   └── util +│   └── resources +│   └── mockito-extensions +├── sentry-apollo-3 +│   ├── api +│   └── src +│   ├── main +│   │   └── java +│   │   └── io +│   │   └── sentry +│   │   └── apollo3 +│   └── test +│   ├── java +│   │   └── io +│   │   └── sentry +│   │   ├── apollo3 +│   │   │   ├── adapter +│   │   │   ├── selections +│   │   │   └── type +│   │   └── util +│   └── resources +│   └── mockito-extensions +├── sentry-apollo-4 +│   ├── api +│   └── src +│   ├── main +│   │   └── java +│   │   └── io +│   │   └── sentry +│   │   └── apollo4 +│   └── test +│   ├── java +│   │   └── io +│   │   └── sentry +│   │   ├── apollo4 +│   │   │   └── generated +│   │   │   ├── adapter +│   │   │   ├── selections +│   │   │   └── type +│   │   └── util +│   └── resources +│   └── mockito-extensions +├── sentry-bom +├── sentry-compose +│   ├── api +│   │   ├── android +│   │   └── desktop +│   └── src +│   ├── androidMain +│   │   ├── kotlin +│   │   │   └── io +│   │   │   └── sentry +│   │   │   └── compose +│   │   │   ├── gestures +│   │   │   └── viewhierarchy +│   │   └── res +│   │   └── values +│   └── androidUnitTest +│   └── kotlin +│   └── io +│   └── sentry +│   └── compose +│   └── viewhierarchy +├── sentry-compose-helper +├── sentry-graphql +│   ├── api +│   └── src +│   ├── main +│   │   └── java +│   │   └── io +│   │   └── sentry +│   │   └── graphql +│   └── test +│   └── kotlin +│   └── io +│   └── sentry +│   └── graphql +├── sentry-graphql-22 +│   ├── api +│   └── src +│   ├── main +│   │   └── java +│   │   └── io +│   │   └── sentry +│   │   └── graphql22 +│   └── test +│   └── kotlin +│   └── io +│   └── sentry +│   └── graphql22 +├── sentry-graphql-core +│   ├── api +│   └── src +│   ├── main +│   │   └── java +│   │   └── io +│   │   └── sentry +│   │   └── graphql +│   └── test +│   └── kotlin +│   └── io +│   └── sentry +│   └── graphql +├── sentry-jdbc +│   ├── api +│   └── src +│   ├── main +│   │   ├── java +│   │   │   └── io +│   │   │   └── sentry +│   │   │   └── jdbc +│   │   └── resources +│   │   └── META-INF +│   │   └── services +│   └── test +│   └── kotlin +│   └── io +│   └── sentry +│   └── jdbc +├── sentry-jul +│   ├── api +│   └── src +│   ├── main +│   │   └── java +│   │   └── io +│   │   └── sentry +│   │   └── jul +│   └── test +│   ├── kotlin +│   │   └── io +│   │   └── sentry +│   │   └── jul +│   └── resources +│   └── mockito-extensions +├── sentry-kotlin-extensions +│   ├── api +│   └── src +│   ├── main +│   │   └── java +│   │   └── io +│   │   └── sentry +│   │   └── kotlin +│   └── test +│   └── java +│   └── io +│   └── sentry +│   └── kotlin +├── sentry-log4j2 +│   ├── api +│   └── src +│   ├── main +│   │   └── java +│   │   └── io +│   │   └── sentry +│   │   └── log4j2 +│   └── test +│   ├── kotlin +│   │   └── io +│   │   └── sentry +│   │   └── log4j2 +│   └── resources +│   └── mockito-extensions +├── sentry-logback +│   ├── api +│   └── src +│   ├── main +│   │   └── java +│   │   └── io +│   │   └── sentry +│   │   └── logback +│   └── test +│   ├── kotlin +│   │   └── io +│   │   └── sentry +│   │   └── logback +│   └── resources +│   └── mockito-extensions +├── sentry-okhttp +│   ├── api +│   └── src +│   ├── main +│   │   ├── java +│   │   │   └── io +│   │   │   └── sentry +│   │   │   └── okhttp +│   │   └── resources +│   │   └── META-INF +│   │   └── proguard +│   └── test +│   ├── java +│   │   └── io +│   │   └── sentry +│   │   └── okhttp +│   └── resources +│   └── mockito-extensions +├── sentry-openfeign +│   ├── api +│   └── src +│   ├── main +│   │   └── java +│   │   └── io +│   │   └── sentry +│   │   └── openfeign +│   └── test +│   ├── kotlin +│   │   └── io +│   │   └── sentry +│   │   └── openfeign +│   └── resources +│   └── mockito-extensions +├── sentry-opentelemetry +│   ├── sentry-opentelemetry-agent +│   │   └── src +│   │   └── main +│   │   └── java +│   │   └── io +│   │   └── sentry +│   │   └── opentelemetry +│   │   └── agent +│   ├── sentry-opentelemetry-agentcustomization +│   │   ├── api +│   │   └── src +│   │   └── main +│   │   ├── java +│   │   │   └── io +│   │   │   └── sentry +│   │   │   └── opentelemetry +│   │   └── resources +│   │   └── META-INF +│   │   └── services +│   ├── sentry-opentelemetry-agentless +│   │   └── src +│   │   └── main +│   │   └── java +│   │   └── io +│   │   └── sentry +│   │   └── opentelemetry +│   │   └── agent +│   ├── sentry-opentelemetry-agentless-spring +│   │   └── src +│   │   └── main +│   │   └── java +│   │   └── io +│   │   └── sentry +│   │   └── opentelemetry +│   │   └── agent +│   ├── sentry-opentelemetry-bootstrap +│   │   ├── api +│   │   └── src +│   │   └── main +│   │   ├── java +│   │   │   └── io +│   │   │   └── sentry +│   │   │   └── opentelemetry +│   │   └── resources +│   │   └── META-INF +│   │   └── services +│   └── sentry-opentelemetry-core +│   ├── api +│   └── src +│   ├── main +│   │   └── java +│   │   └── io +│   │   └── sentry +│   │   └── opentelemetry +│   └── test +│   └── kotlin +├── sentry-quartz +│   ├── api +│   └── src +│   └── main +│   └── java +│   └── io +│   └── sentry +│   └── quartz +├── sentry-reactor +│   ├── api +│   └── src +│   ├── main +│   │   ├── java +│   │   │   └── io +│   │   │   └── sentry +│   │   │   └── reactor +│   │   └── resources +│   │   └── META-INF +│   │   └── services +│   └── test +│   └── kotlin +│   └── io +│   └── sentry +│   └── reactor +├── sentry-samples +│   ├── sentry-samples-android +│   │   └── src +│   │   └── main +│   │   ├── cpp +│   │   ├── java +│   │   │   └── io +│   │   │   └── sentry +│   │   │   └── samples +│   │   │   └── android +│   │   │   └── compose +│   │   └── res +│   │   ├── drawable +│   │   ├── drawable-v24 +│   │   ├── layout +│   │   ├── mipmap-anydpi-v26 +│   │   ├── mipmap-hdpi +│   │   ├── mipmap-mdpi +│   │   ├── mipmap-xhdpi +│   │   ├── mipmap-xxhdpi +│   │   ├── mipmap-xxxhdpi +│   │   ├── raw +│   │   ├── values +│   │   └── xml +│   ├── sentry-samples-console +│   │   └── src +│   │   └── main +│   │   └── java +│   │   └── io +│   │   └── sentry +│   │   └── samples +│   │   └── console +│   ├── sentry-samples-console-opentelemetry-noagent +│   │   └── src +│   │   └── main +│   │   └── java +│   │   └── io +│   │   └── sentry +│   │   └── samples +│   │   └── console +│   ├── sentry-samples-jul +│   │   └── src +│   │   └── main +│   │   ├── java +│   │   │   └── io +│   │   │   └── sentry +│   │   │   └── samples +│   │   │   └── jul +│   │   └── resources +│   ├── sentry-samples-log4j2 +│   │   └── src +│   │   └── main +│   │   ├── java +│   │   │   └── io +│   │   │   └── sentry +│   │   │   └── samples +│   │   │   └── log4j2 +│   │   └── resources +│   ├── sentry-samples-logback +│   │   └── src +│   │   └── main +│   │   ├── java +│   │   │   └── io +│   │   │   └── sentry +│   │   │   └── samples +│   │   │   └── logback +│   │   └── resources +│   ├── sentry-samples-netflix-dgs +│   │   └── src +│   │   └── main +│   │   ├── java +│   │   │   └── io +│   │   │   └── sentry +│   │   │   └── samples +│   │   │   └── netflix +│   │   │   └── dgs +│   │   │   └── graphql +│   │   │   └── types +│   │   └── resources +│   │   └── schema +│   ├── sentry-samples-openfeign +│   │   └── src +│   │   └── main +│   │   └── java +│   │   └── io +│   │   └── sentry +│   │   └── samples +│   │   └── openfeign +│   ├── sentry-samples-servlet +│   │   └── src +│   │   └── main +│   │   ├── java +│   │   │   └── io +│   │   │   └── sentry +│   │   │   └── samples +│   │   │   └── servlet +│   │   └── webapp +│   │   └── WEB-INF +│   ├── sentry-samples-spring +│   │   └── src +│   │   └── main +│   │   ├── java +│   │   │   └── io +│   │   │   └── sentry +│   │   │   └── samples +│   │   │   └── spring +│   │   │   └── web +│   │   └── resources +│   ├── sentry-samples-spring-boot +│   │   └── src +│   │   ├── main +│   │   │   ├── java +│   │   │   │   └── io +│   │   │   │   └── sentry +│   │   │   │   └── samples +│   │   │   │   └── spring +│   │   │   │   └── boot +│   │   │   │   ├── graphql +│   │   │   │   └── quartz +│   │   │   └── resources +│   │   │   └── graphql +│   │   └── test +│   │   ├── kotlin +│   │   │   └── io +│   │   │   └── sentry +│   │   │   └── systemtest +│   │   └── resources +│   ├── sentry-samples-spring-boot-jakarta +│   │   └── src +│   │   ├── main +│   │   │   ├── java +│   │   │   │   └── io +│   │   │   │   └── sentry +│   │   │   │   └── samples +│   │   │   │   └── spring +│   │   │   │   └── boot +│   │   │   │   └── jakarta +│   │   │   │   ├── graphql +│   │   │   │   └── quartz +│   │   │   └── resources +│   │   │   └── graphql +│   │   └── test +│   │   ├── kotlin +│   │   │   └── io +│   │   │   └── sentry +│   │   │   └── systemtest +│   │   └── resources +│   ├── sentry-samples-spring-boot-jakarta-opentelemetry +│   │   └── src +│   │   ├── main +│   │   │   ├── java +│   │   │   │   └── io +│   │   │   │   └── sentry +│   │   │   │   └── samples +│   │   │   │   └── spring +│   │   │   │   └── boot +│   │   │   │   └── jakarta +│   │   │   │   ├── graphql +│   │   │   │   └── quartz +│   │   │   └── resources +│   │   │   └── graphql +│   │   └── test +│   │   ├── kotlin +│   │   │   └── io +│   │   │   └── sentry +│   │   │   └── systemtest +│   │   └── resources +│   ├── sentry-samples-spring-boot-jakarta-opentelemetry-noagent +│   │   └── src +│   │   ├── main +│   │   │   ├── java +│   │   │   │   └── io +│   │   │   │   └── sentry +│   │   │   │   └── samples +│   │   │   │   └── spring +│   │   │   │   └── boot +│   │   │   │   └── jakarta +│   │   │   │   ├── graphql +│   │   │   │   └── quartz +│   │   │   └── resources +│   │   │   └── graphql +│   │   └── test +│   │   ├── kotlin +│   │   │   └── io +│   │   │   └── sentry +│   │   │   └── systemtest +│   │   └── resources +│   ├── sentry-samples-spring-boot-opentelemetry +│   │   └── src +│   │   ├── main +│   │   │   ├── java +│   │   │   │   └── io +│   │   │   │   └── sentry +│   │   │   │   └── samples +│   │   │   │   └── spring +│   │   │   │   └── boot +│   │   │   │   ├── graphql +│   │   │   │   └── quartz +│   │   │   └── resources +│   │   │   └── graphql +│   │   └── test +│   │   ├── kotlin +│   │   │   └── io +│   │   │   └── sentry +│   │   │   └── systemtest +│   │   └── resources +│   ├── sentry-samples-spring-boot-opentelemetry-noagent +│   │   └── src +│   │   ├── main +│   │   │   ├── java +│   │   │   │   └── io +│   │   │   │   └── sentry +│   │   │   │   └── samples +│   │   │   │   └── spring +│   │   │   │   └── boot +│   │   │   │   ├── graphql +│   │   │   │   └── quartz +│   │   │   └── resources +│   │   │   └── graphql +│   │   └── test +│   │   ├── kotlin +│   │   │   └── io +│   │   │   └── sentry +│   │   │   └── systemtest +│   │   └── resources +│   ├── sentry-samples-spring-boot-webflux +│   │   └── src +│   │   ├── main +│   │   │   ├── java +│   │   │   │   └── io +│   │   │   │   └── sentry +│   │   │   │   └── samples +│   │   │   │   └── spring +│   │   │   │   └── boot +│   │   │   │   └── graphql +│   │   │   └── resources +│   │   │   └── graphql +│   │   └── test +│   │   ├── kotlin +│   │   │   └── io +│   │   │   └── sentry +│   │   │   └── systemtest +│   │   └── resources +│   ├── sentry-samples-spring-boot-webflux-jakarta +│   │   └── src +│   │   ├── main +│   │   │   ├── java +│   │   │   │   └── io +│   │   │   │   └── sentry +│   │   │   │   └── samples +│   │   │   │   └── spring +│   │   │   │   └── boot +│   │   │   │   └── jakarta +│   │   │   │   └── graphql +│   │   │   └── resources +│   │   │   └── graphql +│   │   └── test +│   │   ├── kotlin +│   │   │   └── io +│   │   │   └── sentry +│   │   │   └── systemtest +│   │   └── resources +│   └── sentry-samples-spring-jakarta +│   └── src +│   └── main +│   ├── java +│   │   └── io +│   │   └── sentry +│   │   └── samples +│   │   └── spring +│   │   └── jakarta +│   │   └── web +│   └── resources +├── sentry-servlet +│   ├── api +│   └── src +│   ├── main +│   │   ├── java +│   │   │   └── io +│   │   │   └── sentry +│   │   │   └── servlet +│   │   └── resources +│   │   └── META-INF +│   │   └── services +│   └── test +│   └── kotlin +│   └── io +│   └── sentry +│   └── servlet +├── sentry-servlet-jakarta +│   ├── api +│   └── src +│   ├── main +│   │   ├── java +│   │   │   └── io +│   │   │   └── sentry +│   │   │   └── servlet +│   │   │   └── jakarta +│   │   └── resources +│   │   └── META-INF +│   │   └── services +│   └── test +│   └── kotlin +│   └── io +│   └── sentry +│   └── servlet +│   └── jakarta +├── sentry-spring +│   ├── api +│   └── src +│   ├── main +│   │   ├── java +│   │   │   └── io +│   │   │   └── sentry +│   │   │   └── spring +│   │   │   ├── checkin +│   │   │   ├── exception +│   │   │   ├── graphql +│   │   │   ├── opentelemetry +│   │   │   ├── tracing +│   │   │   └── webflux +│   │   └── resources +│   │   └── META-INF +│   │   └── services +│   └── test +│   ├── kotlin +│   │   └── io +│   │   └── sentry +│   │   └── spring +│   │   ├── exception +│   │   ├── graphql +│   │   ├── mvc +│   │   ├── tracing +│   │   └── webflux +│   └── resources +│   └── mockito-extensions +├── sentry-spring-boot +│   ├── api +│   └── src +│   ├── main +│   │   ├── java +│   │   │   └── io +│   │   │   └── sentry +│   │   │   └── spring +│   │   │   └── boot +│   │   │   └── graphql +│   │   └── resources +│   │   └── META-INF +│   │   └── native-image +│   │   └── io.sentry +│   │   └── sentry +│   └── test +│   ├── kotlin +│   │   ├── com +│   │   │   └── acme +│   │   └── io +│   │   └── sentry +│   │   └── spring +│   │   └── boot +│   │   └── it +│   └── resources +│   └── mockito-extensions +├── sentry-spring-boot-jakarta +│   ├── api +│   └── src +│   ├── main +│   │   ├── java +│   │   │   └── io +│   │   │   └── sentry +│   │   │   └── spring +│   │   │   └── boot +│   │   │   └── jakarta +│   │   │   └── graphql +│   │   └── resources +│   │   └── META-INF +│   │   ├── native-image +│   │   │   └── io.sentry +│   │   │   └── sentry +│   │   └── spring +│   └── test +│   ├── kotlin +│   │   ├── com +│   │   │   └── acme +│   │   └── io +│   │   └── sentry +│   │   └── spring +│   │   └── boot +│   │   └── jakarta +│   │   └── it +│   └── resources +│   └── mockito-extensions +├── sentry-spring-boot-starter +│   └── api +├── sentry-spring-boot-starter-jakarta +│   └── api +├── sentry-spring-jakarta +│   ├── api +│   └── src +│   ├── main +│   │   ├── java +│   │   │   └── io +│   │   │   └── sentry +│   │   │   └── spring +│   │   │   └── jakarta +│   │   │   ├── checkin +│   │   │   ├── exception +│   │   │   ├── graphql +│   │   │   ├── opentelemetry +│   │   │   ├── tracing +│   │   │   └── webflux +│   │   │   └── reactor +│   │   └── resources +│   │   └── META-INF +│   │   └── services +│   └── test +│   └── kotlin +│   └── io +│   └── sentry +│   └── spring +│   └── jakarta +│   ├── exception +│   ├── graphql +│   ├── mvc +│   ├── tracing +│   └── webflux +├── sentry-system-test-support +│   ├── api +│   └── src +│   └── main +│   ├── graphql +│   └── kotlin +│   └── io +│   └── sentry +│   └── systemtest +│   ├── graphql +│   └── util +├── sentry-test-support +│   ├── api +│   └── src +│   └── main +│   └── kotlin +│   └── io +│   └── sentry +│   └── test +└── test + +1121 directories diff --git a/project_structure.txt b/project_structure.txt new file mode 100644 index 0000000000..6b0dd6ef81 --- /dev/null +++ b/project_structure.txt @@ -0,0 +1,3306 @@ +. +├── .craft.yml +├── .editorconfig +├── .gitattributes +├── .github +│   ├── CODEOWNERS +│   ├── ISSUE_TEMPLATE +│   │   ├── bug_report_android.yml +│   │   ├── bug_report_java.yml +│   │   ├── config.yml +│   │   ├── feature_android.yml +│   │   ├── feature_java.yml +│   │   └── maintainer-blank.yml +│   ├── dependabot.yml +│   ├── file-filters.yml +│   ├── pull_request_template.md +│   └── workflows +│   ├── add-platform-label.yml +│   ├── agp-matrix.yml +│   ├── build.yml +│   ├── changes-in-high-risk-code.yml +│   ├── codeql-analysis.yml +│   ├── danger.yml +│   ├── enforce-license-compliance.yml +│   ├── format-code.yml +│   ├── integration-tests-benchmarks.yml +│   ├── integration-tests-ui-critical.yml +│   ├── integration-tests-ui.yml +│   ├── release-build.yml +│   ├── release.yml +│   ├── system-tests-backend.yml +│   └── update-deps.yml +├── .gitignore +├── .gitmodules +├── .mvn +│   └── wrapper +│   ├── MavenWrapperDownloader.java +│   └── maven-wrapper.properties +├── .sauce +│   ├── sentry-uitest-android-benchmark-lite.yml +│   ├── sentry-uitest-android-benchmark.yml +│   └── sentry-uitest-android-ui.yml +├── CHANGELOG.md +├── CONTRIBUTING.md +├── LICENSE +├── MIGRATION.md +├── Makefile +├── README.md +├── aidocs +│   ├── .cursorrules +│   ├── README.md +│   ├── mermaid +│   │   ├── README.md +│   │   ├── sentry-crash-monitoring-anr-detection.mmd +│   │   ├── sentry-crash-monitoring-crash-recovery.mmd +│   │   ├── sentry-crash-monitoring-exception-capture.mmd +│   │   ├── sentry-crash-monitoring-native-crash.mmd +│   │   ├── sentry-crash-monitoring-startup-crash.mmd +│   │   ├── sentry-init-quick-reference-android-flow.mmd +│   │   ├── sentry-initialization-flow-android-initialization.mmd +│   │   ├── sentry-initialization-flow-client-creation.mmd +│   │   ├── sentry-initialization-flow-configuration-loading.mmd +│   │   ├── sentry-initialization-flow-core-initialization.mmd +│   │   ├── sentry-initialization-flow-integration-registration.mmd +│   │   ├── sentry-startup-monitoring-time-measurement.mmd +│   │   └── svg +│   │   ├── sentry-crash-monitoring-anr-detection.svg +│   │   ├── sentry-crash-monitoring-crash-recovery.svg +│   │   ├── sentry-crash-monitoring-exception-capture.svg +│   │   ├── sentry-crash-monitoring-native-crash.svg +│   │   ├── sentry-crash-monitoring-startup-crash.svg +│   │   ├── sentry-init-quick-reference-android-flow.svg +│   │   ├── sentry-initialization-flow-android-initialization.svg +│   │   ├── sentry-initialization-flow-client-creation.svg +│   │   ├── sentry-initialization-flow-configuration-loading.svg +│   │   ├── sentry-initialization-flow-core-initialization.svg +│   │   ├── sentry-initialization-flow-integration-registration.svg +│   │   └── sentry-startup-monitoring-time-measurement.svg +│   ├── rules +│   │   ├── .cursorrules +│   │   └── README.md +│   ├── sentry-crash-monitoring.md +│   ├── sentry-init-quick-reference.md +│   ├── sentry-initialization-details.md +│   ├── sentry-initialization-flow.md +│   ├── sentry-network-monitoring.md +│   ├── sentry-profiling-analysis.md +│   ├── sentry-replay-analysis.md +│   ├── sentry-session-management.md +│   ├── sentry-startup-monitoring.md +│   └── sentry-ui-jank-monitoring.md +├── build.gradle.kts +├── buildSrc +│   ├── .kotlin +│   │   └── sessions +│   ├── build.gradle.kts +│   ├── settings.gradle.kts +│   └── src +│   └── main +│   └── java +│   ├── Config.kt +│   └── Publication.kt +├── codecov.yml +├── debug.keystore +├── detekt.yml +├── docs +│   └── stylesheet.css +├── gradle +│   ├── libs.versions.toml +│   └── wrapper +│   └── gradle-wrapper.properties +├── gradle.properties +├── gradlew +├── gradlew.bat +├── hooks +│   └── pre-commit +├── local.properties +├── project_structure.txt +├── scripts +│   ├── bump-version.sh +│   ├── commit-formatted-code.sh +│   ├── mvnw +│   ├── mvnw.cmd +│   ├── settings.xml +│   ├── test-ui-critical.sh +│   ├── toggle-codec-logs.sh +│   ├── update-gradle.sh +│   └── update-sentry-native-ndk.sh +├── sentry +│   ├── api +│   │   └── sentry.api +│   ├── build.gradle.kts +│   └── src +│   ├── main +│   │   ├── java +│   │   │   └── io +│   │   │   └── sentry +│   │   │   ├── AsyncHttpTransportFactory.java +│   │   │   ├── Attachment.java +│   │   │   ├── BackfillingEventProcessor.java +│   │   │   ├── Baggage.java +│   │   │   ├── BaggageHeader.java +│   │   │   ├── Breadcrumb.java +│   │   │   ├── CheckIn.java +│   │   │   ├── CheckInStatus.java +│   │   │   ├── CircularFifoQueue.java +│   │   │   ├── CombinedContextsView.java +│   │   │   ├── CombinedScopeView.java +│   │   │   ├── CompositePerformanceCollector.java +│   │   │   ├── CpuCollectionData.java +│   │   │   ├── CustomSamplingContext.java +│   │   │   ├── DataCategory.java +│   │   │   ├── DateUtils.java +│   │   │   ├── DeduplicateMultithreadedEventProcessor.java +│   │   │   ├── DefaultCompositePerformanceCollector.java +│   │   │   ├── DefaultScopesStorage.java +│   │   │   ├── DefaultSpanFactory.java +│   │   │   ├── DefaultVersionDetector.java +│   │   │   ├── DiagnosticLogger.java +│   │   │   ├── DirectoryProcessor.java +│   │   │   ├── DisabledQueue.java +│   │   │   ├── Dsn.java +│   │   │   ├── DsnUtil.java +│   │   │   ├── DuplicateEventDetectionEventProcessor.java +│   │   │   ├── EnvelopeReader.java +│   │   │   ├── EnvelopeSender.java +│   │   │   ├── EventProcessor.java +│   │   │   ├── ExperimentalOptions.java +│   │   │   ├── ExternalOptions.java +│   │   │   ├── FilterString.java +│   │   │   ├── FullyDisplayedReporter.java +│   │   │   ├── Hint.java +│   │   │   ├── HostnameCache.java +│   │   │   ├── HttpStatusCodeRange.java +│   │   │   ├── HubAdapter.java +│   │   │   ├── HubScopesWrapper.java +│   │   │   ├── IConnectionStatusProvider.java +│   │   │   ├── IContinuousProfiler.java +│   │   │   ├── IEnvelopeReader.java +│   │   │   ├── IEnvelopeSender.java +│   │   │   ├── IHub.java +│   │   │   ├── ILogger.java +│   │   │   ├── IMemoryCollector.java +│   │   │   ├── IOptionsObserver.java +│   │   │   ├── IPerformanceCollector.java +│   │   │   ├── IPerformanceContinuousCollector.java +│   │   │   ├── IPerformanceSnapshotCollector.java +│   │   │   ├── IReplayApi.java +│   │   │   ├── IScope.java +│   │   │   ├── IScopeObserver.java +│   │   │   ├── IScopes.java +│   │   │   ├── IScopesStorage.java +│   │   │   ├── ISentryClient.java +│   │   │   ├── ISentryExecutorService.java +│   │   │   ├── ISentryLifecycleToken.java +│   │   │   ├── ISerializer.java +│   │   │   ├── ISocketTagger.java +│   │   │   ├── ISpan.java +│   │   │   ├── ISpanFactory.java +│   │   │   ├── ITransaction.java +│   │   │   ├── ITransactionProfiler.java +│   │   │   ├── ITransportFactory.java +│   │   │   ├── IVersionDetector.java +│   │   │   ├── InitPriority.java +│   │   │   ├── Instrumenter.java +│   │   │   ├── Integration.java +│   │   │   ├── IpAddressUtils.java +│   │   │   ├── JavaMemoryCollector.java +│   │   │   ├── JsonDeserializer.java +│   │   │   ├── JsonObjectDeserializer.java +│   │   │   ├── JsonObjectReader.java +│   │   │   ├── JsonObjectSerializer.java +│   │   │   ├── JsonObjectWriter.java +│   │   │   ├── JsonReflectionObjectSerializer.java +│   │   │   ├── JsonSerializable.java +│   │   │   ├── JsonSerializer.java +│   │   │   ├── JsonUnknown.java +│   │   │   ├── MainEventProcessor.java +│   │   │   ├── ManifestVersionDetector.java +│   │   │   ├── MeasurementUnit.java +│   │   │   ├── MemoryCollectionData.java +│   │   │   ├── MonitorConfig.java +│   │   │   ├── MonitorContexts.java +│   │   │   ├── MonitorSchedule.java +│   │   │   ├── MonitorScheduleType.java +│   │   │   ├── MonitorScheduleUnit.java +│   │   │   ├── NoOpCompositePerformanceCollector.java +│   │   │   ├── NoOpConnectionStatusProvider.java +│   │   │   ├── NoOpContinuousProfiler.java +│   │   │   ├── NoOpEnvelopeReader.java +│   │   │   ├── NoOpHub.java +│   │   │   ├── NoOpLogger.java +│   │   │   ├── NoOpReplayBreadcrumbConverter.java +│   │   │   ├── NoOpReplayController.java +│   │   │   ├── NoOpScope.java +│   │   │   ├── NoOpScopes.java +│   │   │   ├── NoOpScopesLifecycleToken.java +│   │   │   ├── NoOpScopesStorage.java +│   │   │   ├── NoOpSentryClient.java +│   │   │   ├── NoOpSentryExecutorService.java +│   │   │   ├── NoOpSerializer.java +│   │   │   ├── NoOpSocketTagger.java +│   │   │   ├── NoOpSpan.java +│   │   │   ├── NoOpSpanFactory.java +│   │   │   ├── NoOpTransaction.java +│   │   │   ├── NoOpTransactionProfiler.java +│   │   │   ├── NoOpTransportFactory.java +│   │   │   ├── NoopVersionDetector.java +│   │   │   ├── ObjectReader.java +│   │   │   ├── ObjectWriter.java +│   │   │   ├── OptionsContainer.java +│   │   │   ├── OutboxSender.java +│   │   │   ├── PerformanceCollectionData.java +│   │   │   ├── PreviousSessionFinalizer.java +│   │   │   ├── ProfileChunk.java +│   │   │   ├── ProfileContext.java +│   │   │   ├── ProfileLifecycle.java +│   │   │   ├── ProfilingTraceData.java +│   │   │   ├── ProfilingTransactionData.java +│   │   │   ├── PropagationContext.java +│   │   │   ├── ReplayBreadcrumbConverter.java +│   │   │   ├── ReplayController.java +│   │   │   ├── ReplayRecording.java +│   │   │   ├── RequestDetails.java +│   │   │   ├── RequestDetailsResolver.java +│   │   │   ├── SamplingContext.java +│   │   │   ├── Scope.java +│   │   │   ├── ScopeBindingMode.java +│   │   │   ├── ScopeCallback.java +│   │   │   ├── ScopeObserverAdapter.java +│   │   │   ├── ScopeType.java +│   │   │   ├── Scopes.java +│   │   │   ├── ScopesAdapter.java +│   │   │   ├── ScopesStorageFactory.java +│   │   │   ├── SendCachedEnvelopeFireAndForgetIntegration.java +│   │   │   ├── SendFireAndForgetEnvelopeSender.java +│   │   │   ├── SendFireAndForgetOutboxSender.java +│   │   │   ├── Sentry.java +│   │   │   ├── SentryAppStartProfilingOptions.java +│   │   │   ├── SentryAttribute.java +│   │   │   ├── SentryAttributeType.java +│   │   │   ├── SentryAttributes.java +│   │   │   ├── SentryAutoDateProvider.java +│   │   │   ├── SentryBaseEvent.java +│   │   │   ├── SentryClient.java +│   │   │   ├── SentryCrashLastRunState.java +│   │   │   ├── SentryDate.java +│   │   │   ├── SentryDateProvider.java +│   │   │   ├── SentryEnvelope.java +│   │   │   ├── SentryEnvelopeHeader.java +│   │   │   ├── SentryEnvelopeItem.java +│   │   │   ├── SentryEnvelopeItemHeader.java +│   │   │   ├── SentryEvent.java +│   │   │   ├── SentryExceptionFactory.java +│   │   │   ├── SentryExecutorService.java +│   │   │   ├── SentryInstantDate.java +│   │   │   ├── SentryInstantDateProvider.java +│   │   │   ├── SentryIntegrationPackageStorage.java +│   │   │   ├── SentryItemType.java +│   │   │   ├── SentryLevel.java +│   │   │   ├── SentryLockReason.java +│   │   │   ├── SentryLogEvent.java +│   │   │   ├── SentryLogEventAttributeValue.java +│   │   │   ├── SentryLogEvents.java +│   │   │   ├── SentryLogLevel.java +│   │   │   ├── SentryLongDate.java +│   │   │   ├── SentryNanotimeDate.java +│   │   │   ├── SentryNanotimeDateProvider.java +│   │   │   ├── SentryOpenTelemetryMode.java +│   │   │   ├── SentryOptions.java +│   │   │   ├── SentryReplayEvent.java +│   │   │   ├── SentryReplayOptions.java +│   │   │   ├── SentryRuntimeEventProcessor.java +│   │   │   ├── SentrySpanStorage.java +│   │   │   ├── SentryStackTraceFactory.java +│   │   │   ├── SentryThreadFactory.java +│   │   │   ├── SentryTraceHeader.java +│   │   │   ├── SentryTracer.java +│   │   │   ├── SentryUUID.java +│   │   │   ├── SentryValues.java +│   │   │   ├── SentryWrapper.java +│   │   │   ├── Session.java +│   │   │   ├── ShutdownHookIntegration.java +│   │   │   ├── Span.java +│   │   │   ├── SpanContext.java +│   │   │   ├── SpanDataConvention.java +│   │   │   ├── SpanFactoryFactory.java +│   │   │   ├── SpanFinishedCallback.java +│   │   │   ├── SpanId.java +│   │   │   ├── SpanOptions.java +│   │   │   ├── SpanStatus.java +│   │   │   ├── SpotlightIntegration.java +│   │   │   ├── Stack.java +│   │   │   ├── SynchronizedCollection.java +│   │   │   ├── SynchronizedQueue.java +│   │   │   ├── SystemOutLogger.java +│   │   │   ├── TraceContext.java +│   │   │   ├── TracesSampler.java +│   │   │   ├── TracesSamplingDecision.java +│   │   │   ├── TransactionContext.java +│   │   │   ├── TransactionFinishedCallback.java +│   │   │   ├── TransactionOptions.java +│   │   │   ├── TypeCheckHint.java +│   │   │   ├── UncaughtExceptionHandler.java +│   │   │   ├── UncaughtExceptionHandlerIntegration.java +│   │   │   ├── UserFeedback.java +│   │   │   ├── backpressure +│   │   │   │   ├── BackpressureMonitor.java +│   │   │   │   ├── IBackpressureMonitor.java +│   │   │   │   └── NoOpBackpressureMonitor.java +│   │   │   ├── cache +│   │   │   │   ├── CacheStrategy.java +│   │   │   │   ├── CacheUtils.java +│   │   │   │   ├── EnvelopeCache.java +│   │   │   │   ├── IEnvelopeCache.java +│   │   │   │   ├── PersistingOptionsObserver.java +│   │   │   │   ├── PersistingScopeObserver.java +│   │   │   │   └── tape +│   │   │   │   ├── EmptyObjectQueue.java +│   │   │   │   ├── FileObjectQueue.java +│   │   │   │   ├── ObjectQueue.java +│   │   │   │   └── QueueFile.java +│   │   │   ├── clientreport +│   │   │   │   ├── AtomicClientReportStorage.java +│   │   │   │   ├── ClientReport.java +│   │   │   │   ├── ClientReportKey.java +│   │   │   │   ├── ClientReportRecorder.java +│   │   │   │   ├── DiscardReason.java +│   │   │   │   ├── DiscardedEvent.java +│   │   │   │   ├── IClientReportRecorder.java +│   │   │   │   ├── IClientReportStorage.java +│   │   │   │   └── NoOpClientReportRecorder.java +│   │   │   ├── config +│   │   │   │   ├── AbstractPropertiesProvider.java +│   │   │   │   ├── ClasspathPropertiesLoader.java +│   │   │   │   ├── CompositePropertiesProvider.java +│   │   │   │   ├── EnvironmentVariablePropertiesProvider.java +│   │   │   │   ├── FilesystemPropertiesLoader.java +│   │   │   │   ├── PropertiesLoader.java +│   │   │   │   ├── PropertiesProvider.java +│   │   │   │   ├── PropertiesProviderFactory.java +│   │   │   │   ├── SimplePropertiesProvider.java +│   │   │   │   └── SystemPropertyPropertiesProvider.java +│   │   │   ├── exception +│   │   │   │   ├── ExceptionMechanismException.java +│   │   │   │   ├── InvalidSentryTraceHeaderException.java +│   │   │   │   ├── SentryEnvelopeException.java +│   │   │   │   └── SentryHttpClientException.java +│   │   │   ├── hints +│   │   │   │   ├── AbnormalExit.java +│   │   │   │   ├── ApplyScopeData.java +│   │   │   │   ├── Backfillable.java +│   │   │   │   ├── BlockingFlushHint.java +│   │   │   │   ├── Cached.java +│   │   │   │   ├── DiskFlushNotification.java +│   │   │   │   ├── Enqueable.java +│   │   │   │   ├── EventDropReason.java +│   │   │   │   ├── Flushable.java +│   │   │   │   ├── Resettable.java +│   │   │   │   ├── Retryable.java +│   │   │   │   ├── SessionEnd.java +│   │   │   │   ├── SessionEndHint.java +│   │   │   │   ├── SessionStart.java +│   │   │   │   ├── SessionStartHint.java +│   │   │   │   ├── SubmissionResult.java +│   │   │   │   └── TransactionEnd.java +│   │   │   ├── instrumentation +│   │   │   │   └── file +│   │   │   │   ├── FileIOSpanManager.java +│   │   │   │   ├── FileInputStreamInitData.java +│   │   │   │   ├── FileOutputStreamInitData.java +│   │   │   │   ├── SentryFileInputStream.java +│   │   │   │   ├── SentryFileOutputStream.java +│   │   │   │   ├── SentryFileReader.java +│   │   │   │   └── SentryFileWriter.java +│   │   │   ├── internal +│   │   │   │   ├── ManifestVersionReader.java +│   │   │   │   ├── debugmeta +│   │   │   │   │   ├── IDebugMetaLoader.java +│   │   │   │   │   ├── NoOpDebugMetaLoader.java +│   │   │   │   │   └── ResourcesDebugMetaLoader.java +│   │   │   │   ├── eventprocessor +│   │   │   │   │   └── EventProcessorAndOrder.java +│   │   │   │   ├── gestures +│   │   │   │   │   ├── GestureTargetLocator.java +│   │   │   │   │   └── UiElement.java +│   │   │   │   ├── modules +│   │   │   │   │   ├── CompositeModulesLoader.java +│   │   │   │   │   ├── IModulesLoader.java +│   │   │   │   │   ├── ManifestModulesLoader.java +│   │   │   │   │   ├── ModulesLoader.java +│   │   │   │   │   ├── NoOpModulesLoader.java +│   │   │   │   │   └── ResourcesModulesLoader.java +│   │   │   │   └── viewhierarchy +│   │   │   │   └── ViewHierarchyExporter.java +│   │   │   ├── logger +│   │   │   │   ├── ILoggerApi.java +│   │   │   │   ├── ILoggerBatchProcessor.java +│   │   │   │   ├── LoggerApi.java +│   │   │   │   ├── LoggerBatchProcessor.java +│   │   │   │   ├── NoOpLoggerApi.java +│   │   │   │   ├── NoOpLoggerBatchProcessor.java +│   │   │   │   └── SentryLogParameters.java +│   │   │   ├── opentelemetry +│   │   │   │   └── OpenTelemetryUtil.java +│   │   │   ├── profilemeasurements +│   │   │   │   ├── ProfileMeasurement.java +│   │   │   │   └── ProfileMeasurementValue.java +│   │   │   ├── protocol +│   │   │   │   ├── App.java +│   │   │   │   ├── Browser.java +│   │   │   │   ├── Contexts.java +│   │   │   │   ├── DebugImage.java +│   │   │   │   ├── DebugMeta.java +│   │   │   │   ├── Device.java +│   │   │   │   ├── Feedback.java +│   │   │   │   ├── Geo.java +│   │   │   │   ├── Gpu.java +│   │   │   │   ├── MeasurementValue.java +│   │   │   │   ├── Mechanism.java +│   │   │   │   ├── Message.java +│   │   │   │   ├── MetricSummary.java +│   │   │   │   ├── OperatingSystem.java +│   │   │   │   ├── Request.java +│   │   │   │   ├── Response.java +│   │   │   │   ├── SdkInfo.java +│   │   │   │   ├── SdkVersion.java +│   │   │   │   ├── SentryException.java +│   │   │   │   ├── SentryId.java +│   │   │   │   ├── SentryPackage.java +│   │   │   │   ├── SentryRuntime.java +│   │   │   │   ├── SentrySpan.java +│   │   │   │   ├── SentryStackFrame.java +│   │   │   │   ├── SentryStackTrace.java +│   │   │   │   ├── SentryThread.java +│   │   │   │   ├── SentryTransaction.java +│   │   │   │   ├── Spring.java +│   │   │   │   ├── TransactionInfo.java +│   │   │   │   ├── TransactionNameSource.java +│   │   │   │   ├── User.java +│   │   │   │   ├── ViewHierarchy.java +│   │   │   │   └── ViewHierarchyNode.java +│   │   │   ├── rrweb +│   │   │   │   ├── RRWebBreadcrumbEvent.java +│   │   │   │   ├── RRWebEvent.java +│   │   │   │   ├── RRWebEventType.java +│   │   │   │   ├── RRWebIncrementalSnapshotEvent.java +│   │   │   │   ├── RRWebInteractionEvent.java +│   │   │   │   ├── RRWebInteractionMoveEvent.java +│   │   │   │   ├── RRWebMetaEvent.java +│   │   │   │   ├── RRWebOptionsEvent.java +│   │   │   │   ├── RRWebSpanEvent.java +│   │   │   │   └── RRWebVideoEvent.java +│   │   │   ├── transport +│   │   │   │   ├── AsyncHttpTransport.java +│   │   │   │   ├── AuthenticatorWrapper.java +│   │   │   │   ├── CurrentDateProvider.java +│   │   │   │   ├── HttpConnection.java +│   │   │   │   ├── ICurrentDateProvider.java +│   │   │   │   ├── ITransport.java +│   │   │   │   ├── ITransportGate.java +│   │   │   │   ├── NoOpEnvelopeCache.java +│   │   │   │   ├── NoOpTransport.java +│   │   │   │   ├── NoOpTransportGate.java +│   │   │   │   ├── ProxyAuthenticator.java +│   │   │   │   ├── QueuedThreadPoolExecutor.java +│   │   │   │   ├── RateLimiter.java +│   │   │   │   ├── ReusableCountLatch.java +│   │   │   │   ├── StdoutTransport.java +│   │   │   │   └── TransportResult.java +│   │   │   ├── util +│   │   │   │   ├── AutoClosableReentrantLock.java +│   │   │   │   ├── CheckInUtils.java +│   │   │   │   ├── ClassLoaderUtils.java +│   │   │   │   ├── CollectionUtils.java +│   │   │   │   ├── DebugMetaPropertiesApplier.java +│   │   │   │   ├── ErrorUtils.java +│   │   │   │   ├── EventProcessorUtils.java +│   │   │   │   ├── ExceptionUtils.java +│   │   │   │   ├── FileUtils.java +│   │   │   │   ├── HintUtils.java +│   │   │   │   ├── HttpUtils.java +│   │   │   │   ├── InitUtil.java +│   │   │   │   ├── IntegrationUtils.java +│   │   │   │   ├── JsonSerializationUtils.java +│   │   │   │   ├── LazyEvaluator.java +│   │   │   │   ├── LifecycleHelper.java +│   │   │   │   ├── LoadClass.java +│   │   │   │   ├── LogUtils.java +│   │   │   │   ├── MapObjectReader.java +│   │   │   │   ├── MapObjectWriter.java +│   │   │   │   ├── Objects.java +│   │   │   │   ├── Pair.java +│   │   │   │   ├── Platform.java +│   │   │   │   ├── PropagationTargetsUtils.java +│   │   │   │   ├── Random.java +│   │   │   │   ├── SampleRateUtils.java +│   │   │   │   ├── ScopesUtil.java +│   │   │   │   ├── SentryRandom.java +│   │   │   │   ├── SpanUtils.java +│   │   │   │   ├── StringUtils.java +│   │   │   │   ├── TracingUtils.java +│   │   │   │   ├── UUIDGenerator.java +│   │   │   │   ├── UUIDStringUtils.java +│   │   │   │   ├── UrlUtils.java +│   │   │   │   └── thread +│   │   │   │   ├── IThreadChecker.java +│   │   │   │   ├── NoOpThreadChecker.java +│   │   │   │   └── ThreadChecker.java +│   │   │   └── vendor +│   │   │   ├── Base64.java +│   │   │   └── gson +│   │   │   ├── LICENSE +│   │   │   ├── internal +│   │   │   │   └── bind +│   │   │   │   └── util +│   │   │   │   └── ISO8601Utils.java +│   │   │   └── stream +│   │   │   ├── JsonReader.java +│   │   │   ├── JsonScope.java +│   │   │   ├── JsonToken.java +│   │   │   ├── JsonWriter.java +│   │   │   └── MalformedJsonException.java +│   │   └── resources +│   │   └── META-INF +│   │   └── native-image +│   │   └── io.sentry +│   │   └── sentry +│   │   └── native-image.properties +│   └── test +│   ├── java +│   │   └── io +│   │   └── sentry +│   │   ├── AttachmentTest.kt +│   │   ├── BaggageTest.kt +│   │   ├── BreadcrumbTest.kt +│   │   ├── CachedEvent.kt +│   │   ├── CheckInSerializationTest.kt +│   │   ├── CombinedContextsViewTest.kt +│   │   ├── CombinedScopeViewTest.kt +│   │   ├── CustomCachedApplyScopeDataHint.kt +│   │   ├── CustomEventProcessor.kt +│   │   ├── DateUtilsTest.kt +│   │   ├── DeduplicateMultithreadedEventProcessorTest.kt +│   │   ├── DefaultCompositePerformanceCollectorTest.kt +│   │   ├── DenyReadFileSecurityManager.java +│   │   ├── DiagnosticLoggerTest.kt +│   │   ├── DirectoryProcessorTest.kt +│   │   ├── DisabledQueueTest.kt +│   │   ├── DsnTest.kt +│   │   ├── DsnUtilTest.kt +│   │   ├── DuplicateEventDetectionEventProcessorTest.kt +│   │   ├── EnvelopeSenderTest.kt +│   │   ├── ExternalOptionsTest.kt +│   │   ├── FileFromResources.kt +│   │   ├── FilterStringTest.kt +│   │   ├── FullyDisplayedReporterTest.kt +│   │   ├── HttpStatusCodeRangeTest.kt +│   │   ├── HubAdapterTest.kt +│   │   ├── InstrumenterTest.kt +│   │   ├── IpAddressUtilsTest.kt +│   │   ├── JavaMemoryCollectorTest.kt +│   │   ├── JsonObjectDeserializerTest.kt +│   │   ├── JsonObjectReaderTest.kt +│   │   ├── JsonObjectSerializerTest.kt +│   │   ├── JsonReflectionObjectSerializerTest.kt +│   │   ├── JsonSerializerBenchmarkTests.kt +│   │   ├── JsonSerializerTest.kt +│   │   ├── JsonUnknownSerializationTest.kt +│   │   ├── MainEventProcessorTest.kt +│   │   ├── MeasurementUnitTest.kt +│   │   ├── NoOpConnectionStatusProviderTest.kt +│   │   ├── NoOpContinuousProfilerTest.kt +│   │   ├── NoOpHubTest.kt +│   │   ├── NoOpScopeTest.kt +│   │   ├── NoOpSentryClientTest.kt +│   │   ├── NoOpSentryExecutorServiceTest.kt +│   │   ├── NoOpSerializerTest.kt +│   │   ├── NoOpSpanTest.kt +│   │   ├── NoOpTransactionProfilerTest.kt +│   │   ├── NoOpTransactionTest.kt +│   │   ├── OptionsContainerTest.kt +│   │   ├── OutboxSenderTest.kt +│   │   ├── PerformanceCollectionDataTest.kt +│   │   ├── PreviousSessionFinalizerTest.kt +│   │   ├── PropagationContextTest.kt +│   │   ├── RequestDetailsResolverTest.kt +│   │   ├── SampleDsn.kt +│   │   ├── ScopeTest.kt +│   │   ├── ScopesAdapterTest.kt +│   │   ├── ScopesTest.kt +│   │   ├── SendCachedEnvelopeFireAndForgetIntegrationTest.kt +│   │   ├── SentryAutoDateProviderTest.kt +│   │   ├── SentryBaseEventTypeTest.kt +│   │   ├── SentryClientTest.kt +│   │   ├── SentryCrashLastRunStateTest.kt +│   │   ├── SentryEnvelopeItemTest.kt +│   │   ├── SentryEnvelopeTest.kt +│   │   ├── SentryEventTest.kt +│   │   ├── SentryExceptionFactoryTest.kt +│   │   ├── SentryExecutorServiceTest.kt +│   │   ├── SentryInstantDateTest.kt +│   │   ├── SentryIntegrationPackageStorageTest.kt +│   │   ├── SentryLongDateTest.kt +│   │   ├── SentryNanotimeDateTest.kt +│   │   ├── SentryOptionsManipulator.kt +│   │   ├── SentryOptionsTest.kt +│   │   ├── SentryOptionsTracingTest.kt +│   │   ├── SentryReplayOptionsTest.kt +│   │   ├── SentryRuntimeEventProcessorTest.kt +│   │   ├── SentryStackTraceFactoryTest.kt +│   │   ├── SentryTest.kt +│   │   ├── SentryThreadFactoryTest.kt +│   │   ├── SentryTraceHeaderTest.kt +│   │   ├── SentryTracerTest.kt +│   │   ├── SentryUUIDTest.kt +│   │   ├── SentryValuesTest.kt +│   │   ├── SentryWrapperTest.kt +│   │   ├── SessionAdapterTest.kt +│   │   ├── ShutdownHookIntegrationTest.kt +│   │   ├── SpanContextTest.kt +│   │   ├── SpanStatusTest.kt +│   │   ├── SpanTest.kt +│   │   ├── StackTest.kt +│   │   ├── StringExtensions.kt +│   │   ├── TraceContextSerializationTest.kt +│   │   ├── TracePropagationTargetsTest.kt +│   │   ├── TracesSamplerTest.kt +│   │   ├── TransactionContextTest.kt +│   │   ├── TransactionContextsTest.kt +│   │   ├── UUIDStringUtilsTest.kt +│   │   ├── UncaughtExceptionHandlerIntegrationTest.kt +│   │   ├── UrlDetailsTest.kt +│   │   ├── UserFeedbackSerializationTest.kt +│   │   ├── backpressure +│   │   │   └── BackpressureMonitorTest.kt +│   │   ├── cache +│   │   │   ├── CacheStrategyTest.kt +│   │   │   ├── CacheUtilsTest.kt +│   │   │   ├── EnvelopeCacheTest.kt +│   │   │   ├── PersistingOptionsObserverTest.kt +│   │   │   ├── PersistingScopeObserverTest.kt +│   │   │   └── tape +│   │   │   ├── CorruptQueueFileTest.kt +│   │   │   ├── ObjectQueueTest.kt +│   │   │   └── QueueFileTest.kt +│   │   ├── clientreport +│   │   │   ├── AtomicClientReportStorageTest.kt +│   │   │   ├── ClientReportMultiThreadingTest.kt +│   │   │   └── ClientReportTest.kt +│   │   ├── config +│   │   │   ├── ClasspathPropertiesLoaderTest.kt +│   │   │   ├── CompositePropertiesProviderTest.kt +│   │   │   ├── EnvironmentVariablePropertiesProviderTest.kt +│   │   │   ├── FilesystemPropertiesLoaderTest.kt +│   │   │   ├── PropertiesProviderTest.kt +│   │   │   ├── SimplePropertiesProviderTest.kt +│   │   │   └── SystemPropertyPropertiesProviderTest.kt +│   │   ├── hints +│   │   │   └── HintTest.kt +│   │   ├── instrumentation +│   │   │   └── file +│   │   │   ├── FileIOSpanManagerTest.kt +│   │   │   ├── SentryFileInputStreamTest.kt +│   │   │   ├── SentryFileOutputStreamTest.kt +│   │   │   ├── SentryFileReaderTest.kt +│   │   │   └── SentryFileWriterTest.kt +│   │   ├── internal +│   │   │   ├── SpotlightIntegrationTest.kt +│   │   │   ├── debugmeta +│   │   │   │   └── ResourcesDebugMetaLoaderTest.kt +│   │   │   └── modules +│   │   │   ├── CompositeModulesLoaderTest.kt +│   │   │   ├── ManifestModulesLoaderTest.kt +│   │   │   └── ResourcesModulesLoaderTest.kt +│   │   ├── protocol +│   │   │   ├── AppSerializationTest.kt +│   │   │   ├── AppTest.kt +│   │   │   ├── BreadcrumbSerializationTest.kt +│   │   │   ├── BrowserSerializationTest.kt +│   │   │   ├── BrowserTest.kt +│   │   │   ├── CombinedContextsViewSerializationTest.kt +│   │   │   ├── ContextsSerializationTest.kt +│   │   │   ├── ContextsTest.kt +│   │   │   ├── DebugImageSerializationTest.kt +│   │   │   ├── DebugMetaSerializationTest.kt +│   │   │   ├── DebugMetaTest.kt +│   │   │   ├── DeviceSerializationTest.kt +│   │   │   ├── DeviceTest.kt +│   │   │   ├── FeedbackTest.kt +│   │   │   ├── GpuSerializationTest.kt +│   │   │   ├── GpuTest.kt +│   │   │   ├── MeasurementValueSerializationTest.kt +│   │   │   ├── MechanismSerializationTest.kt +│   │   │   ├── MechanismTest.kt +│   │   │   ├── MessageSerializationTest.kt +│   │   │   ├── MessageTest.kt +│   │   │   ├── OperatingSystemSerializationTest.kt +│   │   │   ├── OperatingSystemTest.kt +│   │   │   ├── ReplayRecordingSerializationTest.kt +│   │   │   ├── RequestSerializationTest.kt +│   │   │   ├── RequestTest.kt +│   │   │   ├── ResponseSerializationTest.kt +│   │   │   ├── SdkInfoSerializationTest.kt +│   │   │   ├── SdkVersionSerializationTest.kt +│   │   │   ├── SentryBaseEventSerializationTest.kt +│   │   │   ├── SentryEnvelopeHeaderSerializationTest.kt +│   │   │   ├── SentryEnvelopeItemHeaderSerializationTest.kt +│   │   │   ├── SentryEventSerializationTest.kt +│   │   │   ├── SentryExceptionSerializationTest.kt +│   │   │   ├── SentryIdSerializationTest.kt +│   │   │   ├── SentryIdTest.kt +│   │   │   ├── SentryItemTypeSerializationTest.kt +│   │   │   ├── SentryLockReasonSerializationTest.kt +│   │   │   ├── SentryLogsSerializationTest.kt +│   │   │   ├── SentryPackageSerializationTest.kt +│   │   │   ├── SentryReplayEventSerializationTest.kt +│   │   │   ├── SentryRuntimeSerializationTest.kt +│   │   │   ├── SentryRuntimeTest.kt +│   │   │   ├── SentrySpanSerializationTest.kt +│   │   │   ├── SentrySpanTest.kt +│   │   │   ├── SentryStackFrameSerializationTest.kt +│   │   │   ├── SentryStackTraceSerializationTest.kt +│   │   │   ├── SentryThreadSerializationTest.kt +│   │   │   ├── SentryTransactionSerializationTest.kt +│   │   │   ├── SerializationUtils.kt +│   │   │   ├── SessionSerializationTest.kt +│   │   │   ├── SpanContextSerializationTest.kt +│   │   │   ├── SpanIdSerializationTest.kt +│   │   │   ├── SpanIdTest.kt +│   │   │   ├── SpringSerializationTest.kt +│   │   │   ├── UserSerializationTest.kt +│   │   │   ├── UserTest.kt +│   │   │   ├── ViewHierarchyNodeSerializationTest.kt +│   │   │   └── ViewHierarchySerializationTest.kt +│   │   ├── rrweb +│   │   │   ├── RRWebBreadcrumbEventSerializationTest.kt +│   │   │   ├── RRWebEventSerializationTest.kt +│   │   │   ├── RRWebInteractionEventSerializationTest.kt +│   │   │   ├── RRWebInteractionMoveEventSerializationTest.kt +│   │   │   ├── RRWebMetaEventSerializationTest.kt +│   │   │   ├── RRWebOptionsEventSerializationTest.kt +│   │   │   ├── RRWebSpanEventSerializationTest.kt +│   │   │   └── RRWebVideoEventSerializationTest.kt +│   │   ├── transport +│   │   │   ├── AsyncHttpTransportClientReportTest.kt +│   │   │   ├── AsyncHttpTransportTest.kt +│   │   │   ├── HttpConnectionTest.kt +│   │   │   ├── QueuedThreadPoolExecutorTest.kt +│   │   │   ├── RateLimiterTest.kt +│   │   │   ├── ReusableCountLatchTest.kt +│   │   │   └── StdoutTransportTest.kt +│   │   ├── util +│   │   │   ├── AutoClosableReentrantLockTest.kt +│   │   │   ├── CheckInUtilsTest.kt +│   │   │   ├── CollectionUtilsTest.kt +│   │   │   ├── ExceptionUtilsTest.kt +│   │   │   ├── Extensions.kt +│   │   │   ├── FileUtilsTest.kt +│   │   │   ├── HintUtilsTest.kt +│   │   │   ├── HttpUtilsTest.kt +│   │   │   ├── InitUtilTest.kt +│   │   │   ├── JsonSerializationUtilsTest.kt +│   │   │   ├── LazyEvaluatorTest.kt +│   │   │   ├── MapObjectReaderTest.kt +│   │   │   ├── MapObjectWriterTest.kt +│   │   │   ├── PlatformTestManipulator.kt +│   │   │   ├── SampleRateUtilTest.kt +│   │   │   ├── SentryRandomTest.kt +│   │   │   ├── SpanUtilsTest.kt +│   │   │   ├── StringUtilsTest.kt +│   │   │   ├── TracingUtilsTest.kt +│   │   │   ├── UrlUtilsTest.kt +│   │   │   └── thread +│   │   │   └── ThreadCheckerTest.kt +│   │   └── vendor +│   │   └── gson +│   │   ├── internal +│   │   │   └── bind +│   │   │   └── util +│   │   │   └── ISO8601UtilsTest.java +│   │   └── stream +│   │   ├── JsonReaderTest.java +│   │   └── JsonWriterTest.java +│   └── resources +│   ├── Tongariro.jpg +│   ├── corrupt_queue_file.txt +│   ├── envelope-event-attachment.txt +│   ├── envelope-feedback.txt +│   ├── envelope-session-start.txt +│   ├── envelope-transaction-with-sample-rand.txt +│   ├── envelope-transaction-with-sample-rate.txt +│   ├── envelope-transaction.txt +│   ├── envelope_attachment.txt +│   ├── envelope_session.txt +│   ├── envelope_session_sdkversion.txt +│   ├── event.json +│   ├── event_breadcrumb_data.json +│   ├── event_with_contexts.json +│   ├── json +│   │   ├── app.json +│   │   ├── breadcrumb.json +│   │   ├── browser.json +│   │   ├── checkin_crontab.json +│   │   ├── checkin_interval.json +│   │   ├── contexts.json +│   │   ├── debug_image.json +│   │   ├── debug_meta.json +│   │   ├── device.json +│   │   ├── gpu.json +│   │   ├── measurement_value_double.json +│   │   ├── measurement_value_int.json +│   │   ├── measurement_value_missing.json +│   │   ├── mechanism.json +│   │   ├── message.json +│   │   ├── operating_system.json +│   │   ├── replay_recording.json +│   │   ├── request.json +│   │   ├── response.json +│   │   ├── rrweb_breadcrumb_event.json +│   │   ├── rrweb_event.json +│   │   ├── rrweb_interaction_event.json +│   │   ├── rrweb_interaction_move_event.json +│   │   ├── rrweb_meta_event.json +│   │   ├── rrweb_options_event.json +│   │   ├── rrweb_span_event.json +│   │   ├── rrweb_video_event.json +│   │   ├── sdk_info.json +│   │   ├── sdk_version.json +│   │   ├── sentry_base_event.json +│   │   ├── sentry_base_event_with_null_extra.json +│   │   ├── sentry_envelope_header.json +│   │   ├── sentry_envelope_item_header.json +│   │   ├── sentry_event.json +│   │   ├── sentry_exception.json +│   │   ├── sentry_id.json +│   │   ├── sentry_lock_reason.json +│   │   ├── sentry_logs.json +│   │   ├── sentry_package.json +│   │   ├── sentry_replay_event.json +│   │   ├── sentry_runtime.json +│   │   ├── sentry_span.json +│   │   ├── sentry_span_legacy_date_format.json +│   │   ├── sentry_stack_frame.json +│   │   ├── sentry_stack_trace.json +│   │   ├── sentry_thread.json +│   │   ├── sentry_transaction.json +│   │   ├── sentry_transaction_legacy_date_format.json +│   │   ├── sentry_transaction_no_measurement_unit.json +│   │   ├── session.json +│   │   ├── span_context.json +│   │   ├── span_context_null_op.json +│   │   ├── span_id.json +│   │   ├── spring.json +│   │   ├── trace_state.json +│   │   ├── trace_state_no_sample_rate.json +│   │   ├── user.json +│   │   ├── view_hierarchy.json +│   │   └── view_hierarchy_node.json +│   ├── mockito-extensions +│   │   └── org.mockito.plugins.MockMaker +│   └── session.json +├── sentry-android +│   ├── build.gradle.kts +│   ├── proguard-rules.pro +│   └── src +│   └── main +│   ├── AndroidManifest.xml +│   └── res +│   └── values +│   └── public.xml +├── sentry-android-core +│   ├── .gitignore +│   ├── api +│   │   └── sentry-android-core.api +│   ├── build.gradle.kts +│   ├── proguard-rules.pro +│   └── src +│   ├── main +│   │   ├── AndroidManifest.xml +│   │   ├── java +│   │   │   └── io +│   │   │   └── sentry +│   │   │   └── android +│   │   │   └── core +│   │   │   ├── ANRWatchDog.java +│   │   │   ├── ActivityBreadcrumbsIntegration.java +│   │   │   ├── ActivityFramesTracker.java +│   │   │   ├── ActivityLifecycleIntegration.java +│   │   │   ├── AndroidContinuousProfiler.java +│   │   │   ├── AndroidCpuCollector.java +│   │   │   ├── AndroidDateUtils.java +│   │   │   ├── AndroidFatalLogger.java +│   │   │   ├── AndroidLogger.java +│   │   │   ├── AndroidMemoryCollector.java +│   │   │   ├── AndroidOptionsInitializer.java +│   │   │   ├── AndroidProfiler.java +│   │   │   ├── AndroidSocketTagger.java +│   │   │   ├── AndroidTransactionProfiler.java +│   │   │   ├── AndroidTransportGate.java +│   │   │   ├── AnrIntegration.java +│   │   │   ├── AnrIntegrationFactory.java +│   │   │   ├── AnrV2EventProcessor.java +│   │   │   ├── AnrV2Integration.java +│   │   │   ├── AppComponentsBreadcrumbsIntegration.java +│   │   │   ├── AppLifecycleIntegration.java +│   │   │   ├── AppState.java +│   │   │   ├── ApplicationNotResponding.java +│   │   │   ├── BuildInfoProvider.java +│   │   │   ├── ContextUtils.java +│   │   │   ├── CurrentActivityHolder.java +│   │   │   ├── DefaultAndroidEventProcessor.java +│   │   │   ├── DeviceInfoUtil.java +│   │   │   ├── EmptySecureContentProvider.java +│   │   │   ├── EnvelopeFileObserver.java +│   │   │   ├── EnvelopeFileObserverIntegration.java +│   │   │   ├── IDebugImagesLoader.java +│   │   │   ├── Installation.java +│   │   │   ├── InternalSentrySdk.java +│   │   │   ├── LifecycleWatcher.java +│   │   │   ├── LoadClass.java +│   │   │   ├── MainLooperHandler.java +│   │   │   ├── ManifestMetadataReader.java +│   │   │   ├── NdkHandlerStrategy.java +│   │   │   ├── NdkIntegration.java +│   │   │   ├── NetworkBreadcrumbsIntegration.java +│   │   │   ├── NoOpDebugImagesLoader.java +│   │   │   ├── PerformanceAndroidEventProcessor.java +│   │   │   ├── ScreenshotEventProcessor.java +│   │   │   ├── SendCachedEnvelopeIntegration.java +│   │   │   ├── SentryAndroid.java +│   │   │   ├── SentryAndroidDateProvider.java +│   │   │   ├── SentryAndroidOptions.java +│   │   │   ├── SentryFrameMetrics.java +│   │   │   ├── SentryInitProvider.java +│   │   │   ├── SentryLogcatAdapter.java +│   │   │   ├── SentryPerformanceProvider.java +│   │   │   ├── SpanFrameMetricsCollector.java +│   │   │   ├── SystemEventsBreadcrumbsIntegration.java +│   │   │   ├── UserInteractionIntegration.java +│   │   │   ├── ViewHierarchyEventProcessor.java +│   │   │   ├── cache +│   │   │   │   └── AndroidEnvelopeCache.java +│   │   │   ├── internal +│   │   │   │   ├── debugmeta +│   │   │   │   │   └── AssetsDebugMetaLoader.java +│   │   │   │   ├── gestures +│   │   │   │   │   ├── AndroidViewGestureTargetLocator.java +│   │   │   │   │   ├── NoOpWindowCallback.java +│   │   │   │   │   ├── SentryGestureListener.java +│   │   │   │   │   ├── SentryWindowCallback.java +│   │   │   │   │   ├── ViewUtils.java +│   │   │   │   │   └── WindowCallbackAdapter.java +│   │   │   │   ├── modules +│   │   │   │   │   └── AssetsModulesLoader.java +│   │   │   │   ├── threaddump +│   │   │   │   │   ├── Line.java +│   │   │   │   │   ├── Lines.java +│   │   │   │   │   └── ThreadDumpParser.java +│   │   │   │   └── util +│   │   │   │   ├── AndroidConnectionStatusProvider.java +│   │   │   │   ├── AndroidCurrentDateProvider.java +│   │   │   │   ├── AndroidThreadChecker.java +│   │   │   │   ├── BreadcrumbFactory.java +│   │   │   │   ├── ClassUtil.java +│   │   │   │   ├── ContentProviderSecurityChecker.java +│   │   │   │   ├── CpuInfoUtils.java +│   │   │   │   ├── Debouncer.java +│   │   │   │   ├── DeviceOrientations.java +│   │   │   │   ├── FirstDrawDoneListener.java +│   │   │   │   ├── Permissions.java +│   │   │   │   ├── RootChecker.java +│   │   │   │   ├── ScreenshotUtils.java +│   │   │   │   └── SentryFrameMetricsCollector.java +│   │   │   ├── performance +│   │   │   │   ├── ActivityLifecycleCallbacksAdapter.java +│   │   │   │   ├── ActivityLifecycleSpanHelper.java +│   │   │   │   ├── ActivityLifecycleTimeSpan.java +│   │   │   │   ├── AppStartMetrics.java +│   │   │   │   ├── TimeSpan.java +│   │   │   │   └── WindowContentChangedCallback.java +│   │   │   └── util +│   │   │   └── AndroidLazyEvaluator.java +│   │   └── res +│   │   └── values +│   │   └── public.xml +│   └── test +│   ├── AndroidManifest.xml +│   ├── assets +│   │   └── sentry-debug-meta.properties +│   ├── java +│   │   └── io +│   │   └── sentry +│   │   └── android +│   │   └── core +│   │   ├── ANRWatchDogTest.kt +│   │   ├── ActivityBreadcrumbsIntegrationTest.kt +│   │   ├── ActivityFramesTrackerTest.kt +│   │   ├── ActivityLifecycleIntegrationTest.kt +│   │   ├── AndroidConnectionStatusProviderTest.kt +│   │   ├── AndroidContinuousProfilerTest.kt +│   │   ├── AndroidCpuCollectorTest.kt +│   │   ├── AndroidMemoryCollectorTest.kt +│   │   ├── AndroidOptionsInitializerTest.kt +│   │   ├── AndroidProfilerTest.kt +│   │   ├── AndroidTransactionProfilerTest.kt +│   │   ├── AndroidTransportGateTest.kt +│   │   ├── AnrIntegrationTest.kt +│   │   ├── AnrV2EventProcessorTest.kt +│   │   ├── AnrV2IntegrationTest.kt +│   │   ├── AppComponentsBreadcrumbsIntegrationTest.kt +│   │   ├── AppLifecycleIntegrationTest.kt +│   │   ├── ApplicationStub.kt +│   │   ├── CachedEvent.kt +│   │   ├── ContextUtilsTest.kt +│   │   ├── ContextUtilsTestHelper.kt +│   │   ├── CustomCachedApplyScopeDataHint.kt +│   │   ├── DefaultAndroidEventProcessorTest.kt +│   │   ├── DeviceInfoUtilTest.kt +│   │   ├── EnvelopeFileObserverIntegrationTest.kt +│   │   ├── EnvelopeFileObserverTest.kt +│   │   ├── InstallationTest.kt +│   │   ├── InternalSentrySdkTest.kt +│   │   ├── LifecycleWatcherTest.kt +│   │   ├── ManifestMetadataReaderTest.kt +│   │   ├── NdkIntegrationTest.kt +│   │   ├── NetworkBreadcrumbsIntegrationTest.kt +│   │   ├── PerformanceAndroidEventProcessorTest.kt +│   │   ├── PermissionsTest.kt +│   │   ├── ScreenshotEventProcessorTest.kt +│   │   ├── SendCachedEnvelopeIntegrationTest.kt +│   │   ├── SentryAndroidDateProviderTest.kt +│   │   ├── SentryAndroidOptionsTest.kt +│   │   ├── SentryAndroidTest.kt +│   │   ├── SentryFrameMetricsTest.kt +│   │   ├── SentryInitProviderTest.kt +│   │   ├── SentryLogcatAdapterTest.kt +│   │   ├── SentryNdk.kt +│   │   ├── SentryPerformanceProviderTest.kt +│   │   ├── SentryShadowProcess.kt +│   │   ├── SessionTrackingIntegrationTest.kt +│   │   ├── SpanFrameMetricsCollectorTest.kt +│   │   ├── SystemEventsBreadcrumbsIntegrationTest.kt +│   │   ├── UserInteractionIntegrationTest.kt +│   │   ├── ViewHierarchyEventProcessorTest.kt +│   │   ├── cache +│   │   │   └── AndroidEnvelopeCacheTest.kt +│   │   ├── internal +│   │   │   ├── debugmeta +│   │   │   │   └── AssetsDebugMetaLoaderTest.kt +│   │   │   ├── gestures +│   │   │   │   ├── SentryGestureListenerClickTest.kt +│   │   │   │   ├── SentryGestureListenerScrollTest.kt +│   │   │   │   ├── SentryGestureListenerTracingTest.kt +│   │   │   │   ├── SentryWindowCallbackTest.kt +│   │   │   │   ├── ViewHelpers.kt +│   │   │   │   └── ViewUtilsTest.kt +│   │   │   ├── modules +│   │   │   │   └── AssetsModulesLoaderTest.kt +│   │   │   ├── threaddump +│   │   │   │   └── ThreadDumpParserTest.kt +│   │   │   └── util +│   │   │   ├── AndroidThreadCheckerTest.kt +│   │   │   ├── ClassUtilTest.kt +│   │   │   ├── ContentProviderSecurityCheckerTest.kt +│   │   │   ├── CpuInfoUtilsTest.kt +│   │   │   ├── DebouncerTest.kt +│   │   │   ├── DeviceOrientationsTest.kt +│   │   │   ├── FirstDrawDoneListenerTest.kt +│   │   │   ├── RootCheckerTest.kt +│   │   │   ├── ScreenshotUtilTest.kt +│   │   │   └── SentryFrameMetricsCollectorTest.kt +│   │   └── performance +│   │   ├── ActivityLifecycleSpanHelperTest.kt +│   │   ├── ActivityLifecycleTimeSpanTest.kt +│   │   ├── AppStartMetricsTest.kt +│   │   └── TimeSpanTest.kt +│   └── resources +│   ├── mockito-extensions +│   │   └── org.mockito.plugins.MockMaker +│   ├── robolectric.properties +│   ├── thread_dump.txt +│   ├── thread_dump_bad_data.txt +│   └── thread_dump_native_only.txt +├── sentry-android-fragment +│   ├── .gitignore +│   ├── api +│   │   └── sentry-android-fragment.api +│   ├── build.gradle.kts +│   ├── proguard-rules.pro +│   └── src +│   ├── main +│   │   ├── java +│   │   │   └── io +│   │   │   └── sentry +│   │   │   └── android +│   │   │   └── fragment +│   │   │   ├── FragmentLifecycleIntegration.kt +│   │   │   ├── FragmentLifecycleState.kt +│   │   │   └── SentryFragmentLifecycleCallbacks.kt +│   │   └── res +│   │   └── values +│   │   └── public.xml +│   └── test +│   └── java +│   └── io +│   └── sentry +│   └── android +│   └── fragment +│   ├── FragmentLifecycleIntegrationTest.kt +│   ├── FragmentLifecycleStateTest.kt +│   └── SentryFragmentLifecycleCallbacksTest.kt +├── sentry-android-integration-tests +│   ├── README.md +│   ├── metrics-test.yml +│   ├── sentry-uitest-android +│   │   ├── .gitignore +│   │   ├── README.md +│   │   ├── build.gradle.kts +│   │   ├── proguard-rules.pro +│   │   └── src +│   │   ├── androidTest +│   │   │   └── java +│   │   │   └── io +│   │   │   └── sentry +│   │   │   └── uitest +│   │   │   └── android +│   │   │   ├── AutomaticSpansTest.kt +│   │   │   ├── BaseUiTest.kt +│   │   │   ├── EnvelopeTests.kt +│   │   │   ├── ReplayTest.kt +│   │   │   ├── SdkInitTests.kt +│   │   │   ├── UserInteractionTests.kt +│   │   │   └── mockservers +│   │   │   ├── EnvelopeAsserter.kt +│   │   │   ├── MockRelay.kt +│   │   │   └── RelayAsserter.kt +│   │   └── main +│   │   ├── AndroidManifest.xml +│   │   ├── java +│   │   │   └── io +│   │   │   └── sentry +│   │   │   └── uitest +│   │   │   └── android +│   │   │   ├── ComposeActivity.kt +│   │   │   ├── EmptyActivity.kt +│   │   │   ├── ProfilingSampleActivity.kt +│   │   │   └── utils +│   │   │   └── BooleanIdlingResource.kt +│   │   └── res +│   │   ├── layout +│   │   │   ├── activity_profiling_sample.xml +│   │   │   └── profiling_sample_item_list.xml +│   │   └── values +│   │   └── values.xml +│   ├── sentry-uitest-android-benchmark +│   │   ├── .gitignore +│   │   ├── benchmark-proguard-rules.pro +│   │   ├── build.gradle.kts +│   │   └── src +│   │   ├── androidTest +│   │   │   └── java +│   │   │   └── io +│   │   │   └── sentry +│   │   │   └── uitest +│   │   │   └── android +│   │   │   └── benchmark +│   │   │   ├── BaseBenchmarkTest.kt +│   │   │   ├── SentryBenchmarkTest.kt +│   │   │   └── util +│   │   │   ├── BenchmarkComparisonResult.kt +│   │   │   ├── BenchmarkOperation.kt +│   │   │   └── BenchmarkOperationComparable.kt +│   │   └── main +│   │   ├── AndroidManifest.xml +│   │   ├── java +│   │   │   └── io +│   │   │   └── sentry +│   │   │   └── uitest +│   │   │   └── android +│   │   │   └── benchmark +│   │   │   ├── BenchmarkActivity.kt +│   │   │   └── BenchmarkTransactionListAdapter.kt +│   │   └── res +│   │   └── layout +│   │   ├── activity_benchmark.xml +│   │   └── benchmark_item_list.xml +│   ├── sentry-uitest-android-critical +│   │   ├── .gitignore +│   │   ├── build.gradle.kts +│   │   ├── maestro +│   │   │   ├── corruptEnvelope.yaml +│   │   │   └── crash.yaml +│   │   ├── proguard-rules.pro +│   │   └── src +│   │   └── main +│   │   ├── AndroidManifest.xml +│   │   └── java +│   │   └── io +│   │   └── sentry +│   │   └── uitest +│   │   └── android +│   │   └── critical +│   │   └── MainActivity.kt +│   ├── test-app-plain +│   │   ├── .gitignore +│   │   ├── build.gradle.kts +│   │   ├── proguard-rules.pro +│   │   └── src +│   │   └── main +│   │   ├── AndroidManifest.xml +│   │   ├── java +│   │   │   └── io +│   │   │   └── sentry +│   │   │   └── java +│   │   │   └── tests +│   │   │   └── perf +│   │   │   └── appplain +│   │   │   ├── FirstFragment.java +│   │   │   ├── MainActivity.java +│   │   │   └── SecondFragment.java +│   │   └── res +│   │   ├── drawable +│   │   │   └── ic_launcher_background.xml +│   │   ├── drawable-v24 +│   │   │   └── ic_launcher_foreground.xml +│   │   ├── layout +│   │   │   ├── activity_main.xml +│   │   │   ├── content_main.xml +│   │   │   ├── fragment_first.xml +│   │   │   └── fragment_second.xml +│   │   ├── menu +│   │   │   └── menu_main.xml +│   │   ├── mipmap-anydpi-v26 +│   │   │   ├── ic_launcher.xml +│   │   │   └── ic_launcher_round.xml +│   │   ├── mipmap-hdpi +│   │   │   ├── ic_launcher.png +│   │   │   └── ic_launcher_round.png +│   │   ├── mipmap-mdpi +│   │   │   ├── ic_launcher.png +│   │   │   └── ic_launcher_round.png +│   │   ├── mipmap-xhdpi +│   │   │   ├── ic_launcher.png +│   │   │   └── ic_launcher_round.png +│   │   ├── mipmap-xxhdpi +│   │   │   ├── ic_launcher.png +│   │   │   └── ic_launcher_round.png +│   │   ├── mipmap-xxxhdpi +│   │   │   ├── ic_launcher.png +│   │   │   └── ic_launcher_round.png +│   │   ├── navigation +│   │   │   └── nav_graph.xml +│   │   ├── values +│   │   │   ├── colors.xml +│   │   │   ├── dimens.xml +│   │   │   ├── strings.xml +│   │   │   └── themes.xml +│   │   ├── values-land +│   │   │   └── dimens.xml +│   │   ├── values-night +│   │   │   └── themes.xml +│   │   ├── values-w1240dp +│   │   │   └── dimens.xml +│   │   ├── values-w600dp +│   │   │   └── dimens.xml +│   │   └── xml +│   │   ├── backup_rules.xml +│   │   └── data_extraction_rules.xml +│   └── test-app-sentry +│   ├── .gitignore +│   ├── build.gradle.kts +│   ├── proguard-rules.pro +│   └── src +│   └── main +│   ├── AndroidManifest.xml +│   ├── java +│   │   └── io +│   │   └── sentry +│   │   └── java +│   │   └── tests +│   │   └── perf +│   │   └── appsentry +│   │   ├── FirstFragment.java +│   │   ├── MainActivity.java +│   │   └── SecondFragment.java +│   └── res +│   ├── drawable +│   │   └── ic_launcher_background.xml +│   ├── drawable-v24 +│   │   └── ic_launcher_foreground.xml +│   ├── layout +│   │   ├── activity_main.xml +│   │   ├── content_main.xml +│   │   ├── fragment_first.xml +│   │   └── fragment_second.xml +│   ├── menu +│   │   └── menu_main.xml +│   ├── mipmap-anydpi-v26 +│   │   ├── ic_launcher.xml +│   │   └── ic_launcher_round.xml +│   ├── mipmap-hdpi +│   │   ├── ic_launcher.png +│   │   └── ic_launcher_round.png +│   ├── mipmap-mdpi +│   │   ├── ic_launcher.png +│   │   └── ic_launcher_round.png +│   ├── mipmap-xhdpi +│   │   ├── ic_launcher.png +│   │   └── ic_launcher_round.png +│   ├── mipmap-xxhdpi +│   │   ├── ic_launcher.png +│   │   └── ic_launcher_round.png +│   ├── mipmap-xxxhdpi +│   │   ├── ic_launcher.png +│   │   └── ic_launcher_round.png +│   ├── navigation +│   │   └── nav_graph.xml +│   ├── values +│   │   ├── colors.xml +│   │   ├── dimens.xml +│   │   ├── strings.xml +│   │   └── themes.xml +│   ├── values-land +│   │   └── dimens.xml +│   ├── values-night +│   │   └── themes.xml +│   ├── values-w1240dp +│   │   └── dimens.xml +│   ├── values-w600dp +│   │   └── dimens.xml +│   └── xml +│   ├── backup_rules.xml +│   └── data_extraction_rules.xml +├── sentry-android-navigation +│   ├── .gitignore +│   ├── api +│   │   └── sentry-android-navigation.api +│   ├── build.gradle.kts +│   ├── proguard-rules.pro +│   └── src +│   ├── main +│   │   ├── java +│   │   │   └── io +│   │   │   └── sentry +│   │   │   └── android +│   │   │   └── navigation +│   │   │   └── SentryNavigationListener.kt +│   │   └── res +│   │   └── values +│   │   └── public.xml +│   └── test +│   └── java +│   └── io +│   └── sentry +│   └── android +│   └── navigation +│   └── SentryNavigationListenerTest.kt +├── sentry-android-ndk +│   ├── api +│   │   └── sentry-android-ndk.api +│   ├── build.gradle.kts +│   ├── proguard-rules.pro +│   └── src +│   ├── main +│   │   ├── java +│   │   │   └── io +│   │   │   └── sentry +│   │   │   └── android +│   │   │   └── ndk +│   │   │   ├── DebugImagesLoader.java +│   │   │   ├── NdkScopeObserver.java +│   │   │   ├── SentryNdk.java +│   │   │   └── SentryNdkUtil.java +│   │   └── res +│   │   └── values +│   │   └── public.xml +│   └── test +│   ├── java +│   │   └── io +│   │   └── sentry +│   │   └── android +│   │   └── ndk +│   │   ├── DebugImagesLoaderTest.kt +│   │   ├── NdkScopeObserverTest.kt +│   │   ├── SentryNdkTest.kt +│   │   └── SentryNdkUtilTest.kt +│   └── resources +│   └── mockito-extensions +│   └── org.mockito.plugins.MockMaker +├── sentry-android-replay +│   ├── .gitignore +│   ├── api +│   │   └── sentry-android-replay.api +│   ├── build.gradle.kts +│   ├── proguard-rules.pro +│   └── src +│   ├── main +│   │   ├── java +│   │   │   └── io +│   │   │   └── sentry +│   │   │   └── android +│   │   │   └── replay +│   │   │   ├── DefaultReplayBreadcrumbConverter.kt +│   │   │   ├── ModifierExtensions.kt +│   │   │   ├── Recorder.kt +│   │   │   ├── ReplayCache.kt +│   │   │   ├── ReplayIntegration.kt +│   │   │   ├── ReplayLifecycle.kt +│   │   │   ├── ScreenshotRecorder.kt +│   │   │   ├── SessionReplayOptions.kt +│   │   │   ├── ViewExtensions.kt +│   │   │   ├── WindowRecorder.kt +│   │   │   ├── Windows.kt +│   │   │   ├── capture +│   │   │   │   ├── BaseCaptureStrategy.kt +│   │   │   │   ├── BufferCaptureStrategy.kt +│   │   │   │   ├── CaptureStrategy.kt +│   │   │   │   └── SessionCaptureStrategy.kt +│   │   │   ├── gestures +│   │   │   │   ├── GestureRecorder.kt +│   │   │   │   └── ReplayGestureConverter.kt +│   │   │   ├── util +│   │   │   │   ├── Context.kt +│   │   │   │   ├── DebugOverlayDrawable.kt +│   │   │   │   ├── Executors.kt +│   │   │   │   ├── FixedWindowCallback.java +│   │   │   │   ├── MainLooperHandler.kt +│   │   │   │   ├── Nodes.kt +│   │   │   │   ├── Persistable.kt +│   │   │   │   ├── Sampling.kt +│   │   │   │   ├── TextLayout.kt +│   │   │   │   └── Views.kt +│   │   │   ├── video +│   │   │   │   ├── SimpleFrameMuxer.kt +│   │   │   │   ├── SimpleMp4FrameMuxer.kt +│   │   │   │   └── SimpleVideoEncoder.kt +│   │   │   └── viewhierarchy +│   │   │   ├── ComposeViewHierarchyNode.kt +│   │   │   └── ViewHierarchyNode.kt +│   │   ├── res +│   │   │   └── values +│   │   │   └── public.xml +│   │   └── resources +│   │   └── META-INF +│   │   └── io +│   │   └── sentry +│   │   └── sentry-android-replay +│   │   └── verification.properties +│   └── test +│   ├── AndroidManifest.xml +│   ├── java +│   │   └── io +│   │   └── sentry +│   │   └── android +│   │   └── replay +│   │   ├── AnrWithReplayIntegrationTest.kt +│   │   ├── DefaultReplayBreadcrumbConverterTest.kt +│   │   ├── ReplayCacheTest.kt +│   │   ├── ReplayIntegrationTest.kt +│   │   ├── ReplayIntegrationWithRecorderTest.kt +│   │   ├── ReplayLifecycleTest.kt +│   │   ├── ReplaySmokeTest.kt +│   │   ├── capture +│   │   │   ├── BufferCaptureStrategyTest.kt +│   │   │   └── SessionCaptureStrategyTest.kt +│   │   ├── gestures +│   │   │   ├── GestureRecorderTest.kt +│   │   │   └── ReplayGestureConverterTest.kt +│   │   ├── util +│   │   │   ├── ReplayShadowMediaCodec.kt +│   │   │   └── TextViewDominantColorTest.kt +│   │   └── viewhierarchy +│   │   ├── ComposeMaskingOptionsTest.kt +│   │   ├── ContainerMaskingOptionsTest.kt +│   │   └── MaskingOptionsTest.kt +│   └── resources +│   └── Tongariro.jpg +├── sentry-android-sqlite +│   ├── api +│   │   └── sentry-android-sqlite.api +│   ├── build.gradle.kts +│   ├── proguard-rules.pro +│   └── src +│   ├── main +│   │   ├── java +│   │   │   └── io +│   │   │   └── sentry +│   │   │   └── android +│   │   │   └── sqlite +│   │   │   ├── SQLiteSpanManager.kt +│   │   │   ├── SentryCrossProcessCursor.kt +│   │   │   ├── SentrySupportSQLiteDatabase.kt +│   │   │   ├── SentrySupportSQLiteOpenHelper.kt +│   │   │   └── SentrySupportSQLiteStatement.kt +│   │   └── res +│   │   └── values +│   │   └── public.xml +│   └── test +│   ├── java +│   │   └── io +│   │   └── sentry +│   │   └── android +│   │   └── sqlite +│   │   ├── SQLiteSpanManagerTest.kt +│   │   ├── SentryCrossProcessCursorTest.kt +│   │   ├── SentrySupportSQLiteDatabaseTest.kt +│   │   ├── SentrySupportSQLiteOpenHelperTest.kt +│   │   └── SentrySupportSQLiteStatementTest.kt +│   └── resources +│   └── mockito-extensions +│   └── org.mockito.plugins.MockMaker +├── sentry-android-timber +│   ├── api +│   │   └── sentry-android-timber.api +│   ├── build.gradle.kts +│   ├── proguard-rules.pro +│   └── src +│   ├── main +│   │   ├── java +│   │   │   └── io +│   │   │   └── sentry +│   │   │   └── android +│   │   │   └── timber +│   │   │   ├── SentryTimberIntegration.kt +│   │   │   └── SentryTimberTree.kt +│   │   └── res +│   │   └── values +│   │   └── public.xml +│   └── test +│   ├── java +│   │   └── io +│   │   └── sentry +│   │   └── android +│   │   └── timber +│   │   ├── SentryTimberIntegrationTest.kt +│   │   └── SentryTimberTreeTest.kt +│   └── resources +│   └── mockito-extensions +│   └── org.mockito.plugins.MockMaker +├── sentry-apache-http-client-5 +│   ├── api +│   │   └── sentry-apache-http-client-5.api +│   ├── build.gradle.kts +│   └── src +│   ├── main +│   │   └── java +│   │   └── io +│   │   └── sentry +│   │   └── transport +│   │   └── apache +│   │   ├── ApacheHttpClientTransport.java +│   │   └── ApacheHttpClientTransportFactory.java +│   └── test +│   ├── kotlin +│   │   └── io +│   │   └── sentry +│   │   ├── SentryOptionsManipulator.kt +│   │   └── transport +│   │   └── apache +│   │   ├── ApacheHttpClientTransportClientReportTest.kt +│   │   ├── ApacheHttpClientTransportFactoryTest.kt +│   │   └── ApacheHttpClientTransportTest.kt +│   └── resources +│   └── mockito-extensions +│   └── org.mockito.plugins.MockMaker +├── sentry-apollo +│   ├── api +│   │   └── sentry-apollo.api +│   ├── build.gradle.kts +│   └── src +│   ├── main +│   │   └── java +│   │   └── io +│   │   └── sentry +│   │   └── apollo +│   │   └── SentryApolloInterceptor.kt +│   └── test +│   ├── java +│   │   └── io +│   │   └── sentry +│   │   ├── apollo +│   │   │   ├── LaunchDetailsQuery.java +│   │   │   ├── SentryApolloInterceptorTest.kt +│   │   │   └── type +│   │   │   └── CustomType.java +│   │   └── util +│   │   └── ApolloPlatformTestManipulator.kt +│   └── resources +│   └── mockito-extensions +│   └── org.mockito.plugins.MockMaker +├── sentry-apollo-3 +│   ├── api +│   │   └── sentry-apollo-3.api +│   ├── build.gradle.kts +│   └── src +│   ├── main +│   │   └── java +│   │   └── io +│   │   └── sentry +│   │   └── apollo3 +│   │   ├── SentryApollo3ClientException.kt +│   │   ├── SentryApollo3HttpInterceptor.kt +│   │   ├── SentryApollo3Interceptor.kt +│   │   └── SentryApolloBuilderExtensions.kt +│   └── test +│   ├── java +│   │   └── io +│   │   └── sentry +│   │   ├── apollo3 +│   │   │   ├── LaunchDetailsQuery.kt +│   │   │   ├── SentryApollo3InterceptorClientErrors.kt +│   │   │   ├── SentryApollo3InterceptorTest.kt +│   │   │   ├── SentryApollo3InterceptorWithVariablesTest.kt +│   │   │   ├── adapter +│   │   │   │   ├── LaunchDetailsQuery_ResponseAdapter.kt +│   │   │   │   └── LaunchDetailsQuery_VariablesAdapter.kt +│   │   │   ├── selections +│   │   │   │   └── LaunchDetailsQuerySelections.kt +│   │   │   └── type +│   │   │   ├── GraphQLBoolean.kt +│   │   │   ├── GraphQLID.kt +│   │   │   ├── GraphQLString.kt +│   │   │   ├── Launch.kt +│   │   │   ├── Mission.kt +│   │   │   ├── Query.kt +│   │   │   └── Rocket.kt +│   │   └── util +│   │   └── Apollo3PlatformTestManipulator.kt +│   └── resources +│   └── mockito-extensions +│   └── org.mockito.plugins.MockMaker +├── sentry-apollo-4 +│   ├── README.md +│   ├── api +│   │   └── sentry-apollo-4.api +│   ├── build.gradle.kts +│   └── src +│   ├── main +│   │   └── java +│   │   └── io +│   │   └── sentry +│   │   └── apollo4 +│   │   ├── SentryApollo4.kt +│   │   ├── SentryApollo4ClientException.kt +│   │   ├── SentryApollo4HttpInterceptor.kt +│   │   ├── SentryApollo4Interceptor.kt +│   │   └── SentryApolloBuilderExtensions.kt +│   └── test +│   ├── java +│   │   └── io +│   │   └── sentry +│   │   ├── apollo4 +│   │   │   ├── SentryApollo4BuilderExtensionsClientErrorsTest.kt +│   │   │   ├── SentryApollo4BuilderExtensionsTest.kt +│   │   │   ├── SentryApollo4HttpInterceptorTest.kt +│   │   │   └── generated +│   │   │   ├── LaunchDetailsQuery.kt +│   │   │   ├── adapter +│   │   │   │   ├── LaunchDetailsQuery_ResponseAdapter.kt +│   │   │   │   └── LaunchDetailsQuery_VariablesAdapter.kt +│   │   │   ├── selections +│   │   │   │   └── LaunchDetailsQuerySelections.kt +│   │   │   └── type +│   │   │   ├── GraphQLBoolean.kt +│   │   │   ├── GraphQLID.kt +│   │   │   ├── GraphQLString.kt +│   │   │   ├── Launch.kt +│   │   │   ├── Mission.kt +│   │   │   ├── Query.kt +│   │   │   └── Rocket.kt +│   │   └── util +│   │   └── Apollo4PlatformTestManipulator.kt +│   └── resources +│   └── mockito-extensions +│   └── org.mockito.plugins.MockMaker +├── sentry-bom +│   └── build.gradle.kts +├── sentry-compose +│   ├── .gitignore +│   ├── README.md +│   ├── api +│   │   ├── android +│   │   │   └── sentry-compose.api +│   │   └── desktop +│   │   └── sentry-compose.api +│   ├── build.gradle.kts +│   ├── gradle.properties +│   ├── proguard-rules.pro +│   └── src +│   ├── androidMain +│   │   ├── kotlin +│   │   │   └── io +│   │   │   └── sentry +│   │   │   └── compose +│   │   │   ├── SentryComposeHelper.kt +│   │   │   ├── SentryComposeTracing.kt +│   │   │   ├── SentryModifier.kt +│   │   │   ├── SentryNavigationIntegration.kt +│   │   │   ├── gestures +│   │   │   │   └── ComposeGestureTargetLocator.kt +│   │   │   └── viewhierarchy +│   │   │   └── ComposeViewHierarchyExporter.kt +│   │   └── res +│   │   └── values +│   │   └── public.xml +│   └── androidUnitTest +│   └── kotlin +│   └── io +│   └── sentry +│   └── compose +│   ├── ComposeIntegrationTests.kt +│   ├── SentryLifecycleObserverTest.kt +│   ├── SentryModifierComposeTest.kt +│   └── viewhierarchy +│   └── ComposeViewHierarchyExporterTest.kt +├── sentry-compose-helper +├── sentry-graphql +│   ├── api +│   │   └── sentry-graphql.api +│   ├── build.gradle.kts +│   └── src +│   ├── main +│   │   └── java +│   │   └── io +│   │   └── sentry +│   │   └── graphql +│   │   └── SentryInstrumentation.java +│   └── test +│   └── kotlin +│   └── io +│   └── sentry +│   └── graphql +│   ├── SentryInstrumentationAnotherTest.kt +│   └── SentryInstrumentationTest.kt +├── sentry-graphql-22 +│   ├── api +│   │   └── sentry-graphql-22.api +│   ├── build.gradle.kts +│   └── src +│   ├── main +│   │   └── java +│   │   └── io +│   │   └── sentry +│   │   └── graphql22 +│   │   └── SentryInstrumentation.java +│   └── test +│   └── kotlin +│   └── io +│   └── sentry +│   └── graphql22 +│   ├── SentryInstrumentationAnotherTest.kt +│   └── SentryInstrumentationTest.kt +├── sentry-graphql-core +│   ├── api +│   │   └── sentry-graphql-core.api +│   ├── build.gradle.kts +│   └── src +│   ├── main +│   │   └── java +│   │   └── io +│   │   └── sentry +│   │   └── graphql +│   │   ├── ExceptionReporter.java +│   │   ├── GraphqlStringUtils.java +│   │   ├── NoOpSubscriptionHandler.java +│   │   ├── SentryGenericDataFetcherExceptionHandler.java +│   │   ├── SentryGraphqlExceptionHandler.java +│   │   ├── SentryGraphqlInstrumentation.java +│   │   └── SentrySubscriptionHandler.java +│   └── test +│   └── kotlin +│   └── io +│   └── sentry +│   └── graphql +│   ├── ExceptionReporterTest.kt +│   ├── GraphqlStringUtilsTest.kt +│   └── SentryGenericDataFetcherExceptionHandlerTest.kt +├── sentry-jdbc +│   ├── api +│   │   └── sentry-jdbc.api +│   ├── build.gradle.kts +│   └── src +│   ├── main +│   │   ├── java +│   │   │   └── io +│   │   │   └── sentry +│   │   │   └── jdbc +│   │   │   ├── DatabaseUtils.java +│   │   │   └── SentryJdbcEventListener.java +│   │   └── resources +│   │   └── META-INF +│   │   └── services +│   │   └── com.p6spy.engine.event.JdbcEventListener +│   └── test +│   └── kotlin +│   └── io +│   └── sentry +│   └── jdbc +│   ├── DatabaseUtilsTest.kt +│   └── SentryJdbcEventListenerTest.kt +├── sentry-jul +│   ├── api +│   │   └── sentry-jul.api +│   ├── build.gradle.kts +│   └── src +│   ├── main +│   │   └── java +│   │   └── io +│   │   └── sentry +│   │   └── jul +│   │   └── SentryHandler.java +│   └── test +│   ├── kotlin +│   │   └── io +│   │   └── sentry +│   │   └── jul +│   │   └── SentryHandlerTest.kt +│   └── resources +│   ├── logging.properties +│   ├── mockito-extensions +│   │   └── org.mockito.plugins.MockMaker +│   └── sentry.properties +├── sentry-kotlin-extensions +│   ├── api +│   │   └── sentry-kotlin-extensions.api +│   ├── build.gradle.kts +│   └── src +│   ├── main +│   │   └── java +│   │   └── io +│   │   └── sentry +│   │   └── kotlin +│   │   ├── SentryContext.kt +│   │   └── SentryCoroutineExceptionHandler.kt +│   └── test +│   └── java +│   └── io +│   └── sentry +│   └── kotlin +│   ├── SentryContextTest.kt +│   └── SentryCoroutineExceptionHandlerTest.kt +├── sentry-log4j2 +│   ├── api +│   │   └── sentry-log4j2.api +│   ├── build.gradle.kts +│   └── src +│   ├── main +│   │   └── java +│   │   └── io +│   │   └── sentry +│   │   └── log4j2 +│   │   └── SentryAppender.java +│   └── test +│   ├── kotlin +│   │   └── io +│   │   └── sentry +│   │   └── log4j2 +│   │   └── SentryAppenderTest.kt +│   └── resources +│   ├── mockito-extensions +│   │   └── org.mockito.plugins.MockMaker +│   └── sentry.properties +├── sentry-logback +│   ├── api +│   │   └── sentry-logback.api +│   ├── build.gradle.kts +│   └── src +│   ├── main +│   │   └── java +│   │   └── io +│   │   └── sentry +│   │   └── logback +│   │   └── SentryAppender.java +│   └── test +│   ├── kotlin +│   │   └── io +│   │   └── sentry +│   │   └── logback +│   │   └── SentryAppenderTest.kt +│   └── resources +│   ├── mockito-extensions +│   │   └── org.mockito.plugins.MockMaker +│   └── sentry.properties +├── sentry-okhttp +│   ├── api +│   │   └── sentry-okhttp.api +│   ├── build.gradle.kts +│   └── src +│   ├── main +│   │   ├── java +│   │   │   └── io +│   │   │   └── sentry +│   │   │   └── okhttp +│   │   │   ├── SentryOkHttpEvent.kt +│   │   │   ├── SentryOkHttpEventListener.kt +│   │   │   ├── SentryOkHttpInterceptor.kt +│   │   │   └── SentryOkHttpUtils.kt +│   │   └── resources +│   │   └── META-INF +│   │   └── proguard +│   │   └── sentry-okhttp.pro +│   └── test +│   ├── java +│   │   └── io +│   │   └── sentry +│   │   └── okhttp +│   │   ├── SentryOkHttpEventListenerTest.kt +│   │   ├── SentryOkHttpEventTest.kt +│   │   ├── SentryOkHttpInterceptorTest.kt +│   │   └── SentryOkHttpUtilsTest.kt +│   └── resources +│   └── mockito-extensions +│   └── org.mockito.plugin.MockMaker +├── sentry-openfeign +│   ├── api +│   │   └── sentry-openfeign.api +│   ├── build.gradle.kts +│   └── src +│   ├── main +│   │   └── java +│   │   └── io +│   │   └── sentry +│   │   └── openfeign +│   │   ├── SentryCapability.java +│   │   └── SentryFeignClient.java +│   └── test +│   ├── kotlin +│   │   └── io +│   │   └── sentry +│   │   └── openfeign +│   │   └── SentryFeignClientTest.kt +│   └── resources +│   └── mockito-extensions +│   └── org.mockito.plugins.MockMaker +├── sentry-opentelemetry +│   ├── README.md +│   ├── sentry-opentelemetry-agent +│   │   ├── README.md +│   │   ├── build.gradle.kts +│   │   └── src +│   │   └── main +│   │   └── java +│   │   └── io +│   │   └── sentry +│   │   └── opentelemetry +│   │   └── agent +│   │   └── AgentMarker.java +│   ├── sentry-opentelemetry-agentcustomization +│   │   ├── api +│   │   │   └── sentry-opentelemetry-agentcustomization.api +│   │   ├── build.gradle.kts +│   │   └── src +│   │   └── main +│   │   ├── java +│   │   │   └── io +│   │   │   └── sentry +│   │   │   └── opentelemetry +│   │   │   ├── SentryAutoConfigurationCustomizerProvider.java +│   │   │   ├── SentryBootstrapPackagesProvider.java +│   │   │   └── SentryPropagatorProvider.java +│   │   └── resources +│   │   └── META-INF +│   │   └── services +│   │   ├── io.opentelemetry.javaagent.tooling.bootstrap.BootstrapPackagesConfigurer +│   │   ├── io.opentelemetry.sdk.autoconfigure.spi.AutoConfigurationCustomizerProvider +│   │   └── io.opentelemetry.sdk.autoconfigure.spi.ConfigurablePropagatorProvider +│   ├── sentry-opentelemetry-agentless +│   │   ├── README.md +│   │   ├── build.gradle.kts +│   │   └── src +│   │   └── main +│   │   └── java +│   │   └── io +│   │   └── sentry +│   │   └── opentelemetry +│   │   └── agent +│   │   └── AgentlessMarker.java +│   ├── sentry-opentelemetry-agentless-spring +│   │   ├── README.md +│   │   ├── build.gradle.kts +│   │   └── src +│   │   └── main +│   │   └── java +│   │   └── io +│   │   └── sentry +│   │   └── opentelemetry +│   │   └── agent +│   │   └── AgentlessSpringMarker.java +│   ├── sentry-opentelemetry-bootstrap +│   │   ├── api +│   │   │   └── sentry-opentelemetry-bootstrap.api +│   │   ├── build.gradle.kts +│   │   └── src +│   │   └── main +│   │   ├── java +│   │   │   └── io +│   │   │   └── sentry +│   │   │   └── opentelemetry +│   │   │   ├── IOtelSpanWrapper.java +│   │   │   ├── InternalSemanticAttributes.java +│   │   │   ├── OtelContextScopesStorage.java +│   │   │   ├── OtelSpanFactory.java +│   │   │   ├── OtelStorageToken.java +│   │   │   ├── OtelStrongRefSpanWrapper.java +│   │   │   ├── OtelTransactionSpanForwarder.java +│   │   │   ├── SentryContextStorage.java +│   │   │   ├── SentryContextStorageProvider.java +│   │   │   ├── SentryContextWrapper.java +│   │   │   ├── SentryOtelKeys.java +│   │   │   ├── SentryOtelThreadLocalStorage.java +│   │   │   └── SentryWeakSpanStorage.java +│   │   └── resources +│   │   └── META-INF +│   │   └── services +│   │   └── io.opentelemetry.context.ContextStorageProvider +│   └── sentry-opentelemetry-core +│   ├── api +│   │   └── sentry-opentelemetry-core.api +│   ├── build.gradle.kts +│   └── src +│   ├── main +│   │   └── java +│   │   └── io +│   │   └── sentry +│   │   └── opentelemetry +│   │   ├── OpenTelemetryAttributesExtractor.java +│   │   ├── OpenTelemetryLinkErrorEventProcessor.java +│   │   ├── OtelInternalSpanDetectionUtil.java +│   │   ├── OtelSamplingUtil.java +│   │   ├── OtelSentryPropagator.java +│   │   ├── OtelSentrySpanProcessor.java +│   │   ├── OtelSpanContext.java +│   │   ├── OtelSpanInfo.java +│   │   ├── OtelSpanWrapper.java +│   │   ├── SentryPropagator.java +│   │   ├── SentrySampler.java +│   │   ├── SentrySamplingResult.java +│   │   ├── SentrySpanExporter.java +│   │   ├── SentrySpanProcessor.java +│   │   ├── SpanDescriptionExtractor.java +│   │   ├── SpanNode.java +│   │   └── TraceData.java +│   └── test +│   └── kotlin +│   ├── OpenTelemetryAttributesExtractorTest.kt +│   ├── OtelInternalSpanDetectionUtilTest.kt +│   ├── OtelSentryPropagatorTest.kt +│   ├── SentrySpanProcessorTest.kt +│   └── SpanDescriptionExtractorTest.kt +├── sentry-quartz +│   ├── api +│   │   └── sentry-quartz.api +│   ├── build.gradle.kts +│   └── src +│   └── main +│   └── java +│   └── io +│   └── sentry +│   └── quartz +│   └── SentryJobListener.java +├── sentry-reactor +│   ├── README.md +│   ├── api +│   │   └── sentry-reactor.api +│   ├── build.gradle.kts +│   └── src +│   ├── main +│   │   ├── java +│   │   │   └── io +│   │   │   └── sentry +│   │   │   └── reactor +│   │   │   ├── SentryReactorThreadLocalAccessor.java +│   │   │   └── SentryReactorUtils.java +│   │   └── resources +│   │   └── META-INF +│   │   └── services +│   │   └── io.micrometer.context.ThreadLocalAccessor +│   └── test +│   └── kotlin +│   └── io +│   └── sentry +│   └── reactor +│   └── SentryReactorUtilsTest.kt +├── sentry-samples +│   ├── sentry-samples-android +│   │   ├── .cxx +│   │   │   ├── Debug +│   │   │   │   └── 3u6s1g3o +│   │   │   │   ├── arm64-v8a +│   │   │   │   │   ├── .cmake +│   │   │   │   │   │   └── api +│   │   │   │   │   │   └── v1 +│   │   │   │   │   │   ├── query +│   │   │   │   │   │   │   └── client-agp +│   │   │   │   │   │   │   ├── cache-v2 +│   │   │   │   │   │   │   ├── cmakeFiles-v1 +│   │   │   │   │   │   │   └── codemodel-v2 +│   │   │   │   │   │   └── reply +│   │   │   │   │   │   ├── cache-v2-ece011d78c9b9ef6c4b7.json +│   │   │   │   │   │   ├── cmakeFiles-v1-213036d95f83728f40a0.json +│   │   │   │   │   │   ├── codemodel-v2-1a805ddcfedad5226927.json +│   │   │   │   │   │   ├── directory-.-Debug-f5ebdc15457944623624.json +│   │   │   │   │   │   ├── index-2025-02-18T10-57-50-0477.json +│   │   │   │   │   │   └── target-native-sample-Debug-be2475b9900f92f0eea9.json +│   │   │   │   │   ├── .ninja_deps +│   │   │   │   │   ├── .ninja_log +│   │   │   │   │   ├── CMakeCache.txt +│   │   │   │   │   ├── CMakeFiles +│   │   │   │   │   │   ├── 3.22.1-g37088a8 +│   │   │   │   │   │   │   ├── CMakeCCompiler.cmake +│   │   │   │   │   │   │   ├── CMakeCXXCompiler.cmake +│   │   │   │   │   │   │   ├── CMakeDetermineCompilerABI_C.bin +│   │   │   │   │   │   │   ├── CMakeDetermineCompilerABI_CXX.bin +│   │   │   │   │   │   │   ├── CMakeSystem.cmake +│   │   │   │   │   │   │   ├── CompilerIdC +│   │   │   │   │   │   │   │   ├── CMakeCCompilerId.c +│   │   │   │   │   │   │   │   ├── CMakeCCompilerId.o +│   │   │   │   │   │   │   │   └── tmp +│   │   │   │   │   │   │   └── CompilerIdCXX +│   │   │   │   │   │   │   ├── CMakeCXXCompilerId.cpp +│   │   │   │   │   │   │   ├── CMakeCXXCompilerId.o +│   │   │   │   │   │   │   └── tmp +│   │   │   │   │   │   ├── CMakeOutput.log +│   │   │   │   │   │   ├── CMakeTmp +│   │   │   │   │   │   ├── TargetDirectories.txt +│   │   │   │   │   │   ├── cmake.check_cache +│   │   │   │   │   │   ├── native-sample.dir +│   │   │   │   │   │   │   └── src +│   │   │   │   │   │   │   └── main +│   │   │   │   │   │   │   └── cpp +│   │   │   │   │   │   │   └── native-sample.cpp.o +│   │   │   │   │   │   └── rules.ninja +│   │   │   │   │   ├── additional_project_files.txt +│   │   │   │   │   ├── android_gradle_build.json +│   │   │   │   │   ├── android_gradle_build_mini.json +│   │   │   │   │   ├── build.ninja +│   │   │   │   │   ├── build_file_index.txt +│   │   │   │   │   ├── cmake_install.cmake +│   │   │   │   │   ├── compile_commands.json +│   │   │   │   │   ├── compile_commands.json.bin +│   │   │   │   │   ├── configure_fingerprint.bin +│   │   │   │   │   ├── metadata_generation_command.txt +│   │   │   │   │   ├── prefab_config.json +│   │   │   │   │   └── symbol_folder_index.txt +│   │   │   │   ├── hash_key.txt +│   │   │   │   ├── prefab +│   │   │   │   │   ├── arm64-v8a +│   │   │   │   │   │   └── prefab +│   │   │   │   │   │   └── lib +│   │   │   │   │   │   └── aarch64-linux-android +│   │   │   │   │   │   └── cmake +│   │   │   │   │   │   └── sentry-native-ndk +│   │   │   │   │   │   ├── sentry-native-ndkConfig.cmake +│   │   │   │   │   │   └── sentry-native-ndkConfigVersion.cmake +│   │   │   │   │   └── x86 +│   │   │   │   │   └── prefab +│   │   │   │   │   └── lib +│   │   │   │   │   └── i686-linux-android +│   │   │   │   │   └── cmake +│   │   │   │   │   └── sentry-native-ndk +│   │   │   │   │   ├── sentry-native-ndkConfig.cmake +│   │   │   │   │   └── sentry-native-ndkConfigVersion.cmake +│   │   │   │   └── x86 +│   │   │   │   ├── .cmake +│   │   │   │   │   └── api +│   │   │   │   │   └── v1 +│   │   │   │   │   ├── query +│   │   │   │   │   │   └── client-agp +│   │   │   │   │   │   ├── cache-v2 +│   │   │   │   │   │   ├── cmakeFiles-v1 +│   │   │   │   │   │   └── codemodel-v2 +│   │   │   │   │   └── reply +│   │   │   │   │   ├── cache-v2-f7db95325bc434cc697a.json +│   │   │   │   │   ├── cmakeFiles-v1-d6783107011ca9e52149.json +│   │   │   │   │   ├── codemodel-v2-fd2b991cf02633ea977f.json +│   │   │   │   │   ├── directory-.-Debug-f5ebdc15457944623624.json +│   │   │   │   │   ├── index-2025-05-27T12-17-10-0384.json +│   │   │   │   │   └── target-native-sample-Debug-97067de7b903026bf6ad.json +│   │   │   │   ├── CMakeCache.txt +│   │   │   │   ├── CMakeFiles +│   │   │   │   │   ├── 3.22.1-g37088a8 +│   │   │   │   │   │   ├── CMakeCCompiler.cmake +│   │   │   │   │   │   ├── CMakeCXXCompiler.cmake +│   │   │   │   │   │   ├── CMakeDetermineCompilerABI_C.bin +│   │   │   │   │   │   ├── CMakeDetermineCompilerABI_CXX.bin +│   │   │   │   │   │   ├── CMakeSystem.cmake +│   │   │   │   │   │   ├── CompilerIdC +│   │   │   │   │   │   │   ├── CMakeCCompilerId.c +│   │   │   │   │   │   │   ├── CMakeCCompilerId.o +│   │   │   │   │   │   │   └── tmp +│   │   │   │   │   │   └── CompilerIdCXX +│   │   │   │   │   │   ├── CMakeCXXCompilerId.cpp +│   │   │   │   │   │   ├── CMakeCXXCompilerId.o +│   │   │   │   │   │   └── tmp +│   │   │   │   │   ├── CMakeOutput.log +│   │   │   │   │   ├── CMakeTmp +│   │   │   │   │   ├── TargetDirectories.txt +│   │   │   │   │   ├── cmake.check_cache +│   │   │   │   │   ├── native-sample.dir +│   │   │   │   │   │   └── src +│   │   │   │   │   │   └── main +│   │   │   │   │   │   └── cpp +│   │   │   │   │   └── rules.ninja +│   │   │   │   ├── additional_project_files.txt +│   │   │   │   ├── android_gradle_build.json +│   │   │   │   ├── android_gradle_build_mini.json +│   │   │   │   ├── build.ninja +│   │   │   │   ├── build_file_index.txt +│   │   │   │   ├── cmake_install.cmake +│   │   │   │   ├── compile_commands.json +│   │   │   │   ├── compile_commands.json.bin +│   │   │   │   ├── configure_fingerprint.bin +│   │   │   │   ├── metadata_generation_command.txt +│   │   │   │   ├── prefab_config.json +│   │   │   │   └── symbol_folder_index.txt +│   │   │   └── tools +│   │   │   └── debug +│   │   │   ├── arm64-v8a +│   │   │   │   └── compile_commands.json +│   │   │   └── x86 +│   │   │   └── compile_commands.json +│   │   ├── .gitignore +│   │   ├── CMakeLists.txt +│   │   ├── build.gradle.kts +│   │   ├── proguard-rules.pro +│   │   └── src +│   │   └── main +│   │   ├── AndroidManifest.xml +│   │   ├── cpp +│   │   │   └── native-sample.cpp +│   │   ├── java +│   │   │   └── io +│   │   │   └── sentry +│   │   │   └── samples +│   │   │   └── android +│   │   │   ├── CoroutinesUtil.kt +│   │   │   ├── FrameDataForSpansActivity.kt +│   │   │   ├── GesturesActivity.kt +│   │   │   ├── GitHubService.kt +│   │   │   ├── GithubAPI.kt +│   │   │   ├── MainActivity.java +│   │   │   ├── MyApplication.java +│   │   │   ├── NativeSample.java +│   │   │   ├── PermissionsActivity.kt +│   │   │   ├── ProfilingActivity.kt +│   │   │   ├── ProfilingListAdapter.kt +│   │   │   ├── SampleFragment.kt +│   │   │   ├── SampleInnerFragment.kt +│   │   │   ├── SecondActivity.kt +│   │   │   ├── ThirdActivityFragment.kt +│   │   │   ├── ThirdFragment.kt +│   │   │   └── compose +│   │   │   └── ComposeActivity.kt +│   │   └── res +│   │   ├── drawable +│   │   │   ├── ic_launcher_background.xml +│   │   │   └── sentry_glyph.xml +│   │   ├── drawable-v24 +│   │   │   └── ic_launcher_foreground.xml +│   │   ├── layout +│   │   │   ├── activity_gestures.xml +│   │   │   ├── activity_main.xml +│   │   │   ├── activity_permissions.xml +│   │   │   ├── activity_profiling.xml +│   │   │   ├── activity_second.xml +│   │   │   ├── activity_third_fragment.xml +│   │   │   ├── fragment_recycler.xml +│   │   │   ├── fragment_sample.xml +│   │   │   ├── fragment_sample_inner.xml +│   │   │   ├── fragment_scrolling.xml +│   │   │   ├── profiling_item_list.xml +│   │   │   └── third_fragment.xml +│   │   ├── mipmap-anydpi-v26 +│   │   │   ├── ic_launcher.xml +│   │   │   └── ic_launcher_round.xml +│   │   ├── mipmap-hdpi +│   │   │   ├── ic_launcher.png +│   │   │   └── ic_launcher_round.png +│   │   ├── mipmap-mdpi +│   │   │   ├── ic_launcher.png +│   │   │   └── ic_launcher_round.png +│   │   ├── mipmap-xhdpi +│   │   │   ├── ic_launcher.png +│   │   │   └── ic_launcher_round.png +│   │   ├── mipmap-xxhdpi +│   │   │   ├── ic_launcher.png +│   │   │   └── ic_launcher_round.png +│   │   ├── mipmap-xxxhdpi +│   │   │   ├── ic_launcher.png +│   │   │   └── ic_launcher_round.png +│   │   ├── raw +│   │   │   └── sentry.png +│   │   ├── values +│   │   │   ├── colors.xml +│   │   │   ├── strings.xml +│   │   │   └── styles.xml +│   │   └── xml +│   │   └── network.xml +│   ├── sentry-samples-console +│   │   ├── README.md +│   │   ├── build.gradle.kts +│   │   └── src +│   │   └── main +│   │   └── java +│   │   └── io +│   │   └── sentry +│   │   └── samples +│   │   └── console +│   │   └── Main.java +│   ├── sentry-samples-console-opentelemetry-noagent +│   │   ├── README.md +│   │   ├── build.gradle.kts +│   │   └── src +│   │   └── main +│   │   └── java +│   │   └── io +│   │   └── sentry +│   │   └── samples +│   │   └── console +│   │   └── Main.java +│   ├── sentry-samples-jul +│   │   ├── README.md +│   │   ├── build.gradle.kts +│   │   └── src +│   │   └── main +│   │   ├── java +│   │   │   └── io +│   │   │   └── sentry +│   │   │   └── samples +│   │   │   └── jul +│   │   │   └── Main.java +│   │   └── resources +│   │   ├── logging.properties +│   │   └── sentry.properties +│   ├── sentry-samples-log4j2 +│   │   ├── README.md +│   │   ├── build.gradle.kts +│   │   └── src +│   │   └── main +│   │   ├── java +│   │   │   └── io +│   │   │   └── sentry +│   │   │   └── samples +│   │   │   └── log4j2 +│   │   │   └── Main.java +│   │   └── resources +│   │   ├── log4j2.xml +│   │   └── sentry.properties +│   ├── sentry-samples-logback +│   │   ├── README.md +│   │   ├── build.gradle.kts +│   │   └── src +│   │   └── main +│   │   ├── java +│   │   │   └── io +│   │   │   └── sentry +│   │   │   └── samples +│   │   │   └── logback +│   │   │   └── Main.java +│   │   └── resources +│   │   ├── logback.xml +│   │   └── sentry.properties +│   ├── sentry-samples-netflix-dgs +│   │   ├── README.md +│   │   ├── build.gradle.kts +│   │   └── src +│   │   └── main +│   │   ├── java +│   │   │   └── io +│   │   │   └── sentry +│   │   │   └── samples +│   │   │   └── netflix +│   │   │   └── dgs +│   │   │   ├── ActorsDataloader.java +│   │   │   ├── NetlixDgsApplication.java +│   │   │   ├── ShowsDatafetcher.java +│   │   │   └── graphql +│   │   │   ├── DgsConstants.java +│   │   │   └── types +│   │   │   ├── Actor.java +│   │   │   └── Show.java +│   │   └── resources +│   │   ├── application.properties +│   │   └── schema +│   │   └── schema.graphqls +│   ├── sentry-samples-openfeign +│   │   ├── README.md +│   │   ├── build.gradle.kts +│   │   └── src +│   │   └── main +│   │   └── java +│   │   └── io +│   │   └── sentry +│   │   └── samples +│   │   └── openfeign +│   │   └── Main.java +│   ├── sentry-samples-servlet +│   │   ├── README.md +│   │   ├── build.gradle.kts +│   │   └── src +│   │   └── main +│   │   ├── java +│   │   │   └── io +│   │   │   └── sentry +│   │   │   └── samples +│   │   │   └── servlet +│   │   │   ├── SampleServlet.java +│   │   │   └── SentryInitializer.java +│   │   └── webapp +│   │   └── WEB-INF +│   │   └── web.xml +│   ├── sentry-samples-spring +│   │   ├── README.md +│   │   ├── build.gradle.kts +│   │   └── src +│   │   └── main +│   │   ├── java +│   │   │   └── io +│   │   │   └── sentry +│   │   │   └── samples +│   │   │   └── spring +│   │   │   ├── AppConfig.java +│   │   │   ├── AppInitializer.java +│   │   │   ├── SecurityConfiguration.java +│   │   │   ├── SentryConfig.java +│   │   │   ├── WebConfig.java +│   │   │   └── web +│   │   │   ├── Person.java +│   │   │   ├── PersonController.java +│   │   │   └── PersonService.java +│   │   └── resources +│   │   ├── logback.xml +│   │   └── sentry.properties +│   ├── sentry-samples-spring-boot +│   │   ├── README.md +│   │   ├── build.gradle.kts +│   │   └── src +│   │   ├── main +│   │   │   ├── java +│   │   │   │   └── io +│   │   │   │   └── sentry +│   │   │   │   └── samples +│   │   │   │   └── spring +│   │   │   │   └── boot +│   │   │   │   ├── CustomJob.java +│   │   │   │   ├── DistributedTracingController.java +│   │   │   │   ├── Person.java +│   │   │   │   ├── PersonController.java +│   │   │   │   ├── PersonService.java +│   │   │   │   ├── SecurityConfiguration.java +│   │   │   │   ├── SentryDemoApplication.java +│   │   │   │   ├── Todo.java +│   │   │   │   ├── TodoController.java +│   │   │   │   ├── graphql +│   │   │   │   │   ├── AssigneeController.java +│   │   │   │   │   ├── GreetingController.java +│   │   │   │   │   ├── ProjectController.java +│   │   │   │   │   └── TaskCreatorController.java +│   │   │   │   └── quartz +│   │   │   │   └── SampleJob.java +│   │   │   └── resources +│   │   │   ├── application.properties +│   │   │   ├── graphql +│   │   │   │   └── schema.graphqls +│   │   │   └── schema.sql +│   │   └── test +│   │   ├── kotlin +│   │   │   └── io +│   │   │   └── sentry +│   │   │   ├── DummyTest.kt +│   │   │   └── systemtest +│   │   │   ├── DistributedTracingSystemTest.kt +│   │   │   ├── GraphqlGreetingSystemTest.kt +│   │   │   ├── GraphqlProjectSystemTest.kt +│   │   │   ├── GraphqlTaskSystemTest.kt +│   │   │   ├── PersonSystemTest.kt +│   │   │   └── TodoSystemTest.kt +│   │   └── resources +│   │   └── logback.xml +│   ├── sentry-samples-spring-boot-jakarta +│   │   ├── README.md +│   │   ├── build.gradle.kts +│   │   ├── sentry-jakarta-text-master.properties +│   │   ├── sentry-package-rename.properties +│   │   └── src +│   │   ├── main +│   │   │   ├── java +│   │   │   │   └── io +│   │   │   │   └── sentry +│   │   │   │   └── samples +│   │   │   │   └── spring +│   │   │   │   └── boot +│   │   │   │   └── jakarta +│   │   │   │   ├── CustomEventProcessor.java +│   │   │   │   ├── CustomJob.java +│   │   │   │   ├── DistributedTracingController.java +│   │   │   │   ├── Person.java +│   │   │   │   ├── PersonController.java +│   │   │   │   ├── PersonService.java +│   │   │   │   ├── SecurityConfiguration.java +│   │   │   │   ├── SentryDemoApplication.java +│   │   │   │   ├── Todo.java +│   │   │   │   ├── TodoController.java +│   │   │   │   ├── graphql +│   │   │   │   │   ├── AssigneeController.java +│   │   │   │   │   ├── GreetingController.java +│   │   │   │   │   ├── ProjectController.java +│   │   │   │   │   └── TaskCreatorController.java +│   │   │   │   └── quartz +│   │   │   │   └── SampleJob.java +│   │   │   └── resources +│   │   │   ├── application.properties +│   │   │   ├── graphql +│   │   │   │   └── schema.graphqls +│   │   │   ├── quartz.properties +│   │   │   └── schema.sql +│   │   └── test +│   │   ├── kotlin +│   │   │   └── io +│   │   │   └── sentry +│   │   │   ├── DummyTest.kt +│   │   │   └── systemtest +│   │   │   ├── DistributedTracingSystemTest.kt +│   │   │   ├── GraphqlGreetingSystemTest.kt +│   │   │   ├── GraphqlProjectSystemTest.kt +│   │   │   ├── GraphqlTaskSystemTest.kt +│   │   │   ├── PersonSystemTest.kt +│   │   │   └── TodoSystemTest.kt +│   │   └── resources +│   │   └── logback.xml +│   ├── sentry-samples-spring-boot-jakarta-opentelemetry +│   │   ├── README.md +│   │   ├── build.gradle.kts +│   │   └── src +│   │   ├── main +│   │   │   ├── java +│   │   │   │   └── io +│   │   │   │   └── sentry +│   │   │   │   └── samples +│   │   │   │   └── spring +│   │   │   │   └── boot +│   │   │   │   └── jakarta +│   │   │   │   ├── CustomEventProcessor.java +│   │   │   │   ├── CustomJob.java +│   │   │   │   ├── DistributedTracingController.java +│   │   │   │   ├── Person.java +│   │   │   │   ├── PersonController.java +│   │   │   │   ├── PersonService.java +│   │   │   │   ├── SecurityConfiguration.java +│   │   │   │   ├── SentryDemoApplication.java +│   │   │   │   ├── Todo.java +│   │   │   │   ├── TodoController.java +│   │   │   │   ├── graphql +│   │   │   │   │   ├── AssigneeController.java +│   │   │   │   │   ├── GreetingController.java +│   │   │   │   │   ├── ProjectController.java +│   │   │   │   │   └── TaskCreatorController.java +│   │   │   │   └── quartz +│   │   │   │   └── SampleJob.java +│   │   │   └── resources +│   │   │   ├── application.properties +│   │   │   ├── graphql +│   │   │   │   └── schema.graphqls +│   │   │   ├── quartz.properties +│   │   │   └── schema.sql +│   │   └── test +│   │   ├── kotlin +│   │   │   └── io +│   │   │   └── sentry +│   │   │   ├── DummyTest.kt +│   │   │   └── systemtest +│   │   │   ├── DistributedTracingSystemTest.kt +│   │   │   ├── GraphqlGreetingSystemTest.kt +│   │   │   ├── GraphqlProjectSystemTest.kt +│   │   │   ├── GraphqlTaskSystemTest.kt +│   │   │   ├── PersonSystemTest.kt +│   │   │   └── TodoSystemTest.kt +│   │   └── resources +│   │   └── logback.xml +│   ├── sentry-samples-spring-boot-jakarta-opentelemetry-noagent +│   │   ├── README.md +│   │   ├── build.gradle.kts +│   │   └── src +│   │   ├── main +│   │   │   ├── java +│   │   │   │   └── io +│   │   │   │   └── sentry +│   │   │   │   └── samples +│   │   │   │   └── spring +│   │   │   │   └── boot +│   │   │   │   └── jakarta +│   │   │   │   ├── CustomEventProcessor.java +│   │   │   │   ├── CustomJob.java +│   │   │   │   ├── DistributedTracingController.java +│   │   │   │   ├── Person.java +│   │   │   │   ├── PersonController.java +│   │   │   │   ├── PersonService.java +│   │   │   │   ├── SecurityConfiguration.java +│   │   │   │   ├── SentryDemoApplication.java +│   │   │   │   ├── Todo.java +│   │   │   │   ├── TodoController.java +│   │   │   │   ├── graphql +│   │   │   │   │   ├── AssigneeController.java +│   │   │   │   │   ├── GreetingController.java +│   │   │   │   │   ├── ProjectController.java +│   │   │   │   │   └── TaskCreatorController.java +│   │   │   │   └── quartz +│   │   │   │   └── SampleJob.java +│   │   │   └── resources +│   │   │   ├── application.properties +│   │   │   ├── graphql +│   │   │   │   └── schema.graphqls +│   │   │   ├── quartz.properties +│   │   │   └── schema.sql +│   │   └── test +│   │   ├── kotlin +│   │   │   └── io +│   │   │   └── sentry +│   │   │   ├── DummyTest.kt +│   │   │   └── systemtest +│   │   │   ├── DistributedTracingSystemTest.kt +│   │   │   ├── GraphqlGreetingSystemTest.kt +│   │   │   ├── GraphqlProjectSystemTest.kt +│   │   │   ├── GraphqlTaskSystemTest.kt +│   │   │   ├── PersonSystemTest.kt +│   │   │   └── TodoSystemTest.kt +│   │   └── resources +│   │   └── logback.xml +│   ├── sentry-samples-spring-boot-opentelemetry +│   │   ├── README.md +│   │   ├── build.gradle.kts +│   │   └── src +│   │   ├── main +│   │   │   ├── java +│   │   │   │   └── io +│   │   │   │   └── sentry +│   │   │   │   └── samples +│   │   │   │   └── spring +│   │   │   │   └── boot +│   │   │   │   ├── CustomJob.java +│   │   │   │   ├── DistributedTracingController.java +│   │   │   │   ├── Person.java +│   │   │   │   ├── PersonController.java +│   │   │   │   ├── PersonService.java +│   │   │   │   ├── SecurityConfiguration.java +│   │   │   │   ├── SentryDemoApplication.java +│   │   │   │   ├── Todo.java +│   │   │   │   ├── TodoController.java +│   │   │   │   ├── graphql +│   │   │   │   │   ├── AssigneeController.java +│   │   │   │   │   ├── GreetingController.java +│   │   │   │   │   ├── ProjectController.java +│   │   │   │   │   └── TaskCreatorController.java +│   │   │   │   └── quartz +│   │   │   │   └── SampleJob.java +│   │   │   └── resources +│   │   │   ├── application.properties +│   │   │   ├── graphql +│   │   │   │   └── schema.graphqls +│   │   │   └── schema.sql +│   │   └── test +│   │   ├── kotlin +│   │   │   └── io +│   │   │   └── sentry +│   │   │   ├── DummyTest.kt +│   │   │   └── systemtest +│   │   │   ├── DistributedTracingSystemTest.kt +│   │   │   ├── GraphqlGreetingSystemTest.kt +│   │   │   ├── GraphqlProjectSystemTest.kt +│   │   │   ├── GraphqlTaskSystemTest.kt +│   │   │   ├── PersonSystemTest.kt +│   │   │   └── TodoSystemTest.kt +│   │   └── resources +│   │   └── logback.xml +│   ├── sentry-samples-spring-boot-opentelemetry-noagent +│   │   ├── README.md +│   │   ├── build.gradle.kts +│   │   └── src +│   │   ├── main +│   │   │   ├── java +│   │   │   │   └── io +│   │   │   │   └── sentry +│   │   │   │   └── samples +│   │   │   │   └── spring +│   │   │   │   └── boot +│   │   │   │   ├── CustomJob.java +│   │   │   │   ├── DistributedTracingController.java +│   │   │   │   ├── Person.java +│   │   │   │   ├── PersonController.java +│   │   │   │   ├── PersonService.java +│   │   │   │   ├── SecurityConfiguration.java +│   │   │   │   ├── SentryDemoApplication.java +│   │   │   │   ├── Todo.java +│   │   │   │   ├── TodoController.java +│   │   │   │   ├── graphql +│   │   │   │   │   ├── AssigneeController.java +│   │   │   │   │   ├── GreetingController.java +│   │   │   │   │   ├── ProjectController.java +│   │   │   │   │   └── TaskCreatorController.java +│   │   │   │   └── quartz +│   │   │   │   └── SampleJob.java +│   │   │   └── resources +│   │   │   ├── application.properties +│   │   │   ├── graphql +│   │   │   │   └── schema.graphqls +│   │   │   └── schema.sql +│   │   └── test +│   │   ├── kotlin +│   │   │   └── io +│   │   │   └── sentry +│   │   │   ├── DummyTest.kt +│   │   │   └── systemtest +│   │   │   ├── DistributedTracingSystemTest.kt +│   │   │   ├── GraphqlGreetingSystemTest.kt +│   │   │   ├── GraphqlProjectSystemTest.kt +│   │   │   ├── GraphqlTaskSystemTest.kt +│   │   │   ├── PersonSystemTest.kt +│   │   │   └── TodoSystemTest.kt +│   │   └── resources +│   │   └── logback.xml +│   ├── sentry-samples-spring-boot-webflux +│   │   ├── README.md +│   │   ├── build.gradle.kts +│   │   └── src +│   │   ├── main +│   │   │   ├── java +│   │   │   │   └── io +│   │   │   │   └── sentry +│   │   │   │   └── samples +│   │   │   │   └── spring +│   │   │   │   └── boot +│   │   │   │   ├── DistributedTracingController.java +│   │   │   │   ├── Person.java +│   │   │   │   ├── PersonController.java +│   │   │   │   ├── PersonService.java +│   │   │   │   ├── SentryDemoApplication.java +│   │   │   │   ├── Todo.java +│   │   │   │   ├── TodoController.java +│   │   │   │   └── graphql +│   │   │   │   └── GreetingController.java +│   │   │   └── resources +│   │   │   ├── application.properties +│   │   │   └── graphql +│   │   │   └── schema.graphqls +│   │   └── test +│   │   ├── kotlin +│   │   │   └── io +│   │   │   └── sentry +│   │   │   ├── DummyTest.kt +│   │   │   └── systemtest +│   │   │   ├── DistributedTracingSystemTest.kt +│   │   │   ├── GraphqlGreetingSystemTest.kt +│   │   │   ├── PersonSystemTest.kt +│   │   │   └── TodoSystemTest.kt +│   │   └── resources +│   │   └── logback.xml +│   ├── sentry-samples-spring-boot-webflux-jakarta +│   │   ├── README.md +│   │   ├── build.gradle.kts +│   │   └── src +│   │   ├── main +│   │   │   ├── java +│   │   │   │   └── io +│   │   │   │   └── sentry +│   │   │   │   └── samples +│   │   │   │   └── spring +│   │   │   │   └── boot +│   │   │   │   └── jakarta +│   │   │   │   ├── DistributedTracingController.java +│   │   │   │   ├── Person.java +│   │   │   │   ├── PersonController.java +│   │   │   │   ├── PersonService.java +│   │   │   │   ├── SentryDemoApplication.java +│   │   │   │   ├── Todo.java +│   │   │   │   ├── TodoController.java +│   │   │   │   └── graphql +│   │   │   │   └── GreetingController.java +│   │   │   └── resources +│   │   │   ├── application.properties +│   │   │   └── graphql +│   │   │   └── schema.graphqls +│   │   └── test +│   │   ├── kotlin +│   │   │   └── io +│   │   │   └── sentry +│   │   │   ├── DummyTest.kt +│   │   │   └── systemtest +│   │   │   ├── DistributedTracingSystemTest.kt +│   │   │   ├── GraphqlGreetingSystemTest.kt +│   │   │   ├── PersonSystemTest.kt +│   │   │   └── TodoSystemTest.kt +│   │   └── resources +│   │   └── logback.xml +│   └── sentry-samples-spring-jakarta +│   ├── README.md +│   ├── build.gradle.kts +│   ├── sentry-jakarta-text-master.properties +│   ├── sentry-package-rename.properties +│   └── src +│   └── main +│   ├── java +│   │   └── io +│   │   └── sentry +│   │   └── samples +│   │   └── spring +│   │   └── jakarta +│   │   ├── AppConfig.java +│   │   ├── AppInitializer.java +│   │   ├── SecurityConfiguration.java +│   │   ├── SentryConfig.java +│   │   ├── WebConfig.java +│   │   └── web +│   │   ├── Person.java +│   │   ├── PersonController.java +│   │   └── PersonService.java +│   └── resources +│   ├── logback.xml +│   └── sentry.properties +├── sentry-servlet +│   ├── api +│   │   └── sentry-servlet.api +│   ├── build.gradle.kts +│   └── src +│   ├── main +│   │   ├── java +│   │   │   └── io +│   │   │   └── sentry +│   │   │   └── servlet +│   │   │   ├── SentryRequestHttpServletRequestProcessor.java +│   │   │   ├── SentryServletContainerInitializer.java +│   │   │   └── SentryServletRequestListener.java +│   │   └── resources +│   │   └── META-INF +│   │   └── services +│   │   └── javax.servlet.ServletContainerInitializer +│   └── test +│   └── kotlin +│   └── io +│   └── sentry +│   └── servlet +│   ├── SentryRequestHttpServletRequestProcessorTest.kt +│   ├── SentryServletContainerInitializerTest.kt +│   └── SentryServletRequestListenerTest.kt +├── sentry-servlet-jakarta +│   ├── api +│   │   └── sentry-servlet-jakarta.api +│   ├── build.gradle.kts +│   └── src +│   ├── main +│   │   ├── java +│   │   │   └── io +│   │   │   └── sentry +│   │   │   └── servlet +│   │   │   └── jakarta +│   │   │   ├── SentryRequestHttpServletRequestProcessor.java +│   │   │   ├── SentryServletContainerInitializer.java +│   │   │   └── SentryServletRequestListener.java +│   │   └── resources +│   │   └── META-INF +│   │   └── services +│   │   └── jakarta.servlet.ServletContainerInitializer +│   └── test +│   └── kotlin +│   └── io +│   └── sentry +│   └── servlet +│   └── jakarta +│   ├── SentryRequestHttpServletRequestProcessorTest.kt +│   ├── SentryServletContainerInitializerTest.kt +│   └── SentryServletRequestListenerTest.kt +├── sentry-spring +│   ├── api +│   │   └── sentry-spring.api +│   ├── build.gradle.kts +│   └── src +│   ├── main +│   │   ├── java +│   │   │   └── io +│   │   │   └── sentry +│   │   │   └── spring +│   │   │   ├── ContextTagsEventProcessor.java +│   │   │   ├── EnableSentry.java +│   │   │   ├── HttpServletRequestSentryUserProvider.java +│   │   │   ├── RequestPayloadExtractor.java +│   │   │   ├── SentryExceptionResolver.java +│   │   │   ├── SentryHubRegistrar.java +│   │   │   ├── SentryInitBeanPostProcessor.java +│   │   │   ├── SentryRequestHttpServletRequestProcessor.java +│   │   │   ├── SentryRequestResolver.java +│   │   │   ├── SentrySpringFilter.java +│   │   │   ├── SentrySpringServletContainerInitializer.java +│   │   │   ├── SentryTaskDecorator.java +│   │   │   ├── SentryUserFilter.java +│   │   │   ├── SentryUserProvider.java +│   │   │   ├── SentryWebConfiguration.java +│   │   │   ├── SpringProfilesEventProcessor.java +│   │   │   ├── SpringSecuritySentryUserProvider.java +│   │   │   ├── checkin +│   │   │   │   ├── SentryCheckIn.java +│   │   │   │   ├── SentryCheckInAdvice.java +│   │   │   │   ├── SentryCheckInAdviceConfiguration.java +│   │   │   │   ├── SentryCheckInPointcutConfiguration.java +│   │   │   │   ├── SentryQuartzConfiguration.java +│   │   │   │   └── SentrySchedulerFactoryBeanCustomizer.java +│   │   │   ├── exception +│   │   │   │   ├── SentryCaptureExceptionParameter.java +│   │   │   │   ├── SentryCaptureExceptionParameterAdvice.java +│   │   │   │   ├── SentryCaptureExceptionParameterConfiguration.java +│   │   │   │   ├── SentryCaptureExceptionParameterPointcutConfiguration.java +│   │   │   │   └── SentryExceptionParameterAdviceConfiguration.java +│   │   │   ├── graphql +│   │   │   │   ├── SentryBatchLoaderRegistry.java +│   │   │   │   ├── SentryDataFetcherExceptionResolverAdapter.java +│   │   │   │   ├── SentryDgsSubscriptionHandler.java +│   │   │   │   ├── SentryGraphqlBeanPostProcessor.java +│   │   │   │   ├── SentryGraphqlConfiguration.java +│   │   │   │   └── SentrySpringSubscriptionHandler.java +│   │   │   ├── opentelemetry +│   │   │   │   ├── SentryOpenTelemetryAgentWithoutAutoInitConfiguration.java +│   │   │   │   └── SentryOpenTelemetryNoAgentConfiguration.java +│   │   │   ├── tracing +│   │   │   │   ├── CombinedTransactionNameProvider.java +│   │   │   │   ├── SentryAdviceConfiguration.java +│   │   │   │   ├── SentrySpan.java +│   │   │   │   ├── SentrySpanAdvice.java +│   │   │   │   ├── SentrySpanClientHttpRequestInterceptor.java +│   │   │   │   ├── SentrySpanClientWebRequestFilter.java +│   │   │   │   ├── SentrySpanPointcutConfiguration.java +│   │   │   │   ├── SentryTracingConfiguration.java +│   │   │   │   ├── SentryTracingFilter.java +│   │   │   │   ├── SentryTransaction.java +│   │   │   │   ├── SentryTransactionAdvice.java +│   │   │   │   ├── SentryTransactionPointcutConfiguration.java +│   │   │   │   ├── SpringMvcTransactionNameProvider.java +│   │   │   │   ├── SpringServletTransactionNameProvider.java +│   │   │   │   ├── TransactionNameProvider.java +│   │   │   │   └── TransactionNameWithSource.java +│   │   │   └── webflux +│   │   │   ├── SentryRequestResolver.java +│   │   │   ├── SentryScheduleHook.java +│   │   │   ├── SentryWebExceptionHandler.java +│   │   │   ├── SentryWebFilter.java +│   │   │   └── TransactionNameProvider.java +│   │   └── resources +│   │   └── META-INF +│   │   └── services +│   │   └── javax.servlet.ServletContainerInitializer +│   └── test +│   ├── kotlin +│   │   └── io +│   │   └── sentry +│   │   └── spring +│   │   ├── ContextTagsEventProcessorTest.kt +│   │   ├── EnableSentryTest.kt +│   │   ├── HttpServletRequestSentryUserProviderTest.kt +│   │   ├── SentryCheckInAdviceTest.kt +│   │   ├── SentryExceptionResolverTest.kt +│   │   ├── SentryInitBeanPostProcessorTest.kt +│   │   ├── SentryRequestHttpServletRequestProcessorTest.kt +│   │   ├── SentrySpringFilterTest.kt +│   │   ├── SentryTaskDecoratorTest.kt +│   │   ├── SentryUserFilterTest.kt +│   │   ├── SpringProfilesEventProcessorTest.kt +│   │   ├── SpringSecuritySentryUserProviderTest.kt +│   │   ├── exception +│   │   │   └── SentryCaptureExceptionParameterAdviceTest.kt +│   │   ├── graphql +│   │   │   └── SentrySpringSubscriptionHandlerTest.kt +│   │   ├── mvc +│   │   │   └── SentrySpringIntegrationTest.kt +│   │   ├── tracing +│   │   │   ├── SentrySpanAdviceTest.kt +│   │   │   ├── SentryTracingFilterTest.kt +│   │   │   └── SentryTransactionAdviceTest.kt +│   │   └── webflux +│   │   ├── SentryScheduleHookTest.kt +│   │   ├── SentryWebFluxTracingFilterTest.kt +│   │   └── SentryWebfluxIntegrationTest.kt +│   └── resources +│   └── mockito-extensions +│   └── org.mockito.plugins.MockMaker +├── sentry-spring-boot +│   ├── README.md +│   ├── api +│   │   └── sentry-spring-boot.api +│   ├── build.gradle.kts +│   └── src +│   ├── main +│   │   ├── java +│   │   │   └── io +│   │   │   └── sentry +│   │   │   └── spring +│   │   │   └── boot +│   │   │   ├── InAppIncludesResolver.java +│   │   │   ├── SentryAutoConfiguration.java +│   │   │   ├── SentryLogbackAppenderAutoConfiguration.java +│   │   │   ├── SentryLogbackInitializer.java +│   │   │   ├── SentryProperties.java +│   │   │   ├── SentrySpanRestTemplateCustomizer.java +│   │   │   ├── SentrySpanWebClientCustomizer.java +│   │   │   ├── SentrySpringVersionChecker.java +│   │   │   ├── SentryWebfluxAutoConfiguration.java +│   │   │   └── graphql +│   │   │   └── SentryGraphqlAutoConfiguration.java +│   │   └── resources +│   │   └── META-INF +│   │   ├── native-image +│   │   │   └── io.sentry +│   │   │   └── sentry +│   │   │   └── proxy-config.json +│   │   └── spring.factories +│   └── test +│   ├── kotlin +│   │   ├── com +│   │   │   └── acme +│   │   │   └── MainBootClass.kt +│   │   └── io +│   │   └── sentry +│   │   └── spring +│   │   └── boot +│   │   ├── SentryAutoConfigurationTest.kt +│   │   ├── SentryLogbackAppenderAutoConfigurationTest.kt +│   │   ├── SentrySpanRestTemplateCustomizerTest.kt +│   │   ├── SentrySpanWebClientCustomizerTest.kt +│   │   ├── SentryWebfluxAutoConfigurationTest.kt +│   │   └── it +│   │   └── SentrySpringIntegrationTest.kt +│   └── resources +│   └── mockito-extensions +│   └── org.mockito.plugins.MockMaker +├── sentry-spring-boot-jakarta +│   ├── .gitignore +│   ├── api +│   │   └── sentry-spring-boot-jakarta.api +│   ├── build.gradle.kts +│   ├── sentry-jakarta-text-master.properties +│   ├── sentry-package-rename.properties +│   ├── spring-test-rename.properties +│   └── src +│   ├── main +│   │   ├── java +│   │   │   └── io +│   │   │   └── sentry +│   │   │   └── spring +│   │   │   └── boot +│   │   │   └── jakarta +│   │   │   ├── InAppIncludesResolver.java +│   │   │   ├── SentryAutoConfiguration.java +│   │   │   ├── SentryLogbackAppenderAutoConfiguration.java +│   │   │   ├── SentryLogbackInitializer.java +│   │   │   ├── SentryProperties.java +│   │   │   ├── SentrySpanRestClientCustomizer.java +│   │   │   ├── SentrySpanRestTemplateCustomizer.java +│   │   │   ├── SentrySpanWebClientCustomizer.java +│   │   │   ├── SentrySpringVersionChecker.java +│   │   │   ├── SentryWebfluxAutoConfiguration.java +│   │   │   └── graphql +│   │   │   ├── SentryGraphql22AutoConfiguration.java +│   │   │   └── SentryGraphqlAutoConfiguration.java +│   │   └── resources +│   │   └── META-INF +│   │   ├── native-image +│   │   │   └── io.sentry +│   │   │   └── sentry +│   │   │   └── proxy-config.json +│   │   ├── spring +│   │   │   └── org.springframework.boot.autoconfigure.AutoConfiguration.imports +│   │   └── spring.factories +│   └── test +│   ├── kotlin +│   │   ├── com +│   │   │   └── acme +│   │   │   └── MainBootClass.kt +│   │   └── io +│   │   └── sentry +│   │   └── spring +│   │   └── boot +│   │   └── jakarta +│   │   ├── SentryAutoConfigurationTest.kt +│   │   ├── SentryLogbackAppenderAutoConfigurationTest.kt +│   │   ├── SentrySpanRestClientCustomizerTest.kt +│   │   ├── SentrySpanRestTemplateCustomizerTest.kt +│   │   ├── SentrySpanWebClientCustomizerTest.kt +│   │   ├── SentryWebfluxAutoConfigurationTest.kt +│   │   └── it +│   │   └── SentrySpringIntegrationTest.kt +│   └── resources +│   └── mockito-extensions +│   └── org.mockito.plugins.MockMaker +├── sentry-spring-boot-starter +│   ├── api +│   │   └── sentry-spring-boot-starter.api +│   └── build.gradle.kts +├── sentry-spring-boot-starter-jakarta +│   ├── .gitignore +│   ├── api +│   │   └── sentry-spring-boot-starter-jakarta.api +│   └── build.gradle.kts +├── sentry-spring-jakarta +│   ├── api +│   │   └── sentry-spring-jakarta.api +│   ├── build.gradle.kts +│   ├── sentry-jakarta-text-master.properties +│   ├── sentry-package-rename.properties +│   ├── spring-test-rename.properties +│   └── src +│   ├── main +│   │   ├── java +│   │   │   └── io +│   │   │   └── sentry +│   │   │   └── spring +│   │   │   └── jakarta +│   │   │   ├── ContextTagsEventProcessor.java +│   │   │   ├── EnableSentry.java +│   │   │   ├── HttpServletRequestSentryUserProvider.java +│   │   │   ├── RequestPayloadExtractor.java +│   │   │   ├── SentryExceptionResolver.java +│   │   │   ├── SentryHubRegistrar.java +│   │   │   ├── SentryInitBeanPostProcessor.java +│   │   │   ├── SentryRequestHttpServletRequestProcessor.java +│   │   │   ├── SentryRequestResolver.java +│   │   │   ├── SentrySpringFilter.java +│   │   │   ├── SentrySpringServletContainerInitializer.java +│   │   │   ├── SentryTaskDecorator.java +│   │   │   ├── SentryUserFilter.java +│   │   │   ├── SentryUserProvider.java +│   │   │   ├── SentryWebConfiguration.java +│   │   │   ├── SpringProfilesEventProcessor.java +│   │   │   ├── SpringSecuritySentryUserProvider.java +│   │   │   ├── checkin +│   │   │   │   ├── SentryCheckIn.java +│   │   │   │   ├── SentryCheckInAdvice.java +│   │   │   │   ├── SentryCheckInAdviceConfiguration.java +│   │   │   │   ├── SentryCheckInPointcutConfiguration.java +│   │   │   │   ├── SentryQuartzConfiguration.java +│   │   │   │   └── SentrySchedulerFactoryBeanCustomizer.java +│   │   │   ├── exception +│   │   │   │   ├── SentryCaptureExceptionParameter.java +│   │   │   │   ├── SentryCaptureExceptionParameterAdvice.java +│   │   │   │   ├── SentryCaptureExceptionParameterConfiguration.java +│   │   │   │   ├── SentryCaptureExceptionParameterPointcutConfiguration.java +│   │   │   │   └── SentryExceptionParameterAdviceConfiguration.java +│   │   │   ├── graphql +│   │   │   │   ├── SentryBatchLoaderRegistry.java +│   │   │   │   ├── SentryDataFetcherExceptionResolverAdapter.java +│   │   │   │   ├── SentryDgsSubscriptionHandler.java +│   │   │   │   ├── SentryGraphql22Configuration.java +│   │   │   │   ├── SentryGraphqlBeanPostProcessor.java +│   │   │   │   ├── SentryGraphqlConfiguration.java +│   │   │   │   └── SentrySpringSubscriptionHandler.java +│   │   │   ├── opentelemetry +│   │   │   │   ├── SentryOpenTelemetryAgentWithoutAutoInitConfiguration.java +│   │   │   │   └── SentryOpenTelemetryNoAgentConfiguration.java +│   │   │   ├── tracing +│   │   │   │   ├── CombinedTransactionNameProvider.java +│   │   │   │   ├── SentryAdviceConfiguration.java +│   │   │   │   ├── SentrySpan.java +│   │   │   │   ├── SentrySpanAdvice.java +│   │   │   │   ├── SentrySpanClientHttpRequestInterceptor.java +│   │   │   │   ├── SentrySpanClientWebRequestFilter.java +│   │   │   │   ├── SentrySpanPointcutConfiguration.java +│   │   │   │   ├── SentryTracingConfiguration.java +│   │   │   │   ├── SentryTracingFilter.java +│   │   │   │   ├── SentryTransaction.java +│   │   │   │   ├── SentryTransactionAdvice.java +│   │   │   │   ├── SentryTransactionPointcutConfiguration.java +│   │   │   │   ├── SpringMvcTransactionNameProvider.java +│   │   │   │   ├── SpringServletTransactionNameProvider.java +│   │   │   │   ├── TransactionNameProvider.java +│   │   │   │   └── TransactionNameWithSource.java +│   │   │   └── webflux +│   │   │   ├── AbstractSentryWebFilter.java +│   │   │   ├── SentryRequestResolver.java +│   │   │   ├── SentryScheduleHook.java +│   │   │   ├── SentryWebExceptionHandler.java +│   │   │   ├── SentryWebFilter.java +│   │   │   ├── SentryWebFilterWithThreadLocalAccessor.java +│   │   │   ├── TransactionNameProvider.java +│   │   │   └── reactor +│   │   │   └── ReactorUtils.java +│   │   └── resources +│   │   └── META-INF +│   │   └── services +│   │   └── jakarta.servlet.ServletContainerInitializer +│   └── test +│   └── kotlin +│   └── io +│   └── sentry +│   └── spring +│   └── jakarta +│   ├── ContextTagsEventProcessorTest.kt +│   ├── EnableSentryTest.kt +│   ├── HttpServletRequestSentryUserProviderTest.kt +│   ├── SentryCheckInAdviceTest.kt +│   ├── SentryExceptionResolverTest.kt +│   ├── SentryInitBeanPostProcessorTest.kt +│   ├── SentryRequestHttpServletRequestProcessorTest.kt +│   ├── SentrySpringFilterTest.kt +│   ├── SentryTaskDecoratorTest.kt +│   ├── SentryUserFilterTest.kt +│   ├── SpringProfilesEventProcessorTest.kt +│   ├── SpringSecuritySentryUserProviderTest.kt +│   ├── exception +│   │   └── SentryCaptureExceptionParameterAdviceTest.kt +│   ├── graphql +│   │   └── SentrySpringSubscriptionHandlerTest.kt +│   ├── mvc +│   │   └── SentrySpringIntegrationTest.kt +│   ├── tracing +│   │   ├── SentrySpanAdviceTest.kt +│   │   ├── SentryTracingFilterTest.kt +│   │   └── SentryTransactionAdviceTest.kt +│   └── webflux +│   ├── SentryScheduleHookTest.kt +│   ├── SentryWebFluxTracingFilterTest.kt +│   └── SentryWebfluxIntegrationTest.kt +├── sentry-system-test-support +│   ├── api +│   │   └── sentry-system-test-support.api +│   ├── build.gradle.kts +│   └── src +│   └── main +│   ├── graphql +│   │   ├── greeting.graphql +│   │   ├── project.graphql +│   │   ├── schema.graphqls +│   │   └── task.graphql +│   └── kotlin +│   └── io +│   └── sentry +│   └── systemtest +│   ├── ResponseTypes.kt +│   ├── graphql +│   │   └── GraphqlTestClient.kt +│   └── util +│   ├── LoggingInsecureRestClient.kt +│   ├── RestTestClient.kt +│   ├── SentryMockServerClient.kt +│   └── TestHelper.kt +├── sentry-test-support +│   ├── api +│   │   └── sentry-test-support.api +│   ├── build.gradle.kts +│   └── src +│   └── main +│   └── kotlin +│   └── io +│   └── sentry +│   ├── Assertions.kt +│   └── test +│   ├── Init.kt +│   ├── Mocks.kt +│   └── Reflection.kt +├── sentry.properties +├── settings.gradle.kts +└── test + ├── system-test-run-all.sh + ├── system-test-run.sh + ├── system-test-sentry-server-start.sh + ├── system-test-sentry-server-stop.sh + ├── system-test-sentry-server.py + ├── system-test-spring-server-start.sh + └── wait-for-spring.sh + +1185 directories, 2119 files diff --git a/project_structure_detailed.txt b/project_structure_detailed.txt new file mode 100644 index 0000000000..05730efd17 --- /dev/null +++ b/project_structure_detailed.txt @@ -0,0 +1,3307 @@ +[2.5K May 28 11:22] . +├── [2.4K May 27 19:34] .craft.yml +├── [ 252 Feb 18 16:11] .editorconfig +├── [ 128 May 27 19:34] .gitattributes +├── [ 256 Feb 18 16:11] .github +│   ├── [ 57 Feb 18 16:11] CODEOWNERS +│   ├── [ 256 May 27 19:34] ISSUE_TEMPLATE +│   │   ├── [2.1K May 27 19:34] bug_report_android.yml +│   │   ├── [2.2K May 27 19:34] bug_report_java.yml +│   │   ├── [ 235 Feb 18 16:11] config.yml +│   │   ├── [ 843 May 27 19:34] feature_android.yml +│   │   ├── [ 837 May 27 19:34] feature_java.yml +│   │   └── [ 421 May 27 19:34] maintainer-blank.yml +│   ├── [ 116 Feb 18 16:11] dependabot.yml +│   ├── [ 624 Feb 18 16:11] file-filters.yml +│   ├── [ 759 Feb 18 16:11] pull_request_template.md +│   └── [ 544 May 27 19:34] workflows +│   ├── [ 378 Feb 18 16:11] add-platform-label.yml +│   ├── [3.2K May 27 19:34] agp-matrix.yml +│   ├── [2.0K May 27 19:34] build.yml +│   ├── [2.0K Feb 18 16:11] changes-in-high-risk-code.yml +│   ├── [1.1K May 27 19:34] codeql-analysis.yml +│   ├── [ 189 Feb 18 16:11] danger.yml +│   ├── [ 631 May 27 19:34] enforce-license-compliance.yml +│   ├── [1.1K Feb 18 16:11] format-code.yml +│   ├── [3.9K May 27 19:34] integration-tests-benchmarks.yml +│   ├── [4.0K May 27 19:34] integration-tests-ui-critical.yml +│   ├── [2.1K May 27 19:34] integration-tests-ui.yml +│   ├── [1.0K May 27 19:34] release-build.yml +│   ├── [1.3K May 27 19:34] release.yml +│   ├── [4.3K May 27 19:34] system-tests-backend.yml +│   └── [ 827 Feb 18 16:11] update-deps.yml +├── [ 392 May 27 19:34] .gitignore +├── [ 0 Feb 18 16:11] .gitmodules +├── [ 96 Feb 18 16:11] .mvn +│   └── [ 160 Feb 18 16:11] wrapper +│   ├── [4.5K Feb 18 16:11] MavenWrapperDownloader.java +│   └── [ 218 Feb 18 16:11] maven-wrapper.properties +├── [ 160 Feb 18 16:11] .sauce +│   ├── [ 857 Feb 18 16:11] sentry-uitest-android-benchmark-lite.yml +│   ├── [2.8K Feb 18 16:11] sentry-uitest-android-benchmark.yml +│   └── [1.3K Feb 18 16:11] sentry-uitest-android-ui.yml +├── [246K May 27 19:34] CHANGELOG.md +├── [1.4K May 27 19:34] CONTRIBUTING.md +├── [1.1K Feb 18 16:11] LICENSE +├── [2.4K Feb 18 16:11] MIGRATION.md +├── [2.2K May 27 19:34] Makefile +├── [ 18K May 27 19:34] README.md +├── [ 512 May 28 11:08] aidocs +│   ├── [1.5K May 28 11:14] .cursorrules +│   ├── [ 18K May 27 20:36] README.md +│   ├── [ 512 May 28 11:04] mermaid +│   │   ├── [5.1K May 28 11:06] README.md +│   │   ├── [ 805 May 28 10:59] sentry-crash-monitoring-anr-detection.mmd +│   │   ├── [ 675 May 28 11:00] sentry-crash-monitoring-crash-recovery.mmd +│   │   ├── [ 737 May 28 11:01] sentry-crash-monitoring-exception-capture.mmd +│   │   ├── [ 689 May 28 10:59] sentry-crash-monitoring-native-crash.mmd +│   │   ├── [ 662 May 28 11:00] sentry-crash-monitoring-startup-crash.mmd +│   │   ├── [ 353 May 28 10:58] sentry-init-quick-reference-android-flow.mmd +│   │   ├── [3.4K May 28 10:57] sentry-initialization-flow-android-initialization.mmd +│   │   ├── [1.1K May 28 10:57] sentry-initialization-flow-client-creation.mmd +│   │   ├── [1.2K May 28 10:58] sentry-initialization-flow-configuration-loading.mmd +│   │   ├── [3.7K May 28 10:56] sentry-initialization-flow-core-initialization.mmd +│   │   ├── [1.6K May 28 10:57] sentry-initialization-flow-integration-registration.mmd +│   │   ├── [1.0K May 28 10:58] sentry-startup-monitoring-time-measurement.mmd +│   │   └── [ 448 May 28 11:04] svg +│   │   ├── [ 26K May 28 11:04] sentry-crash-monitoring-anr-detection.svg +│   │   ├── [ 25K May 28 11:04] sentry-crash-monitoring-crash-recovery.svg +│   │   ├── [ 26K May 28 11:04] sentry-crash-monitoring-exception-capture.svg +│   │   ├── [ 26K May 28 11:04] sentry-crash-monitoring-native-crash.svg +│   │   ├── [ 25K May 28 11:04] sentry-crash-monitoring-startup-crash.svg +│   │   ├── [ 22K May 28 11:04] sentry-init-quick-reference-android-flow.svg +│   │   ├── [ 46K May 28 11:04] sentry-initialization-flow-android-initialization.svg +│   │   ├── [ 28K May 28 11:04] sentry-initialization-flow-client-creation.svg +│   │   ├── [ 33K May 28 11:04] sentry-initialization-flow-configuration-loading.svg +│   │   ├── [ 53K May 28 11:04] sentry-initialization-flow-core-initialization.svg +│   │   ├── [ 30K May 28 11:04] sentry-initialization-flow-integration-registration.svg +│   │   └── [ 28K May 28 11:04] sentry-startup-monitoring-time-measurement.svg +│   ├── [ 128 May 28 11:09] rules +│   │   ├── [6.8K May 28 11:15] .cursorrules +│   │   └── [2.0K May 28 11:11] README.md +│   ├── [ 53K May 28 10:37] sentry-crash-monitoring.md +│   ├── [3.7K May 27 12:33] sentry-init-quick-reference.md +│   ├── [9.1K May 27 12:33] sentry-initialization-details.md +│   ├── [ 12K May 28 10:49] sentry-initialization-flow.md +│   ├── [ 30K May 27 19:43] sentry-network-monitoring.md +│   ├── [ 35K May 27 19:40] sentry-profiling-analysis.md +│   ├── [ 23K May 27 19:37] sentry-replay-analysis.md +│   ├── [ 26K May 27 19:34] sentry-session-management.md +│   ├── [ 22K May 27 19:15] sentry-startup-monitoring.md +│   └── [ 23K May 27 19:28] sentry-ui-jank-monitoring.md +├── [ 11K May 27 19:34] build.gradle.kts +├── [ 256 Feb 18 16:15] buildSrc +│   ├── [ 96 Feb 18 16:15] .kotlin +│   │   └── [ 64 May 27 20:19] sessions +│   ├── [ 237 Feb 18 16:11] build.gradle.kts +│   ├── [ 37 Feb 18 16:11] settings.gradle.kts +│   └── [ 96 Feb 18 16:11] src +│   └── [ 96 Feb 18 16:11] main +│   └── [ 128 May 27 19:34] java +│   ├── [ 13K May 27 19:34] Config.kt +│   └── [3.4K Feb 18 16:11] Publication.kt +├── [ 355 May 27 19:34] codecov.yml +├── [2.4K Feb 18 16:11] debug.keystore +├── [ 33 Feb 18 16:11] detekt.yml +├── [ 96 Feb 18 16:11] docs +│   └── [ 12K Feb 18 16:11] stylesheet.css +├── [ 128 May 27 19:34] gradle +│   ├── [5.0K May 27 19:34] libs.versions.toml +│   └── [ 128 May 27 19:34] wrapper +│   └── [ 253 May 27 19:34] gradle-wrapper.properties +├── [1.8K May 27 19:34] gradle.properties +├── [8.5K May 27 19:34] gradlew +├── [2.9K May 27 19:34] gradlew.bat +├── [ 96 Feb 18 16:11] hooks +│   └── [ 315 Feb 18 16:11] pre-commit +├── [ 346 Feb 18 16:15] local.properties +├── [200K May 28 11:22] project_structure.txt +├── [ 0 May 28 11:22] project_structure_detailed.txt +├── [ 352 May 27 19:34] scripts +│   ├── [ 475 Feb 18 16:11] bump-version.sh +│   ├── [ 483 Feb 18 16:11] commit-formatted-code.sh +│   ├── [9.8K Feb 18 16:11] mvnw +│   ├── [6.5K Feb 18 16:11] mvnw.cmd +│   ├── [ 449 Feb 18 16:11] settings.xml +│   ├── [1.1K Feb 18 16:11] test-ui-critical.sh +│   ├── [3.1K May 27 19:34] toggle-codec-logs.sh +│   ├── [1.2K Feb 18 16:11] update-gradle.sh +│   └── [ 617 Feb 18 16:11] update-sentry-native-ndk.sh +├── [ 192 May 27 19:34] sentry +│   ├── [ 96 May 27 19:34] api +│   │   └── [351K May 27 19:34] sentry.api +│   ├── [2.7K May 27 19:34] build.gradle.kts +│   └── [ 128 Feb 18 16:11] src +│   ├── [ 128 Feb 18 16:11] main +│   │   ├── [ 96 Feb 18 16:11] java +│   │   │   └── [ 96 Feb 18 16:11] io +│   │   │   └── [7.1K May 27 19:34] sentry +│   │   │   ├── [ 809 Feb 18 16:11] AsyncHttpTransportFactory.java +│   │   │   ├── [ 14K May 27 19:34] Attachment.java +│   │   │   ├── [ 314 Feb 18 16:11] BackfillingEventProcessor.java +│   │   │   ├── [ 21K May 27 19:34] Baggage.java +│   │   │   ├── [1.0K Feb 18 16:11] BaggageHeader.java +│   │   │   ├── [ 25K May 27 19:34] Breadcrumb.java +│   │   │   ├── [7.4K Feb 18 16:11] CheckIn.java +│   │   │   ├── [ 329 Feb 18 16:11] CheckInStatus.java +│   │   │   ├── [ 11K Feb 18 16:11] CircularFifoQueue.java +│   │   │   ├── [8.3K May 27 19:34] CombinedContextsView.java +│   │   │   ├── [ 14K May 27 19:34] CombinedScopeView.java +│   │   │   ├── [1.2K May 27 19:34] CompositePerformanceCollector.java +│   │   │   ├── [ 579 May 27 19:34] CpuCollectionData.java +│   │   │   ├── [ 796 Feb 18 16:11] CustomSamplingContext.java +│   │   │   ├── [ 727 May 27 19:34] DataCategory.java +│   │   │   ├── [4.7K May 27 19:34] DateUtils.java +│   │   │   ├── [2.2K Feb 18 16:11] DeduplicateMultithreadedEventProcessor.java +│   │   │   ├── [7.8K May 27 19:34] DefaultCompositePerformanceCollector.java +│   │   │   ├── [1.0K Feb 18 16:11] DefaultScopesStorage.java +│   │   │   ├── [1.2K May 27 19:34] DefaultSpanFactory.java +│   │   │   ├── [ 530 May 27 19:34] DefaultVersionDetector.java +│   │   │   ├── [2.8K Feb 18 16:11] DiagnosticLogger.java +│   │   │   ├── [5.7K Feb 18 16:11] DirectoryProcessor.java +│   │   │   ├── [2.4K Feb 18 16:11] DisabledQueue.java +│   │   │   ├── [2.6K Feb 18 16:11] Dsn.java +│   │   │   ├── [1.1K May 27 19:34] DsnUtil.java +│   │   │   ├── [2.2K Feb 18 16:11] DuplicateEventDetectionEventProcessor.java +│   │   │   ├── [5.3K Feb 18 16:11] EnvelopeReader.java +│   │   │   ├── [4.6K Feb 18 16:11] EnvelopeSender.java +│   │   │   ├── [1.5K Feb 18 16:11] EventProcessor.java +│   │   │   ├── [ 424 Feb 18 16:11] ExperimentalOptions.java +│   │   │   ├── [ 18K May 27 19:34] ExternalOptions.java +│   │   │   ├── [1.3K May 27 19:34] FilterString.java +│   │   │   ├── [1.1K Feb 18 16:11] FullyDisplayedReporter.java +│   │   │   ├── [5.0K Feb 18 16:11] Hint.java +│   │   │   ├── [5.1K May 27 19:34] HostnameCache.java +│   │   │   ├── [ 753 Feb 18 16:11] HttpStatusCodeRange.java +│   │   │   ├── [9.7K May 27 19:34] HubAdapter.java +│   │   │   ├── [9.0K May 27 19:34] HubScopesWrapper.java +│   │   │   ├── [1.4K Feb 18 16:11] IConnectionStatusProvider.java +│   │   │   ├── [ 764 May 27 19:34] IContinuousProfiler.java +│   │   │   ├── [ 282 Feb 18 16:11] IEnvelopeReader.java +│   │   │   ├── [ 170 Feb 18 16:11] IEnvelopeSender.java +│   │   │   ├── [ 199 Feb 18 16:11] IHub.java +│   │   │   ├── [1.5K Feb 18 16:11] ILogger.java +│   │   │   ├── [ 374 Feb 18 16:11] IMemoryCollector.java +│   │   │   ├── [ 780 Feb 18 16:11] IOptionsObserver.java +│   │   │   ├── [ 209 Feb 18 16:11] IPerformanceCollector.java +│   │   │   ├── [ 828 Feb 18 16:11] IPerformanceContinuousCollector.java +│   │   │   ├── [ 405 Feb 18 16:11] IPerformanceSnapshotCollector.java +│   │   │   ├── [ 490 May 27 19:34] IReplayApi.java +│   │   │   ├── [9.4K May 27 19:34] IScope.java +│   │   │   ├── [1.3K Feb 18 16:11] IScopeObserver.java +│   │   │   ├── [ 22K May 27 19:34] IScopes.java +│   │   │   ├── [ 342 Feb 18 16:11] IScopesStorage.java +│   │   │   ├── [9.9K May 27 19:34] ISentryClient.java +│   │   │   ├── [1.3K Feb 18 16:11] ISentryExecutorService.java +│   │   │   ├── [ 177 Feb 18 16:11] ISentryLifecycleToken.java +│   │   │   ├── [1.1K Feb 18 16:11] ISerializer.java +│   │   │   ├── [ 272 May 27 19:34] ISocketTagger.java +│   │   │   ├── [6.4K May 27 19:34] ISpan.java +│   │   │   ├── [ 633 May 27 19:34] ISpanFactory.java +│   │   │   ├── [2.5K Feb 18 16:11] ITransaction.java +│   │   │   ├── [ 702 Feb 18 16:11] ITransactionProfiler.java +│   │   │   ├── [ 604 Feb 18 16:11] ITransportFactory.java +│   │   │   ├── [ 281 May 27 19:34] IVersionDetector.java +│   │   │   ├── [ 160 Feb 18 16:11] InitPriority.java +│   │   │   ├── [ 217 Feb 18 16:11] Instrumenter.java +│   │   │   ├── [ 448 Feb 18 16:11] Integration.java +│   │   │   ├── [ 592 Feb 18 16:11] IpAddressUtils.java +│   │   │   ├── [ 665 May 27 19:34] JavaMemoryCollector.java +│   │   │   ├── [ 268 Feb 18 16:11] JsonDeserializer.java +│   │   │   ├── [5.4K Feb 18 16:11] JsonObjectDeserializer.java +│   │   │   ├── [7.3K Feb 18 16:11] JsonObjectReader.java +│   │   │   ├── [4.8K Feb 18 16:11] JsonObjectSerializer.java +│   │   │   ├── [3.0K May 27 19:34] JsonObjectWriter.java +│   │   │   ├── [5.3K Feb 18 16:11] JsonReflectionObjectSerializer.java +│   │   │   ├── [ 239 Feb 18 16:11] JsonSerializable.java +│   │   │   ├── [ 13K May 27 19:34] JsonSerializer.java +│   │   │   ├── [ 290 Feb 18 16:11] JsonUnknown.java +│   │   │   ├── [9.5K May 27 19:34] MainEventProcessor.java +│   │   │   ├── [ 642 May 27 19:34] ManifestVersionDetector.java +│   │   │   ├── [3.4K Feb 18 16:11] MeasurementUnit.java +│   │   │   ├── [ 882 May 27 19:34] MemoryCollectionData.java +│   │   │   ├── [6.2K Feb 18 16:11] MonitorConfig.java +│   │   │   ├── [2.9K Feb 18 16:11] MonitorContexts.java +│   │   │   ├── [4.8K Feb 18 16:11] MonitorSchedule.java +│   │   │   ├── [ 335 Feb 18 16:11] MonitorScheduleType.java +│   │   │   ├── [ 368 Feb 18 16:11] MonitorScheduleUnit.java +│   │   │   ├── [1020 May 27 19:34] NoOpCompositePerformanceCollector.java +│   │   │   ├── [ 702 Feb 18 16:11] NoOpConnectionStatusProvider.java +│   │   │   ├── [ 902 May 27 19:34] NoOpContinuousProfiler.java +│   │   │   ├── [ 546 Feb 18 16:11] NoOpEnvelopeReader.java +│   │   │   ├── [7.7K May 27 19:34] NoOpHub.java +│   │   │   ├── [ 858 Feb 18 16:11] NoOpLogger.java +│   │   │   ├── [ 579 Feb 18 16:11] NoOpReplayBreadcrumbConverter.java +│   │   │   ├── [1.2K May 27 19:34] NoOpReplayController.java +│   │   │   ├── [6.4K May 27 19:34] NoOpScope.java +│   │   │   ├── [7.7K May 27 19:34] NoOpScopes.java +│   │   │   ├── [ 355 Feb 18 16:11] NoOpScopesLifecycleToken.java +│   │   │   ├── [ 613 Feb 18 16:11] NoOpScopesStorage.java +│   │   │   ├── [2.6K May 27 19:34] NoOpSentryClient.java +│   │   │   ├── [1007 Feb 18 16:11] NoOpSentryExecutorService.java +│   │   │   ├── [1.3K Feb 18 16:11] NoOpSerializer.java +│   │   │   ├── [ 452 May 27 19:34] NoOpSocketTagger.java +│   │   │   ├── [4.3K May 27 19:34] NoOpSpan.java +│   │   │   ├── [ 964 May 27 19:34] NoOpSpanFactory.java +│   │   │   ├── [5.7K May 27 19:34] NoOpTransaction.java +│   │   │   ├── [ 892 Feb 18 16:11] NoOpTransactionProfiler.java +│   │   │   ├── [ 667 Feb 18 16:11] NoOpTransportFactory.java +│   │   │   ├── [ 365 May 27 19:34] NoopVersionDetector.java +│   │   │   ├── [2.7K Feb 18 16:11] ObjectReader.java +│   │   │   ├── [1.2K May 27 19:34] ObjectWriter.java +│   │   │   ├── [ 723 Feb 18 16:11] OptionsContainer.java +│   │   │   ├── [ 11K Feb 18 16:11] OutboxSender.java +│   │   │   ├── [ 935 Feb 18 16:11] PerformanceCollectionData.java +│   │   │   ├── [5.4K Feb 18 16:11] PreviousSessionFinalizer.java +│   │   │   ├── [ 11K May 27 19:34] ProfileChunk.java +│   │   │   ├── [3.3K May 27 19:34] ProfileContext.java +│   │   │   ├── [ 522 May 27 19:34] ProfileLifecycle.java +│   │   │   ├── [ 22K May 27 19:34] ProfilingTraceData.java +│   │   │   ├── [7.7K Feb 18 16:11] ProfilingTransactionData.java +│   │   │   ├── [4.5K May 27 19:34] PropagationContext.java +│   │   │   ├── [ 318 Feb 18 16:11] ReplayBreadcrumbConverter.java +│   │   │   ├── [ 637 May 27 19:34] ReplayController.java +│   │   │   ├── [8.5K Feb 18 16:11] ReplayRecording.java +│   │   │   ├── [1007 Feb 18 16:11] RequestDetails.java +│   │   │   ├── [2.0K May 27 19:34] RequestDetailsResolver.java +│   │   │   ├── [2.0K May 27 19:34] SamplingContext.java +│   │   │   ├── [ 31K May 27 19:34] Scope.java +│   │   │   ├── [ 560 Feb 18 16:11] ScopeBindingMode.java +│   │   │   ├── [ 133 Feb 18 16:11] ScopeCallback.java +│   │   │   ├── [1.5K Feb 18 16:11] ScopeObserverAdapter.java +│   │   │   ├── [ 92 Feb 18 16:11] ScopeType.java +│   │   │   ├── [ 39K May 27 19:34] Scopes.java +│   │   │   ├── [9.6K May 27 19:34] ScopesAdapter.java +│   │   │   ├── [1.7K Feb 18 16:11] ScopesStorageFactory.java +│   │   │   ├── [6.6K Feb 18 16:11] SendCachedEnvelopeFireAndForgetIntegration.java +│   │   │   ├── [1.6K Feb 18 16:11] SendFireAndForgetEnvelopeSender.java +│   │   │   ├── [1.7K Feb 18 16:11] SendFireAndForgetOutboxSender.java +│   │   │   ├── [ 45K May 27 19:34] Sentry.java +│   │   │   ├── [ 12K May 27 19:34] SentryAppStartProfilingOptions.java +│   │   │   ├── [1.6K May 27 19:34] SentryAttribute.java +│   │   │   ├── [ 252 May 27 19:34] SentryAttributeType.java +│   │   │   ├── [1.7K May 27 19:34] SentryAttributes.java +│   │   │   ├── [1007 Feb 18 16:11] SentryAutoDateProvider.java +│   │   │   ├── [ 14K May 27 19:34] SentryBaseEvent.java +│   │   │   ├── [ 51K May 27 19:34] SentryClient.java +│   │   │   ├── [2.5K Feb 18 16:11] SentryCrashLastRunState.java +│   │   │   ├── [1.6K Feb 18 16:11] SentryDate.java +│   │   │   ├── [ 145 Feb 18 16:11] SentryDateProvider.java +│   │   │   ├── [3.3K Feb 18 16:11] SentryEnvelope.java +│   │   │   ├── [5.1K Feb 18 16:11] SentryEnvelopeHeader.java +│   │   │   ├── [ 23K May 27 19:34] SentryEnvelopeItem.java +│   │   │   ├── [7.3K May 27 19:34] SentryEnvelopeItemHeader.java +│   │   │   ├── [ 12K Feb 18 16:11] SentryEvent.java +│   │   │   ├── [7.8K Feb 18 16:11] SentryExceptionFactory.java +│   │   │   ├── [2.4K Feb 18 16:11] SentryExecutorService.java +│   │   │   ├── [ 671 Feb 18 16:11] SentryInstantDate.java +│   │   │   ├── [ 244 Feb 18 16:11] SentryInstantDateProvider.java +│   │   │   ├── [4.3K May 27 19:34] SentryIntegrationPackageStorage.java +│   │   │   ├── [2.1K May 27 19:34] SentryItemType.java +│   │   │   ├── [ 743 Feb 18 16:11] SentryLevel.java +│   │   │   ├── [5.1K Feb 18 16:11] SentryLockReason.java +│   │   │   ├── [6.6K May 27 19:34] SentryLogEvent.java +│   │   │   ├── [3.2K May 27 19:34] SentryLogEventAttributeValue.java +│   │   │   ├── [2.6K May 27 19:34] SentryLogEvents.java +│   │   │   ├── [ 990 May 27 19:34] SentryLogLevel.java +│   │   │   ├── [ 245 Feb 18 16:11] SentryLongDate.java +│   │   │   ├── [2.8K Feb 18 16:11] SentryNanotimeDate.java +│   │   │   ├── [ 247 Feb 18 16:11] SentryNanotimeDateProvider.java +│   │   │   ├── [ 864 Feb 18 16:11] SentryOpenTelemetryMode.java +│   │   │   ├── [103K May 27 19:34] SentryOptions.java +│   │   │   ├── [9.2K Feb 18 16:11] SentryReplayEvent.java +│   │   │   ├── [ 11K May 27 19:34] SentryReplayOptions.java +│   │   │   ├── [1.6K Feb 18 16:11] SentryRuntimeEventProcessor.java +│   │   │   ├── [1.5K Feb 18 16:11] SentrySpanStorage.java +│   │   │   ├── [4.8K Feb 18 16:11] SentryStackTraceFactory.java +│   │   │   ├── [5.5K Feb 18 16:11] SentryThreadFactory.java +│   │   │   ├── [1.7K Feb 18 16:11] SentryTraceHeader.java +│   │   │   ├── [ 32K May 27 19:34] SentryTracer.java +│   │   │   ├── [ 666 Feb 18 16:11] SentryUUID.java +│   │   │   ├── [ 537 Feb 18 16:11] SentryValues.java +│   │   │   ├── [2.6K May 27 19:34] SentryWrapper.java +│   │   │   ├── [ 16K Feb 18 16:11] Session.java +│   │   │   ├── [2.3K Feb 18 16:11] ShutdownHookIntegration.java +│   │   │   ├── [ 13K May 27 19:34] Span.java +│   │   │   ├── [ 14K May 27 19:34] SpanContext.java +│   │   │   ├── [1.2K May 27 19:34] SpanDataConvention.java +│   │   │   ├── [1.4K Feb 18 16:11] SpanFactoryFactory.java +│   │   │   ├── [ 314 Feb 18 16:11] SpanFinishedCallback.java +│   │   │   ├── [1.6K Feb 18 16:11] SpanId.java +│   │   │   ├── [2.3K Feb 18 16:11] SpanOptions.java +│   │   │   ├── [4.3K Feb 18 16:11] SpanStatus.java +│   │   │   ├── [4.9K May 27 19:34] SpotlightIntegration.java +│   │   │   ├── [2.7K Feb 18 16:11] Stack.java +│   │   │   ├── [6.8K Feb 18 16:11] SynchronizedCollection.java +│   │   │   ├── [4.4K Feb 18 16:11] SynchronizedQueue.java +│   │   │   ├── [2.6K Feb 18 16:11] SystemOutLogger.java +│   │   │   ├── [8.5K Feb 18 16:11] TraceContext.java +│   │   │   ├── [3.4K May 27 19:34] TracesSampler.java +│   │   │   ├── [2.0K Feb 18 16:11] TracesSamplingDecision.java +│   │   │   ├── [5.7K May 27 19:34] TransactionContext.java +│   │   │   ├── [ 332 Feb 18 16:11] TransactionFinishedCallback.java +│   │   │   ├── [5.4K Feb 18 16:11] TransactionOptions.java +│   │   │   ├── [5.3K Feb 18 16:11] TypeCheckHint.java +│   │   │   ├── [ 930 Feb 18 16:11] UncaughtExceptionHandler.java +│   │   │   ├── [7.1K Feb 18 16:11] UncaughtExceptionHandlerIntegration.java +│   │   │   ├── [5.5K Feb 18 16:11] UserFeedback.java +│   │   │   ├── [ 160 Feb 18 16:11] backpressure +│   │   │   │   ├── [2.6K Feb 18 16:11] BackpressureMonitor.java +│   │   │   │   ├── [ 203 Feb 18 16:11] IBackpressureMonitor.java +│   │   │   │   └── [ 510 Feb 18 16:11] NoOpBackpressureMonitor.java +│   │   │   ├── [ 288 May 27 19:34] cache +│   │   │   │   ├── [9.2K Feb 18 16:11] CacheStrategy.java +│   │   │   │   ├── [3.6K May 27 19:34] CacheUtils.java +│   │   │   │   ├── [ 16K Feb 18 16:11] EnvelopeCache.java +│   │   │   │   ├── [ 409 Feb 18 16:11] IEnvelopeCache.java +│   │   │   │   ├── [3.2K Feb 18 16:11] PersistingOptionsObserver.java +│   │   │   │   ├── [ 10K May 27 19:34] PersistingScopeObserver.java +│   │   │   │   └── [ 192 May 27 19:34] tape +│   │   │   │   ├── [1021 May 27 19:34] EmptyObjectQueue.java +│   │   │   │   ├── [3.9K May 27 19:34] FileObjectQueue.java +│   │   │   │   ├── [3.5K May 27 19:34] ObjectQueue.java +│   │   │   │   └── [ 26K May 27 19:34] QueueFile.java +│   │   │   ├── [ 352 May 27 19:34] clientreport +│   │   │   │   ├── [2.0K Feb 18 16:11] AtomicClientReportStorage.java +│   │   │   │   ├── [3.8K Feb 18 16:11] ClientReport.java +│   │   │   │   ├── [ 949 Feb 18 16:11] ClientReportKey.java +│   │   │   │   ├── [6.2K May 27 19:34] ClientReportRecorder.java +│   │   │   │   ├── [ 587 Feb 18 16:11] DiscardReason.java +│   │   │   │   ├── [4.1K Feb 18 16:11] DiscardedEvent.java +│   │   │   │   ├── [ 808 Feb 18 16:11] IClientReportRecorder.java +│   │   │   │   ├── [ 258 Feb 18 16:11] IClientReportStorage.java +│   │   │   │   └── [1.0K Feb 18 16:11] NoOpClientReportRecorder.java +│   │   │   ├── [ 384 May 27 19:34] config +│   │   │   │   ├── [2.2K Feb 18 16:11] AbstractPropertiesProvider.java +│   │   │   │   ├── [1.7K Feb 18 16:11] ClasspathPropertiesLoader.java +│   │   │   │   ├── [1.2K Feb 18 16:11] CompositePropertiesProvider.java +│   │   │   │   ├── [2.0K Feb 18 16:11] EnvironmentVariablePropertiesProvider.java +│   │   │   │   ├── [1.2K Feb 18 16:11] FilesystemPropertiesLoader.java +│   │   │   │   ├── [ 288 Feb 18 16:11] PropertiesLoader.java +│   │   │   │   ├── [3.2K May 27 19:34] PropertiesProvider.java +│   │   │   │   ├── [2.5K Feb 18 16:11] PropertiesProviderFactory.java +│   │   │   │   ├── [ 364 Feb 18 16:11] SimplePropertiesProvider.java +│   │   │   │   └── [ 362 Feb 18 16:11] SystemPropertyPropertiesProvider.java +│   │   │   ├── [ 192 Feb 18 16:11] exception +│   │   │   │   ├── [2.5K Feb 18 16:11] ExceptionMechanismException.java +│   │   │   │   ├── [ 970 Feb 18 16:11] InvalidSentryTraceHeaderException.java +│   │   │   │   ├── [ 542 Feb 18 16:11] SentryEnvelopeException.java +│   │   │   │   └── [ 425 Feb 18 16:11] SentryHttpClientException.java +│   │   │   ├── [ 608 Feb 18 16:11] hints +│   │   │   │   ├── [ 487 Feb 18 16:11] AbnormalExit.java +│   │   │   │   ├── [ 224 Feb 18 16:11] ApplyScopeData.java +│   │   │   │   ├── [ 253 Feb 18 16:11] Backfillable.java +│   │   │   │   ├── [1.1K Feb 18 16:11] BlockingFlushHint.java +│   │   │   │   ├── [ 123 Feb 18 16:11] Cached.java +│   │   │   │   ├── [ 314 Feb 18 16:11] DiskFlushNotification.java +│   │   │   │   ├── [ 180 Feb 18 16:11] Enqueable.java +│   │   │   │   ├── [ 246 Feb 18 16:11] EventDropReason.java +│   │   │   │   ├── [ 138 Feb 18 16:11] Flushable.java +│   │   │   │   ├── [ 163 Feb 18 16:11] Resettable.java +│   │   │   │   ├── [ 111 Feb 18 16:11] Retryable.java +│   │   │   │   ├── [ 111 Feb 18 16:11] SessionEnd.java +│   │   │   │   ├── [ 85 Feb 18 16:11] SessionEndHint.java +│   │   │   │   ├── [ 115 Feb 18 16:11] SessionStart.java +│   │   │   │   ├── [ 89 Feb 18 16:11] SessionStartHint.java +│   │   │   │   ├── [ 123 Feb 18 16:11] SubmissionResult.java +│   │   │   │   └── [ 159 Feb 18 16:11] TransactionEnd.java +│   │   │   ├── [ 96 Feb 18 16:11] instrumentation +│   │   │   │   └── [ 288 May 27 19:34] file +│   │   │   │   ├── [4.8K Feb 18 16:11] FileIOSpanManager.java +│   │   │   │   ├── [ 713 Feb 18 16:11] FileInputStreamInitData.java +│   │   │   │   ├── [ 796 Feb 18 16:11] FileOutputStreamInitData.java +│   │   │   │   ├── [5.8K May 27 19:34] SentryFileInputStream.java +│   │   │   │   ├── [7.0K May 27 19:34] SentryFileOutputStream.java +│   │   │   │   ├── [ 852 Feb 18 16:11] SentryFileReader.java +│   │   │   │   └── [1.2K Feb 18 16:11] SentryFileWriter.java +│   │   │   ├── [ 256 May 27 19:34] internal +│   │   │   │   ├── [4.7K May 27 19:34] ManifestVersionReader.java +│   │   │   │   ├── [ 160 Feb 18 16:11] debugmeta +│   │   │   │   │   ├── [ 285 Feb 18 16:11] IDebugMetaLoader.java +│   │   │   │   │   ├── [ 554 Feb 18 16:11] NoOpDebugMetaLoader.java +│   │   │   │   │   └── [2.2K Feb 18 16:11] ResourcesDebugMetaLoader.java +│   │   │   │   ├── [ 96 Feb 18 16:11] eventprocessor +│   │   │   │   │   └── [ 879 Feb 18 16:11] EventProcessorAndOrder.java +│   │   │   │   ├── [ 128 Feb 18 16:11] gestures +│   │   │   │   │   ├── [ 256 Feb 18 16:11] GestureTargetLocator.java +│   │   │   │   │   └── [1.8K Feb 18 16:11] UiElement.java +│   │   │   │   ├── [ 256 May 27 19:34] modules +│   │   │   │   │   ├── [ 962 Feb 18 16:11] CompositeModulesLoader.java +│   │   │   │   │   ├── [ 257 Feb 18 16:11] IModulesLoader.java +│   │   │   │   │   ├── [3.2K Feb 18 16:11] ManifestModulesLoader.java +│   │   │   │   │   ├── [2.3K May 27 19:34] ModulesLoader.java +│   │   │   │   │   ├── [ 452 Feb 18 16:11] NoOpModulesLoader.java +│   │   │   │   │   └── [1.5K Feb 18 16:11] ResourcesModulesLoader.java +│   │   │   │   └── [ 96 Feb 18 16:11] viewhierarchy +│   │   │   │   └── [ 565 Feb 18 16:11] ViewHierarchyExporter.java +│   │   │   ├── [ 288 May 27 19:34] logger +│   │   │   │   ├── [1.1K May 27 19:34] ILoggerApi.java +│   │   │   │   ├── [ 226 May 27 19:34] ILoggerBatchProcessor.java +│   │   │   │   ├── [8.3K May 27 19:34] LoggerApi.java +│   │   │   │   ├── [3.5K May 27 19:34] LoggerBatchProcessor.java +│   │   │   │   ├── [1.6K May 27 19:34] NoOpLoggerApi.java +│   │   │   │   ├── [ 639 May 27 19:34] NoOpLoggerBatchProcessor.java +│   │   │   │   └── [1.1K May 27 19:34] SentryLogParameters.java +│   │   │   ├── [ 96 Feb 18 16:11] opentelemetry +│   │   │   │   └── [2.6K Feb 18 16:11] OpenTelemetryUtil.java +│   │   │   ├── [ 128 May 27 19:34] profilemeasurements +│   │   │   │   ├── [4.7K Feb 18 16:11] ProfileMeasurement.java +│   │   │   │   └── [5.0K May 27 19:34] ProfileMeasurementValue.java +│   │   │   ├── [1.1K May 27 19:34] protocol +│   │   │   │   ├── [ 12K Feb 18 16:11] App.java +│   │   │   │   ├── [3.6K Feb 18 16:11] Browser.java +│   │   │   │   ├── [ 11K May 27 19:34] Contexts.java +│   │   │   │   ├── [ 12K Feb 18 16:11] DebugImage.java +│   │   │   │   ├── [4.8K May 27 19:34] DebugMeta.java +│   │   │   │   ├── [ 26K Feb 18 16:11] Device.java +│   │   │   │   ├── [6.9K May 27 19:34] Feedback.java +│   │   │   │   ├── [5.0K Feb 18 16:11] Geo.java +│   │   │   │   ├── [7.9K Feb 18 16:11] Gpu.java +│   │   │   │   ├── [4.1K Feb 18 16:11] MeasurementValue.java +│   │   │   │   ├── [9.4K Feb 18 16:11] Mechanism.java +│   │   │   │   ├── [4.9K Feb 18 16:11] Message.java +│   │   │   │   ├── [ 1 Feb 18 16:11] MetricSummary.java +│   │   │   │   ├── [6.5K Feb 18 16:11] OperatingSystem.java +│   │   │   │   ├── [ 12K Feb 18 16:11] Request.java +│   │   │   │   ├── [5.6K Feb 18 16:11] Response.java +│   │   │   │   ├── [4.4K Feb 18 16:11] SdkInfo.java +│   │   │   │   ├── [8.6K Feb 18 16:11] SdkVersion.java +│   │   │   │   ├── [7.2K Feb 18 16:11] SentryException.java +│   │   │   │   ├── [2.7K Feb 18 16:11] SentryId.java +│   │   │   │   ├── [4.2K Feb 18 16:11] SentryPackage.java +│   │   │   │   ├── [4.1K Feb 18 16:11] SentryRuntime.java +│   │   │   │   ├── [ 11K Feb 18 16:11] SentrySpan.java +│   │   │   │   ├── [ 15K May 27 19:34] SentryStackFrame.java +│   │   │   │   ├── [5.9K Feb 18 16:11] SentryStackTrace.java +│   │   │   │   ├── [9.6K Feb 18 16:11] SentryThread.java +│   │   │   │   ├── [ 11K May 27 19:34] SentryTransaction.java +│   │   │   │   ├── [3.5K May 27 19:34] Spring.java +│   │   │   │   ├── [2.5K Feb 18 16:11] TransactionInfo.java +│   │   │   │   ├── [1.3K Feb 18 16:11] TransactionNameSource.java +│   │   │   │   ├── [ 11K Feb 18 16:11] User.java +│   │   │   │   ├── [3.1K Feb 18 16:11] ViewHierarchy.java +│   │   │   │   └── [6.6K Feb 18 16:11] ViewHierarchyNode.java +│   │   │   ├── [ 384 Feb 18 16:11] rrweb +│   │   │   │   ├── [9.4K Feb 18 16:11] RRWebBreadcrumbEvent.java +│   │   │   │   ├── [2.4K Feb 18 16:11] RRWebEvent.java +│   │   │   │   ├── [ 883 Feb 18 16:11] RRWebEventType.java +│   │   │   │   ├── [2.5K Feb 18 16:11] RRWebIncrementalSnapshotEvent.java +│   │   │   │   ├── [7.4K Feb 18 16:11] RRWebInteractionEvent.java +│   │   │   │   ├── [8.5K Feb 18 16:11] RRWebInteractionMoveEvent.java +│   │   │   │   ├── [5.5K Feb 18 16:11] RRWebMetaEvent.java +│   │   │   │   ├── [7.3K Feb 18 16:11] RRWebOptionsEvent.java +│   │   │   │   ├── [8.6K Feb 18 16:11] RRWebSpanEvent.java +│   │   │   │   └── [ 13K Feb 18 16:11] RRWebVideoEvent.java +│   │   │   ├── [ 576 May 27 19:34] transport +│   │   │   │   ├── [ 13K Feb 18 16:11] AsyncHttpTransport.java +│   │   │   │   ├── [ 543 Feb 18 16:11] AuthenticatorWrapper.java +│   │   │   │   ├── [ 469 Feb 18 16:11] CurrentDateProvider.java +│   │   │   │   ├── [9.6K May 27 19:34] HttpConnection.java +│   │   │   │   ├── [ 315 Feb 18 16:11] ICurrentDateProvider.java +│   │   │   │   ├── [1009 May 27 19:34] ITransport.java +│   │   │   │   ├── [ 607 Feb 18 16:11] ITransportGate.java +│   │   │   │   ├── [ 722 Feb 18 16:11] NoOpEnvelopeCache.java +│   │   │   │   ├── [ 910 Feb 18 16:11] NoOpTransport.java +│   │   │   │   ├── [ 352 Feb 18 16:11] NoOpTransportGate.java +│   │   │   │   ├── [1.0K Feb 18 16:11] ProxyAuthenticator.java +│   │   │   │   ├── [4.2K Feb 18 16:11] QueuedThreadPoolExecutor.java +│   │   │   │   ├── [ 13K May 27 19:34] RateLimiter.java +│   │   │   │   ├── [7.9K Feb 18 16:11] ReusableCountLatch.java +│   │   │   │   ├── [1.2K Feb 18 16:11] StdoutTransport.java +│   │   │   │   └── [1.9K Feb 18 16:11] TransportResult.java +│   │   │   ├── [1.2K May 27 19:34] util +│   │   │   │   ├── [ 750 Feb 18 16:11] AutoClosableReentrantLock.java +│   │   │   │   ├── [4.8K Feb 18 16:11] CheckInUtils.java +│   │   │   │   ├── [ 747 May 27 19:34] ClassLoaderUtils.java +│   │   │   │   ├── [6.1K May 27 19:34] CollectionUtils.java +│   │   │   │   ├── [2.0K Feb 18 16:11] DebugMetaPropertiesApplier.java +│   │   │   │   ├── [1.7K Feb 18 16:11] ErrorUtils.java +│   │   │   │   ├── [ 775 Feb 18 16:11] EventProcessorUtils.java +│   │   │   │   ├── [1003 Feb 18 16:11] ExceptionUtils.java +│   │   │   │   ├── [3.9K Feb 18 16:11] FileUtils.java +│   │   │   │   ├── [4.3K Feb 18 16:11] HintUtils.java +│   │   │   │   ├── [4.4K Feb 18 16:11] HttpUtils.java +│   │   │   │   ├── [1.7K May 27 19:34] InitUtil.java +│   │   │   │   ├── [ 377 Feb 18 16:11] IntegrationUtils.java +│   │   │   │   ├── [2.2K Feb 18 16:11] JsonSerializationUtils.java +│   │   │   │   ├── [1.8K Feb 18 16:11] LazyEvaluator.java +│   │   │   │   ├── [ 454 Feb 18 16:11] LifecycleHelper.java +│   │   │   │   ├── [1.5K May 27 19:34] LoadClass.java +│   │   │   │   ├── [ 631 Feb 18 16:11] LogUtils.java +│   │   │   │   ├── [ 11K Feb 18 16:11] MapObjectReader.java +│   │   │   │   ├── [8.0K May 27 19:34] MapObjectWriter.java +│   │   │   │   ├── [ 659 Feb 18 16:11] Objects.java +│   │   │   │   ├── [ 476 Feb 18 16:11] Pair.java +│   │   │   │   ├── [1.3K Feb 18 16:11] Platform.java +│   │   │   │   ├── [ 849 Feb 18 16:11] PropagationTargetsUtils.java +│   │   │   │   ├── [ 18K Feb 18 16:11] Random.java +│   │   │   │   ├── [2.3K May 27 19:34] SampleRateUtils.java +│   │   │   │   ├── [2.2K May 27 19:34] ScopesUtil.java +│   │   │   │   ├── [1.2K Feb 18 16:11] SentryRandom.java +│   │   │   │   ├── [2.6K Feb 18 16:11] SpanUtils.java +│   │   │   │   ├── [7.5K May 27 19:34] StringUtils.java +│   │   │   │   ├── [7.8K May 27 19:34] TracingUtils.java +│   │   │   │   ├── [2.5K Feb 18 16:11] UUIDGenerator.java +│   │   │   │   ├── [6.3K Feb 18 16:11] UUIDStringUtils.java +│   │   │   │   ├── [3.4K May 27 19:34] UrlUtils.java +│   │   │   │   └── [ 160 May 27 19:34] thread +│   │   │   │   ├── [1.2K May 27 19:34] IThreadChecker.java +│   │   │   │   ├── [ 898 May 27 19:34] NoOpThreadChecker.java +│   │   │   │   └── [1.5K May 27 19:34] ThreadChecker.java +│   │   │   └── [ 128 Feb 18 16:11] vendor +│   │   │   ├── [ 24K Feb 18 16:11] Base64.java +│   │   │   └── [ 160 Feb 18 16:11] gson +│   │   │   ├── [ 11K Feb 18 16:11] LICENSE +│   │   │   ├── [ 96 Feb 18 16:11] internal +│   │   │   │   └── [ 96 Feb 18 16:11] bind +│   │   │   │   └── [ 96 Feb 18 16:11] util +│   │   │   │   └── [ 14K Feb 18 16:11] ISO8601Utils.java +│   │   │   └── [ 224 May 27 19:34] stream +│   │   │   ├── [ 49K Feb 18 16:11] JsonReader.java +│   │   │   ├── [2.1K Feb 18 16:11] JsonScope.java +│   │   │   ├── [2.2K Feb 18 16:11] JsonToken.java +│   │   │   ├── [ 19K May 27 19:34] JsonWriter.java +│   │   │   └── [1.7K Feb 18 16:11] MalformedJsonException.java +│   │   └── [ 96 Feb 18 16:11] resources +│   │   └── [ 96 Feb 18 16:11] META-INF +│   │   └── [ 96 Feb 18 16:11] native-image +│   │   └── [ 96 Feb 18 16:11] io.sentry +│   │   └── [ 96 Feb 18 16:11] sentry +│   │   └── [ 36 Feb 18 16:11] native-image.properties +│   └── [ 128 Feb 18 16:11] test +│   ├── [ 96 Feb 18 16:11] java +│   │   └── [ 96 Feb 18 16:11] io +│   │   └── [3.6K May 27 19:34] sentry +│   │   ├── [4.1K Feb 18 16:11] AttachmentTest.kt +│   │   ├── [ 31K May 27 19:34] BaggageTest.kt +│   │   ├── [ 11K May 27 19:34] BreadcrumbTest.kt +│   │   ├── [ 77 Feb 18 16:11] CachedEvent.kt +│   │   ├── [6.7K May 27 19:34] CheckInSerializationTest.kt +│   │   ├── [ 21K May 27 19:34] CombinedContextsViewTest.kt +│   │   ├── [ 46K May 27 19:34] CombinedScopeViewTest.kt +│   │   ├── [ 150 Feb 18 16:11] CustomCachedApplyScopeDataHint.kt +│   │   ├── [ 145 Feb 18 16:11] CustomEventProcessor.kt +│   │   ├── [4.2K Feb 18 16:11] DateUtilsTest.kt +│   │   ├── [4.8K Feb 18 16:11] DeduplicateMultithreadedEventProcessorTest.kt +│   │   ├── [ 15K May 27 19:34] DefaultCompositePerformanceCollectorTest.kt +│   │   ├── [ 843 Feb 18 16:11] DenyReadFileSecurityManager.java +│   │   ├── [6.5K Feb 18 16:11] DiagnosticLoggerTest.kt +│   │   ├── [5.3K Feb 18 16:11] DirectoryProcessorTest.kt +│   │   ├── [2.8K Feb 18 16:11] DisabledQueueTest.kt +│   │   ├── [3.3K Feb 18 16:11] DsnTest.kt +│   │   ├── [1.7K May 27 19:34] DsnUtilTest.kt +│   │   ├── [3.4K Feb 18 16:11] DuplicateEventDetectionEventProcessorTest.kt +│   │   ├── [4.9K Feb 18 16:11] EnvelopeSenderTest.kt +│   │   ├── [ 14K May 27 19:34] ExternalOptionsTest.kt +│   │   ├── [ 737 Feb 18 16:11] FileFromResources.kt +│   │   ├── [ 705 May 27 19:34] FilterStringTest.kt +│   │   ├── [1.6K Feb 18 16:11] FullyDisplayedReporterTest.kt +│   │   ├── [1.3K Feb 18 16:11] HttpStatusCodeRangeTest.kt +│   │   ├── [9.6K May 27 19:34] HubAdapterTest.kt +│   │   ├── [ 447 Feb 18 16:11] InstrumenterTest.kt +│   │   ├── [ 540 Feb 18 16:11] IpAddressUtilsTest.kt +│   │   ├── [ 850 May 27 19:34] JavaMemoryCollectorTest.kt +│   │   ├── [5.8K Feb 18 16:11] JsonObjectDeserializerTest.kt +│   │   ├── [8.8K Feb 18 16:11] JsonObjectReaderTest.kt +│   │   ├── [9.4K Feb 18 16:11] JsonObjectSerializerTest.kt +│   │   ├── [ 12K Feb 18 16:11] JsonReflectionObjectSerializerTest.kt +│   │   ├── [3.1K Feb 18 16:11] JsonSerializerBenchmarkTests.kt +│   │   ├── [ 71K May 27 19:34] JsonSerializerTest.kt +│   │   ├── [8.5K Feb 18 16:11] JsonUnknownSerializationTest.kt +│   │   ├── [ 22K Feb 18 16:11] MainEventProcessorTest.kt +│   │   ├── [2.3K Feb 18 16:11] MeasurementUnitTest.kt +│   │   ├── [ 930 Feb 18 16:11] NoOpConnectionStatusProviderTest.kt +│   │   ├── [ 852 May 27 19:34] NoOpContinuousProfilerTest.kt +│   │   ├── [3.4K May 27 19:34] NoOpHubTest.kt +│   │   ├── [2.8K Feb 18 16:11] NoOpScopeTest.kt +│   │   ├── [2.7K May 27 19:34] NoOpSentryClientTest.kt +│   │   ├── [ 870 Feb 18 16:11] NoOpSentryExecutorServiceTest.kt +│   │   ├── [ 349 Feb 18 16:11] NoOpSerializerTest.kt +│   │   ├── [ 948 Feb 18 16:11] NoOpSpanTest.kt +│   │   ├── [ 736 Feb 18 16:11] NoOpTransactionProfilerTest.kt +│   │   ├── [1.5K Feb 18 16:11] NoOpTransactionTest.kt +│   │   ├── [ 321 Feb 18 16:11] OptionsContainerTest.kt +│   │   ├── [ 16K May 27 19:34] OutboxSenderTest.kt +│   │   ├── [1.7K May 27 19:34] PerformanceCollectionDataTest.kt +│   │   ├── [6.8K Feb 18 16:11] PreviousSessionFinalizerTest.kt +│   │   ├── [1.4K Feb 18 16:11] PropagationContextTest.kt +│   │   ├── [1.8K Feb 18 16:11] RequestDetailsResolverTest.kt +│   │   ├── [ 335 Feb 18 16:11] SampleDsn.kt +│   │   ├── [ 36K May 27 19:34] ScopeTest.kt +│   │   ├── [9.9K May 27 19:34] ScopesAdapterTest.kt +│   │   ├── [102K May 27 19:34] ScopesTest.kt +│   │   ├── [8.8K Feb 18 16:11] SendCachedEnvelopeFireAndForgetIntegrationTest.kt +│   │   ├── [ 817 Feb 18 16:11] SentryAutoDateProviderTest.kt +│   │   ├── [ 382 Feb 18 16:11] SentryBaseEventTypeTest.kt +│   │   ├── [129K May 27 19:34] SentryClientTest.kt +│   │   ├── [2.1K Feb 18 16:11] SentryCrashLastRunStateTest.kt +│   │   ├── [ 24K May 27 19:34] SentryEnvelopeItemTest.kt +│   │   ├── [7.4K Feb 18 16:11] SentryEnvelopeTest.kt +│   │   ├── [6.8K May 27 19:34] SentryEventTest.kt +│   │   ├── [ 17K Feb 18 16:11] SentryExceptionFactoryTest.kt +│   │   ├── [3.9K Feb 18 16:11] SentryExecutorServiceTest.kt +│   │   ├── [2.1K Feb 18 16:11] SentryInstantDateTest.kt +│   │   ├── [2.6K May 27 19:34] SentryIntegrationPackageStorageTest.kt +│   │   ├── [3.0K Feb 18 16:11] SentryLongDateTest.kt +│   │   ├── [2.5K Feb 18 16:11] SentryNanotimeDateTest.kt +│   │   ├── [ 318 Feb 18 16:11] SentryOptionsManipulator.kt +│   │   ├── [ 29K May 27 19:34] SentryOptionsTest.kt +│   │   ├── [1.4K Feb 18 16:11] SentryOptionsTracingTest.kt +│   │   ├── [1.0K Feb 18 16:11] SentryReplayOptionsTest.kt +│   │   ├── [2.1K Feb 18 16:11] SentryRuntimeEventProcessorTest.kt +│   │   ├── [ 12K Feb 18 16:11] SentryStackTraceFactoryTest.kt +│   │   ├── [ 52K May 27 19:34] SentryTest.kt +│   │   ├── [3.6K Feb 18 16:11] SentryThreadFactoryTest.kt +│   │   ├── [2.7K Feb 18 16:11] SentryTraceHeaderTest.kt +│   │   ├── [ 55K May 27 19:34] SentryTracerTest.kt +│   │   ├── [ 444 Feb 18 16:11] SentryUUIDTest.kt +│   │   ├── [ 750 Feb 18 16:11] SentryValuesTest.kt +│   │   ├── [8.8K May 27 19:34] SentryWrapperTest.kt +│   │   ├── [ 20K Feb 18 16:11] SessionAdapterTest.kt +│   │   ├── [3.6K Feb 18 16:11] ShutdownHookIntegrationTest.kt +│   │   ├── [1.7K May 27 19:34] SpanContextTest.kt +│   │   ├── [1.3K Feb 18 16:11] SpanStatusTest.kt +│   │   ├── [ 18K May 27 19:34] SpanTest.kt +│   │   ├── [2.7K Feb 18 16:11] StackTest.kt +│   │   ├── [ 175 Feb 18 16:11] StringExtensions.kt +│   │   ├── [2.9K Feb 18 16:11] TraceContextSerializationTest.kt +│   │   ├── [1.4K Feb 18 16:11] TracePropagationTargetsTest.kt +│   │   ├── [ 20K May 27 19:34] TracesSamplerTest.kt +│   │   ├── [5.0K May 27 19:34] TransactionContextTest.kt +│   │   ├── [ 1 Feb 18 16:11] TransactionContextsTest.kt +│   │   ├── [ 708 Feb 18 16:11] UUIDStringUtilsTest.kt +│   │   ├── [ 13K Feb 18 16:11] UncaughtExceptionHandlerIntegrationTest.kt +│   │   ├── [2.6K Feb 18 16:11] UrlDetailsTest.kt +│   │   ├── [2.6K Feb 18 16:11] UserFeedbackSerializationTest.kt +│   │   ├── [ 96 Feb 18 16:11] backpressure +│   │   │   └── [2.8K Feb 18 16:11] BackpressureMonitorTest.kt +│   │   ├── [ 256 May 27 19:34] cache +│   │   │   ├── [6.5K Feb 18 16:11] CacheStrategyTest.kt +│   │   │   ├── [2.4K May 27 19:34] CacheUtilsTest.kt +│   │   │   ├── [ 12K Feb 18 16:11] EnvelopeCacheTest.kt +│   │   │   ├── [5.2K Feb 18 16:11] PersistingOptionsObserverTest.kt +│   │   │   ├── [ 11K May 27 19:34] PersistingScopeObserverTest.kt +│   │   │   └── [ 160 May 27 19:34] tape +│   │   │   ├── [1.1K May 27 19:34] CorruptQueueFileTest.kt +│   │   │   ├── [6.2K May 27 19:34] ObjectQueueTest.kt +│   │   │   └── [ 20K May 27 19:34] QueueFileTest.kt +│   │   ├── [ 160 May 27 19:34] clientreport +│   │   │   ├── [3.4K Feb 18 16:11] AtomicClientReportStorageTest.kt +│   │   │   ├── [6.8K Feb 18 16:11] ClientReportMultiThreadingTest.kt +│   │   │   └── [ 14K May 27 19:34] ClientReportTest.kt +│   │   ├── [ 288 Feb 18 16:11] config +│   │   │   ├── [2.0K Feb 18 16:11] ClasspathPropertiesLoaderTest.kt +│   │   │   ├── [2.0K Feb 18 16:11] CompositePropertiesProviderTest.kt +│   │   │   ├── [1.2K Feb 18 16:11] EnvironmentVariablePropertiesProviderTest.kt +│   │   │   ├── [1.3K Feb 18 16:11] FilesystemPropertiesLoaderTest.kt +│   │   │   ├── [1.7K Feb 18 16:11] PropertiesProviderTest.kt +│   │   │   ├── [1.1K Feb 18 16:11] SimplePropertiesProviderTest.kt +│   │   │   └── [1.0K Feb 18 16:11] SystemPropertyPropertiesProviderTest.kt +│   │   ├── [ 96 Feb 18 16:11] hints +│   │   │   └── [7.1K Feb 18 16:11] HintTest.kt +│   │   ├── [ 96 Feb 18 16:11] instrumentation +│   │   │   └── [ 224 May 27 19:34] file +│   │   │   ├── [1.2K Feb 18 16:11] FileIOSpanManagerTest.kt +│   │   │   ├── [ 10K May 27 19:34] SentryFileInputStreamTest.kt +│   │   │   ├── [8.5K May 27 19:34] SentryFileOutputStreamTest.kt +│   │   │   ├── [3.6K Feb 18 16:11] SentryFileReaderTest.kt +│   │   │   └── [4.0K Feb 18 16:11] SentryFileWriterTest.kt +│   │   ├── [ 160 Feb 18 16:11] internal +│   │   │   ├── [2.5K Feb 18 16:11] SpotlightIntegrationTest.kt +│   │   │   ├── [ 96 Feb 18 16:11] debugmeta +│   │   │   │   └── [4.8K Feb 18 16:11] ResourcesDebugMetaLoaderTest.kt +│   │   │   └── [ 160 Feb 18 16:11] modules +│   │   │   ├── [1.2K Feb 18 16:11] CompositeModulesLoaderTest.kt +│   │   │   ├── [3.1K Feb 18 16:11] ManifestModulesLoaderTest.kt +│   │   │   └── [2.5K Feb 18 16:11] ResourcesModulesLoaderTest.kt +│   │   ├── [1.9K May 27 19:34] protocol +│   │   │   ├── [2.3K Feb 18 16:11] AppSerializationTest.kt +│   │   │   ├── [2.5K Feb 18 16:11] AppTest.kt +│   │   │   ├── [3.8K Feb 18 16:11] BreadcrumbSerializationTest.kt +│   │   │   ├── [1.7K Feb 18 16:11] BrowserSerializationTest.kt +│   │   │   ├── [1.1K Feb 18 16:11] BrowserTest.kt +│   │   │   ├── [3.0K May 27 19:34] CombinedContextsViewSerializationTest.kt +│   │   │   ├── [2.6K May 27 19:34] ContextsSerializationTest.kt +│   │   │   ├── [4.3K May 27 19:34] ContextsTest.kt +│   │   │   ├── [2.1K Feb 18 16:11] DebugImageSerializationTest.kt +│   │   │   ├── [2.5K Feb 18 16:11] DebugMetaSerializationTest.kt +│   │   │   ├── [3.2K May 27 19:34] DebugMetaTest.kt +│   │   │   ├── [3.6K Feb 18 16:11] DeviceSerializationTest.kt +│   │   │   ├── [3.9K Feb 18 16:11] DeviceTest.kt +│   │   │   ├── [1.9K May 27 19:34] FeedbackTest.kt +│   │   │   ├── [1.4K Feb 18 16:11] GpuSerializationTest.kt +│   │   │   ├── [1.5K Feb 18 16:11] GpuTest.kt +│   │   │   ├── [2.5K Feb 18 16:11] MeasurementValueSerializationTest.kt +│   │   │   ├── [2.1K Feb 18 16:11] MechanismSerializationTest.kt +│   │   │   ├── [1019 Feb 18 16:11] MechanismTest.kt +│   │   │   ├── [1.8K Feb 18 16:11] MessageSerializationTest.kt +│   │   │   ├── [ 484 Feb 18 16:11] MessageTest.kt +│   │   │   ├── [2.1K Feb 18 16:11] OperatingSystemSerializationTest.kt +│   │   │   ├── [1.5K Feb 18 16:11] OperatingSystemTest.kt +│   │   │   ├── [2.0K Feb 18 16:11] ReplayRecordingSerializationTest.kt +│   │   │   ├── [1.8K Feb 18 16:11] RequestSerializationTest.kt +│   │   │   ├── [4.3K Feb 18 16:11] RequestTest.kt +│   │   │   ├── [1.4K Feb 18 16:11] ResponseSerializationTest.kt +│   │   │   ├── [1.7K Feb 18 16:11] SdkInfoSerializationTest.kt +│   │   │   ├── [2.8K Feb 18 16:11] SdkVersionSerializationTest.kt +│   │   │   ├── [4.3K Feb 18 16:11] SentryBaseEventSerializationTest.kt +│   │   │   ├── [2.3K Feb 18 16:11] SentryEnvelopeHeaderSerializationTest.kt +│   │   │   ├── [2.0K May 27 19:34] SentryEnvelopeItemHeaderSerializationTest.kt +│   │   │   ├── [2.9K May 27 19:34] SentryEventSerializationTest.kt +│   │   │   ├── [3.7K Feb 18 16:11] SentryExceptionSerializationTest.kt +│   │   │   ├── [1.6K Feb 18 16:11] SentryIdSerializationTest.kt +│   │   │   ├── [3.2K Feb 18 16:11] SentryIdTest.kt +│   │   │   ├── [3.2K May 27 19:34] SentryItemTypeSerializationTest.kt +│   │   │   ├── [1.2K Feb 18 16:11] SentryLockReasonSerializationTest.kt +│   │   │   ├── [3.2K May 27 19:34] SentryLogsSerializationTest.kt +│   │   │   ├── [1.7K Feb 18 16:11] SentryPackageSerializationTest.kt +│   │   │   ├── [2.0K Feb 18 16:11] SentryReplayEventSerializationTest.kt +│   │   │   ├── [1.9K Feb 18 16:11] SentryRuntimeSerializationTest.kt +│   │   │   ├── [1.2K Feb 18 16:11] SentryRuntimeTest.kt +│   │   │   ├── [2.7K Feb 18 16:11] SentrySpanSerializationTest.kt +│   │   │   ├── [ 953 Feb 18 16:11] SentrySpanTest.kt +│   │   │   ├── [2.8K May 27 19:34] SentryStackFrameSerializationTest.kt +│   │   │   ├── [2.8K Feb 18 16:11] SentryStackTraceSerializationTest.kt +│   │   │   ├── [3.7K Feb 18 16:11] SentryThreadSerializationTest.kt +│   │   │   ├── [3.4K Feb 18 16:11] SentryTransactionSerializationTest.kt +│   │   │   ├── [1.2K Feb 18 16:11] SerializationUtils.kt +│   │   │   ├── [2.1K Feb 18 16:11] SessionSerializationTest.kt +│   │   │   ├── [3.3K May 27 19:34] SpanContextSerializationTest.kt +│   │   │   ├── [1.6K Feb 18 16:11] SpanIdSerializationTest.kt +│   │   │   ├── [1.1K Feb 18 16:11] SpanIdTest.kt +│   │   │   ├── [1.0K May 27 19:34] SpringSerializationTest.kt +│   │   │   ├── [4.5K Feb 18 16:11] UserSerializationTest.kt +│   │   │   ├── [2.7K Feb 18 16:11] UserTest.kt +│   │   │   ├── [2.1K Feb 18 16:11] ViewHierarchyNodeSerializationTest.kt +│   │   │   └── [1.8K Feb 18 16:11] ViewHierarchySerializationTest.kt +│   │   ├── [ 320 Feb 18 16:11] rrweb +│   │   │   ├── [1.4K Feb 18 16:11] RRWebBreadcrumbEventSerializationTest.kt +│   │   │   ├── [2.3K Feb 18 16:11] RRWebEventSerializationTest.kt +│   │   │   ├── [1.3K Feb 18 16:11] RRWebInteractionEventSerializationTest.kt +│   │   │   ├── [1.4K Feb 18 16:11] RRWebInteractionMoveEventSerializationTest.kt +│   │   │   ├── [1.2K Feb 18 16:11] RRWebMetaEventSerializationTest.kt +│   │   │   ├── [1.6K Feb 18 16:11] RRWebOptionsEventSerializationTest.kt +│   │   │   ├── [1.3K Feb 18 16:11] RRWebSpanEventSerializationTest.kt +│   │   │   └── [1.4K Feb 18 16:11] RRWebVideoEventSerializationTest.kt +│   │   ├── [ 288 May 27 19:34] transport +│   │   │   ├── [ 12K Feb 18 16:11] AsyncHttpTransportClientReportTest.kt +│   │   │   ├── [ 17K Feb 18 16:11] AsyncHttpTransportTest.kt +│   │   │   ├── [9.5K May 27 19:34] HttpConnectionTest.kt +│   │   │   ├── [5.5K Feb 18 16:11] QueuedThreadPoolExecutorTest.kt +│   │   │   ├── [ 20K May 27 19:34] RateLimiterTest.kt +│   │   │   ├── [4.6K Feb 18 16:11] ReusableCountLatchTest.kt +│   │   │   └── [ 814 Feb 18 16:11] StdoutTransportTest.kt +│   │   ├── [ 736 May 27 19:34] util +│   │   │   ├── [ 382 Feb 18 16:11] AutoClosableReentrantLockTest.kt +│   │   │   ├── [ 12K Feb 18 16:11] CheckInUtilsTest.kt +│   │   │   ├── [3.4K May 27 19:34] CollectionUtilsTest.kt +│   │   │   ├── [ 596 Feb 18 16:11] ExceptionUtilsTest.kt +│   │   │   ├── [ 172 Feb 18 16:11] Extensions.kt +│   │   │   ├── [2.4K Feb 18 16:11] FileUtilsTest.kt +│   │   │   ├── [1.4K Feb 18 16:11] HintUtilsTest.kt +│   │   │   ├── [3.0K Feb 18 16:11] HttpUtilsTest.kt +│   │   │   ├── [3.0K Feb 18 16:11] InitUtilTest.kt +│   │   │   ├── [2.4K Feb 18 16:11] JsonSerializationUtilsTest.kt +│   │   │   ├── [1.3K Feb 18 16:11] LazyEvaluatorTest.kt +│   │   │   ├── [6.5K Feb 18 16:11] MapObjectReaderTest.kt +│   │   │   ├── [5.6K Feb 18 16:11] MapObjectWriterTest.kt +│   │   │   ├── [ 315 Feb 18 16:11] PlatformTestManipulator.kt +│   │   │   ├── [6.8K May 27 19:34] SampleRateUtilTest.kt +│   │   │   ├── [1.3K Feb 18 16:11] SentryRandomTest.kt +│   │   │   ├── [3.1K Feb 18 16:11] SpanUtilsTest.kt +│   │   │   ├── [6.2K Feb 18 16:11] StringUtilsTest.kt +│   │   │   ├── [ 15K May 27 19:34] TracingUtilsTest.kt +│   │   │   ├── [ 12K May 27 19:34] UrlUtilsTest.kt +│   │   │   └── [ 96 May 27 19:34] thread +│   │   │   └── [1.6K May 27 19:34] ThreadCheckerTest.kt +│   │   └── [ 96 Feb 18 16:11] vendor +│   │   └── [ 128 Feb 18 16:11] gson +│   │   ├── [ 96 Feb 18 16:11] internal +│   │   │   └── [ 96 Feb 18 16:11] bind +│   │   │   └── [ 96 Feb 18 16:11] util +│   │   │   └── [3.8K Feb 18 16:11] ISO8601UtilsTest.java +│   │   └── [ 128 Feb 18 16:11] stream +│   │   ├── [ 55K Feb 18 16:11] JsonReaderTest.java +│   │   └── [ 20K Feb 18 16:11] JsonWriterTest.java +│   └── [ 608 May 27 19:34] resources +│   ├── [234K Feb 18 16:11] Tongariro.jpg +│   ├── [4.0K May 27 19:34] corrupt_queue_file.txt +│   ├── [ 519 Feb 18 16:11] envelope-event-attachment.txt +│   ├── [ 642 Feb 18 16:11] envelope-feedback.txt +│   ├── [ 421 Feb 18 16:11] envelope-session-start.txt +│   ├── [ 964 Feb 18 16:11] envelope-transaction-with-sample-rand.txt +│   ├── [ 942 Feb 18 16:11] envelope-transaction-with-sample-rate.txt +│   ├── [ 834 Feb 18 16:11] envelope-transaction.txt +│   ├── [ 155 Feb 18 16:11] envelope_attachment.txt +│   ├── [ 376 Feb 18 16:11] envelope_session.txt +│   ├── [ 516 Feb 18 16:11] envelope_session_sdkversion.txt +│   ├── [ 672 Feb 18 16:11] event.json +│   ├── [1.3K Feb 18 16:11] event_breadcrumb_data.json +│   ├── [ 368 Feb 18 16:11] event_with_contexts.json +│   ├── [1.9K May 27 19:34] json +│   │   ├── [ 621 Feb 18 16:11] app.json +│   │   ├── [ 377 Feb 18 16:11] breadcrumb.json +│   │   ├── [ 110 Feb 18 16:11] browser.json +│   │   ├── [ 701 May 27 19:34] checkin_crontab.json +│   │   ├── [ 717 May 27 19:34] checkin_interval.json +│   │   ├── [4.6K May 27 19:34] contexts.json +│   │   ├── [ 483 Feb 18 16:11] debug_image.json +│   │   ├── [ 805 Feb 18 16:11] debug_meta.json +│   │   ├── [1.5K Feb 18 16:11] device.json +│   │   ├── [ 408 Feb 18 16:11] gpu.json +│   │   ├── [ 78 Feb 18 16:11] measurement_value_double.json +│   │   ├── [ 60 Feb 18 16:11] measurement_value_int.json +│   │   ├── [ 46 Feb 18 16:11] measurement_value_missing.json +│   │   ├── [ 441 Feb 18 16:11] mechanism.json +│   │   ├── [ 236 Feb 18 16:11] message.json +│   │   ├── [ 308 Feb 18 16:11] operating_system.json +│   │   ├── [1.1K Feb 18 16:11] replay_recording.json +│   │   ├── [ 754 Feb 18 16:11] request.json +│   │   ├── [ 311 Feb 18 16:11] response.json +│   │   ├── [ 335 Feb 18 16:11] rrweb_breadcrumb_event.json +│   │   ├── [ 40 Feb 18 16:11] rrweb_event.json +│   │   ├── [ 175 Feb 18 16:11] rrweb_interaction_event.json +│   │   ├── [ 218 Feb 18 16:11] rrweb_interaction_move_event.json +│   │   ├── [ 131 Feb 18 16:11] rrweb_meta_event.json +│   │   ├── [ 408 Feb 18 16:11] rrweb_options_event.json +│   │   ├── [ 363 Feb 18 16:11] rrweb_span_event.json +│   │   ├── [ 382 Feb 18 16:11] rrweb_video_event.json +│   │   ├── [ 162 Feb 18 16:11] sdk_info.json +│   │   ├── [ 409 Feb 18 16:11] sdk_version.json +│   │   ├── [8.0K May 27 19:34] sentry_base_event.json +│   │   ├── [8.0K May 27 19:34] sentry_base_event_with_null_extra.json +│   │   ├── [1.1K Feb 18 16:11] sentry_envelope_header.json +│   │   ├── [ 271 May 27 19:34] sentry_envelope_item_header.json +│   │   ├── [ 15K May 27 19:34] sentry_event.json +│   │   ├── [1.8K Feb 18 16:11] sentry_exception.json +│   │   ├── [ 56 Feb 18 16:11] sentry_id.json +│   │   ├── [ 119 Feb 18 16:11] sentry_lock_reason.json +│   │   ├── [1.1K May 27 19:34] sentry_logs.json +│   │   ├── [ 110 Feb 18 16:11] sentry_package.json +│   │   ├── [8.8K May 27 19:34] sentry_replay_event.json +│   │   ├── [ 173 Feb 18 16:11] sentry_runtime.json +│   │   ├── [ 706 Feb 18 16:11] sentry_span.json +│   │   ├── [ 728 Feb 18 16:11] sentry_span_legacy_date_format.json +│   │   ├── [ 995 May 27 19:34] sentry_stack_frame.json +│   │   ├── [1.0K Feb 18 16:11] sentry_stack_trace.json +│   │   ├── [1.7K Feb 18 16:11] sentry_thread.json +│   │   ├── [ 11K May 27 19:34] sentry_transaction.json +│   │   ├── [ 11K May 27 19:34] sentry_transaction_legacy_date_format.json +│   │   ├── [8.9K May 27 19:34] sentry_transaction_no_measurement_unit.json +│   │   ├── [ 606 Feb 18 16:11] session.json +│   │   ├── [ 733 May 27 19:34] span_context.json +│   │   ├── [ 569 Feb 18 16:11] span_context_null_op.json +│   │   ├── [ 58 Feb 18 16:11] span_id.json +│   │   ├── [ 46 May 27 19:34] spring.json +│   │   ├── [ 483 Feb 18 16:11] trace_state.json +│   │   ├── [ 341 Feb 18 16:11] trace_state_no_sample_rate.json +│   │   ├── [ 547 Feb 18 16:11] user.json +│   │   ├── [ 111 Feb 18 16:11] view_hierarchy.json +│   │   └── [ 300 Feb 18 16:11] view_hierarchy_node.json +│   ├── [ 96 Feb 18 16:11] mockito-extensions +│   │   └── [ 18 Feb 18 16:11] org.mockito.plugins.MockMaker +│   └── [ 375 Feb 18 16:11] session.json +├── [ 192 May 27 19:34] sentry-android +│   ├── [ 868 May 27 19:34] build.gradle.kts +│   ├── [ 0 Feb 18 16:11] proguard-rules.pro +│   └── [ 96 Feb 18 16:11] src +│   └── [ 128 Feb 18 16:11] main +│   ├── [ 173 Feb 18 16:11] AndroidManifest.xml +│   └── [ 96 Feb 18 16:11] res +│   └── [ 96 Feb 18 16:11] values +│   └── [ 77 Feb 18 16:11] public.xml +├── [ 256 May 27 19:34] sentry-android-core +│   ├── [ 62 Feb 18 16:11] .gitignore +│   ├── [ 96 May 27 19:34] api +│   │   └── [ 29K May 27 19:34] sentry-android-core.api +│   ├── [3.5K May 27 19:34] build.gradle.kts +│   ├── [3.7K Feb 18 16:11] proguard-rules.pro +│   └── [ 128 Feb 18 16:11] src +│   ├── [ 160 Feb 18 16:11] main +│   │   ├── [ 657 Feb 18 16:11] AndroidManifest.xml +│   │   ├── [ 96 Feb 18 16:11] java +│   │   │   └── [ 96 Feb 18 16:11] io +│   │   │   └── [ 96 Feb 18 16:11] sentry +│   │   │   └── [ 96 Feb 18 16:11] android +│   │   │   └── [1.9K May 27 19:34] core +│   │   │   ├── [7.0K Feb 18 16:11] ANRWatchDog.java +│   │   │   ├── [4.6K Feb 18 16:11] ActivityBreadcrumbsIntegration.java +│   │   │   ├── [9.2K Feb 18 16:11] ActivityFramesTracker.java +│   │   │   ├── [ 30K May 27 19:34] ActivityLifecycleIntegration.java +│   │   │   ├── [ 14K May 27 19:34] AndroidContinuousProfiler.java +│   │   │   ├── [4.3K May 27 19:34] AndroidCpuCollector.java +│   │   │   ├── [ 645 Feb 18 16:11] AndroidDateUtils.java +│   │   │   ├── [1.6K May 27 19:34] AndroidFatalLogger.java +│   │   │   ├── [2.2K Feb 18 16:11] AndroidLogger.java +│   │   │   ├── [ 927 May 27 19:34] AndroidMemoryCollector.java +│   │   │   ├── [ 19K May 27 19:34] AndroidOptionsInitializer.java +│   │   │   ├── [ 15K May 27 19:34] AndroidProfiler.java +│   │   │   ├── [ 728 May 27 19:34] AndroidSocketTagger.java +│   │   │   ├── [ 12K May 27 19:34] AndroidTransactionProfiler.java +│   │   │   ├── [ 971 Feb 18 16:11] AndroidTransportGate.java +│   │   │   ├── [6.6K Feb 18 16:11] AnrIntegration.java +│   │   │   ├── [ 595 Feb 18 16:11] AnrIntegrationFactory.java +│   │   │   ├── [ 25K May 27 19:34] AnrV2EventProcessor.java +│   │   │   ├── [ 15K May 27 19:34] AnrV2Integration.java +│   │   │   ├── [6.3K May 27 19:34] AppComponentsBreadcrumbsIntegration.java +│   │   │   ├── [4.6K May 27 19:34] AppLifecycleIntegration.java +│   │   │   ├── [1.1K Feb 18 16:11] AppState.java +│   │   │   ├── [1.2K Feb 18 16:11] ApplicationNotResponding.java +│   │   │   ├── [2.4K Feb 18 16:11] BuildInfoProvider.java +│   │   │   ├── [ 20K May 27 19:34] ContextUtils.java +│   │   │   ├── [1.2K May 27 19:34] CurrentActivityHolder.java +│   │   │   ├── [ 12K Feb 18 16:11] DefaultAndroidEventProcessor.java +│   │   │   ├── [ 16K May 27 19:34] DeviceInfoUtil.java +│   │   │   ├── [1.7K Feb 18 16:11] EmptySecureContentProvider.java +│   │   │   ├── [3.5K Feb 18 16:11] EnvelopeFileObserver.java +│   │   │   ├── [3.8K May 27 19:34] EnvelopeFileObserverIntegration.java +│   │   │   ├── [ 494 Feb 18 16:11] IDebugImagesLoader.java +│   │   │   ├── [2.4K Feb 18 16:11] Installation.java +│   │   │   ├── [ 13K May 27 19:34] InternalSentrySdk.java +│   │   │   ├── [5.1K May 27 19:34] LifecycleWatcher.java +│   │   │   ├── [1.1K Feb 18 16:11] LoadClass.java +│   │   │   ├── [ 536 Feb 18 16:11] MainLooperHandler.java +│   │   │   ├── [ 23K May 27 19:34] ManifestMetadataReader.java +│   │   │   ├── [ 362 Feb 18 16:11] NdkHandlerStrategy.java +│   │   │   ├── [3.6K Feb 18 16:11] NdkIntegration.java +│   │   │   ├── [ 12K Feb 18 16:11] NetworkBreadcrumbsIntegration.java +│   │   │   ├── [ 693 Feb 18 16:11] NoOpDebugImagesLoader.java +│   │   │   ├── [ 12K Feb 18 16:11] PerformanceAndroidEventProcessor.java +│   │   │   ├── [3.9K May 27 19:34] ScreenshotEventProcessor.java +│   │   │   ├── [7.1K Feb 18 16:11] SendCachedEnvelopeIntegration.java +│   │   │   ├── [ 10K May 27 19:34] SentryAndroid.java +│   │   │   ├── [ 897 Feb 18 16:11] SentryAndroidDateProvider.java +│   │   │   ├── [ 20K May 27 19:34] SentryAndroidOptions.java +│   │   │   ├── [3.0K Feb 18 16:11] SentryFrameMetrics.java +│   │   │   ├── [1.5K May 27 19:34] SentryInitProvider.java +│   │   │   ├── [3.7K Feb 18 16:11] SentryLogcatAdapter.java +│   │   │   ├── [9.1K May 27 19:34] SentryPerformanceProvider.java +│   │   │   ├── [ 13K Feb 18 16:11] SpanFrameMetricsCollector.java +│   │   │   ├── [ 16K May 27 19:34] SystemEventsBreadcrumbsIntegration.java +│   │   │   ├── [5.5K May 27 19:34] UserInteractionIntegration.java +│   │   │   ├── [9.4K Feb 18 16:11] ViewHierarchyEventProcessor.java +│   │   │   ├── [ 96 Feb 18 16:11] cache +│   │   │   │   └── [6.3K Feb 18 16:11] AndroidEnvelopeCache.java +│   │   │   ├── [ 224 Feb 18 16:11] internal +│   │   │   │   ├── [ 96 May 27 19:34] debugmeta +│   │   │   │   │   └── [2.0K May 27 19:34] AssetsDebugMetaLoader.java +│   │   │   │   ├── [ 256 May 27 19:34] gestures +│   │   │   │   │   ├── [2.4K Feb 18 16:11] AndroidViewGestureTargetLocator.java +│   │   │   │   │   ├── [2.6K Feb 18 16:11] NoOpWindowCallback.java +│   │   │   │   │   ├── [ 13K May 27 19:34] SentryGestureListener.java +│   │   │   │   │   ├── [2.8K May 27 19:34] SentryWindowCallback.java +│   │   │   │   │   ├── [4.6K May 27 19:34] ViewUtils.java +│   │   │   │   │   └── [3.7K Feb 18 16:11] WindowCallbackAdapter.java +│   │   │   │   ├── [ 96 May 27 19:34] modules +│   │   │   │   │   └── [1.4K May 27 19:34] AssetsModulesLoader.java +│   │   │   │   ├── [ 160 May 27 19:34] threaddump +│   │   │   │   │   ├── [1.1K Feb 18 16:11] Line.java +│   │   │   │   │   ├── [2.7K Feb 18 16:11] Lines.java +│   │   │   │   │   └── [ 17K May 27 19:34] ThreadDumpParser.java +│   │   │   │   └── [ 512 May 27 19:34] util +│   │   │   │   ├── [ 13K May 27 19:34] AndroidConnectionStatusProvider.java +│   │   │   │   ├── [ 581 Feb 18 16:11] AndroidCurrentDateProvider.java +│   │   │   │   ├── [1.6K May 27 19:34] AndroidThreadChecker.java +│   │   │   │   ├── [ 563 Feb 18 16:11] BreadcrumbFactory.java +│   │   │   │   ├── [ 511 Feb 18 16:11] ClassUtil.java +│   │   │   │   ├── [2.4K Feb 18 16:11] ContentProviderSecurityChecker.java +│   │   │   │   ├── [2.6K Feb 18 16:11] CpuInfoUtils.java +│   │   │   │   ├── [1.5K Feb 18 16:11] Debouncer.java +│   │   │   │   ├── [ 930 Feb 18 16:11] DeviceOrientations.java +│   │   │   │   ├── [5.3K Feb 18 16:11] FirstDrawDoneListener.java +│   │   │   │   ├── [ 672 Feb 18 16:11] Permissions.java +│   │   │   │   ├── [5.7K Feb 18 16:11] RootChecker.java +│   │   │   │   ├── [6.7K May 27 19:34] ScreenshotUtils.java +│   │   │   │   └── [ 15K Feb 18 16:11] SentryFrameMetricsCollector.java +│   │   │   ├── [ 256 May 27 19:34] performance +│   │   │   │   ├── [ 913 Feb 18 16:11] ActivityLifecycleCallbacksAdapter.java +│   │   │   │   ├── [4.9K Feb 18 16:11] ActivityLifecycleSpanHelper.java +│   │   │   │   ├── [ 855 Feb 18 16:11] ActivityLifecycleTimeSpan.java +│   │   │   │   ├── [ 16K May 27 19:34] AppStartMetrics.java +│   │   │   │   ├── [4.7K Feb 18 16:11] TimeSpan.java +│   │   │   │   └── [ 588 Feb 18 16:11] WindowContentChangedCallback.java +│   │   │   └── [ 96 Feb 18 16:11] util +│   │   │   └── [1.8K Feb 18 16:11] AndroidLazyEvaluator.java +│   │   └── [ 96 Feb 18 16:11] res +│   │   └── [ 96 Feb 18 16:11] values +│   │   └── [ 77 Feb 18 16:11] public.xml +│   └── [ 192 Feb 18 16:11] test +│   ├── [ 698 Feb 18 16:11] AndroidManifest.xml +│   ├── [ 96 Feb 18 16:11] assets +│   │   └── [ 29 Feb 18 16:11] sentry-debug-meta.properties +│   ├── [ 96 Feb 18 16:11] java +│   │   └── [ 96 Feb 18 16:11] io +│   │   └── [ 96 Feb 18 16:11] sentry +│   │   └── [ 96 Feb 18 16:11] android +│   │   └── [1.7K May 27 19:34] core +│   │   ├── [7.6K Feb 18 16:11] ANRWatchDogTest.kt +│   │   ├── [4.7K Feb 18 16:11] ActivityBreadcrumbsIntegrationTest.kt +│   │   ├── [ 14K Feb 18 16:11] ActivityFramesTrackerTest.kt +│   │   ├── [ 63K May 27 19:34] ActivityLifecycleIntegrationTest.kt +│   │   ├── [ 11K May 27 19:34] AndroidConnectionStatusProviderTest.kt +│   │   ├── [ 22K May 27 19:34] AndroidContinuousProfilerTest.kt +│   │   ├── [1.4K May 27 19:34] AndroidCpuCollectorTest.kt +│   │   ├── [1.1K May 27 19:34] AndroidMemoryCollectorTest.kt +│   │   ├── [ 31K May 27 19:34] AndroidOptionsInitializerTest.kt +│   │   ├── [ 11K May 27 19:34] AndroidProfilerTest.kt +│   │   ├── [ 20K May 27 19:34] AndroidTransactionProfilerTest.kt +│   │   ├── [1.2K Feb 18 16:11] AndroidTransportGateTest.kt +│   │   ├── [4.8K Feb 18 16:11] AnrIntegrationTest.kt +│   │   ├── [ 26K May 27 19:34] AnrV2EventProcessorTest.kt +│   │   ├── [ 24K May 27 19:34] AnrV2IntegrationTest.kt +│   │   ├── [6.1K May 27 19:34] AppComponentsBreadcrumbsIntegrationTest.kt +│   │   ├── [3.1K Feb 18 16:11] AppLifecycleIntegrationTest.kt +│   │   ├── [ 102 Feb 18 16:11] ApplicationStub.kt +│   │   ├── [ 90 Feb 18 16:11] CachedEvent.kt +│   │   ├── [ 11K May 27 19:34] ContextUtilsTest.kt +│   │   ├── [1.6K Feb 18 16:11] ContextUtilsTestHelper.kt +│   │   ├── [ 163 Feb 18 16:11] CustomCachedApplyScopeDataHint.kt +│   │   ├── [ 21K Feb 18 16:11] DefaultAndroidEventProcessorTest.kt +│   │   ├── [4.4K May 27 19:34] DeviceInfoUtilTest.kt +│   │   ├── [4.2K Feb 18 16:11] EnvelopeFileObserverIntegrationTest.kt +│   │   ├── [3.8K Feb 18 16:11] EnvelopeFileObserverTest.kt +│   │   ├── [1.3K Feb 18 16:11] InstallationTest.kt +│   │   ├── [ 22K May 27 19:34] InternalSentrySdkTest.kt +│   │   ├── [ 10K May 27 19:34] LifecycleWatcherTest.kt +│   │   ├── [ 53K May 27 19:34] ManifestMetadataReaderTest.kt +│   │   ├── [5.1K Feb 18 16:11] NdkIntegrationTest.kt +│   │   ├── [ 24K Feb 18 16:11] NetworkBreadcrumbsIntegrationTest.kt +│   │   ├── [ 29K May 27 19:34] PerformanceAndroidEventProcessorTest.kt +│   │   ├── [ 737 Feb 18 16:11] PermissionsTest.kt +│   │   ├── [9.9K Feb 18 16:11] ScreenshotEventProcessorTest.kt +│   │   ├── [8.7K Feb 18 16:11] SendCachedEnvelopeIntegrationTest.kt +│   │   ├── [ 349 Feb 18 16:11] SentryAndroidDateProviderTest.kt +│   │   ├── [6.0K Feb 18 16:11] SentryAndroidOptionsTest.kt +│   │   ├── [ 23K May 27 19:34] SentryAndroidTest.kt +│   │   ├── [4.0K Feb 18 16:11] SentryFrameMetricsTest.kt +│   │   ├── [5.8K May 27 19:34] SentryInitProviderTest.kt +│   │   ├── [7.1K Feb 18 16:11] SentryLogcatAdapterTest.kt +│   │   ├── [ 212 Feb 18 16:11] SentryNdk.kt +│   │   ├── [ 13K May 27 19:34] SentryPerformanceProviderTest.kt +│   │   ├── [ 534 Feb 18 16:11] SentryShadowProcess.kt +│   │   ├── [6.8K May 27 19:34] SessionTrackingIntegrationTest.kt +│   │   ├── [ 15K Feb 18 16:11] SpanFrameMetricsCollectorTest.kt +│   │   ├── [ 13K May 27 19:34] SystemEventsBreadcrumbsIntegrationTest.kt +│   │   ├── [7.4K May 27 19:34] UserInteractionIntegrationTest.kt +│   │   ├── [ 13K Feb 18 16:11] ViewHierarchyEventProcessorTest.kt +│   │   ├── [ 96 Feb 18 16:11] cache +│   │   │   └── [6.5K Feb 18 16:11] AndroidEnvelopeCacheTest.kt +│   │   ├── [ 224 Feb 18 16:11] internal +│   │   │   ├── [ 96 Feb 18 16:11] debugmeta +│   │   │   │   └── [3.2K Feb 18 16:11] AssetsDebugMetaLoaderTest.kt +│   │   │   ├── [ 256 May 27 19:34] gestures +│   │   │   │   ├── [9.4K Feb 18 16:11] SentryGestureListenerClickTest.kt +│   │   │   │   ├── [ 10K Feb 18 16:11] SentryGestureListenerScrollTest.kt +│   │   │   │   ├── [ 14K May 27 19:34] SentryGestureListenerTracingTest.kt +│   │   │   │   ├── [2.9K Feb 18 16:11] SentryWindowCallbackTest.kt +│   │   │   │   ├── [2.3K Feb 18 16:11] ViewHelpers.kt +│   │   │   │   └── [3.1K Feb 18 16:11] ViewUtilsTest.kt +│   │   │   ├── [ 96 Feb 18 16:11] modules +│   │   │   │   └── [2.8K Feb 18 16:11] AssetsModulesLoaderTest.kt +│   │   │   ├── [ 96 May 27 19:34] threaddump +│   │   │   │   └── [8.0K May 27 19:34] ThreadDumpParserTest.kt +│   │   │   └── [ 384 May 27 19:34] util +│   │   │   ├── [2.1K May 27 19:34] AndroidThreadCheckerTest.kt +│   │   │   ├── [ 916 Feb 18 16:11] ClassUtilTest.kt +│   │   │   ├── [2.7K Feb 18 16:11] ContentProviderSecurityCheckerTest.kt +│   │   │   ├── [2.9K Feb 18 16:11] CpuInfoUtilsTest.kt +│   │   │   ├── [3.4K Feb 18 16:11] DebouncerTest.kt +│   │   │   ├── [1.1K Feb 18 16:11] DeviceOrientationsTest.kt +│   │   │   ├── [7.2K Feb 18 16:11] FirstDrawDoneListenerTest.kt +│   │   │   ├── [5.0K Feb 18 16:11] RootCheckerTest.kt +│   │   │   ├── [5.2K May 27 19:34] ScreenshotUtilTest.kt +│   │   │   └── [ 21K Feb 18 16:11] SentryFrameMetricsCollectorTest.kt +│   │   └── [ 192 May 27 19:34] performance +│   │   ├── [7.6K Feb 18 16:11] ActivityLifecycleSpanHelperTest.kt +│   │   ├── [1.6K Feb 18 16:11] ActivityLifecycleTimeSpanTest.kt +│   │   ├── [ 22K May 27 19:34] AppStartMetricsTest.kt +│   │   └── [3.7K Feb 18 16:11] TimeSpanTest.kt +│   └── [ 224 Feb 18 16:11] resources +│   ├── [ 96 Feb 18 16:11] mockito-extensions +│   │   └── [ 18 Feb 18 16:11] org.mockito.plugins.MockMaker +│   ├── [ 58 Feb 18 16:11] robolectric.properties +│   ├── [ 47K Feb 18 16:11] thread_dump.txt +│   ├── [ 47K Feb 18 16:11] thread_dump_bad_data.txt +│   └── [176K Feb 18 16:11] thread_dump_native_only.txt +├── [ 256 May 27 19:34] sentry-android-fragment +│   ├── [ 26 Feb 18 16:11] .gitignore +│   ├── [ 96 Feb 18 16:11] api +│   │   └── [4.6K Feb 18 16:11] sentry-android-fragment.api +│   ├── [1.8K May 27 19:34] build.gradle.kts +│   ├── [ 561 Feb 18 16:11] proguard-rules.pro +│   └── [ 128 Feb 18 16:11] src +│   ├── [ 128 Feb 18 16:11] main +│   │   ├── [ 96 Feb 18 16:11] java +│   │   │   └── [ 96 Feb 18 16:11] io +│   │   │   └── [ 96 Feb 18 16:11] sentry +│   │   │   └── [ 96 Feb 18 16:11] android +│   │   │   └── [ 160 May 27 19:34] fragment +│   │   │   ├── [3.6K May 27 19:34] FragmentLifecycleIntegration.kt +│   │   │   ├── [ 884 Feb 18 16:11] FragmentLifecycleState.kt +│   │   │   └── [6.5K Feb 18 16:11] SentryFragmentLifecycleCallbacks.kt +│   │   └── [ 96 Feb 18 16:11] res +│   │   └── [ 96 Feb 18 16:11] values +│   │   └── [ 77 Feb 18 16:11] public.xml +│   └── [ 96 Feb 18 16:11] test +│   └── [ 96 Feb 18 16:11] java +│   └── [ 96 Feb 18 16:11] io +│   └── [ 96 Feb 18 16:11] sentry +│   └── [ 96 Feb 18 16:11] android +│   └── [ 160 Feb 18 16:11] fragment +│   ├── [3.9K Feb 18 16:11] FragmentLifecycleIntegrationTest.kt +│   ├── [ 280 Feb 18 16:11] FragmentLifecycleStateTest.kt +│   └── [9.6K Feb 18 16:11] SentryFragmentLifecycleCallbacksTest.kt +├── [ 288 Feb 18 16:11] sentry-android-integration-tests +│   ├── [ 586 Feb 18 16:11] README.md +│   ├── [ 425 Feb 18 16:11] metrics-test.yml +│   ├── [ 224 May 27 19:34] sentry-uitest-android +│   │   ├── [ 7 Feb 18 16:11] .gitignore +│   │   ├── [2.4K Feb 18 16:11] README.md +│   │   ├── [5.1K May 27 19:34] build.gradle.kts +│   │   ├── [1.4K Feb 18 16:11] proguard-rules.pro +│   │   └── [ 128 Feb 18 16:11] src +│   │   ├── [ 96 Feb 18 16:11] androidTest +│   │   │   └── [ 96 Feb 18 16:11] java +│   │   │   └── [ 96 Feb 18 16:11] io +│   │   │   └── [ 96 Feb 18 16:11] sentry +│   │   │   └── [ 96 Feb 18 16:11] uitest +│   │   │   └── [ 288 May 27 19:34] android +│   │   │   ├── [6.0K Feb 18 16:11] AutomaticSpansTest.kt +│   │   │   ├── [4.5K Feb 18 16:11] BaseUiTest.kt +│   │   │   ├── [ 14K May 27 19:34] EnvelopeTests.kt +│   │   │   ├── [3.0K Feb 18 16:11] ReplayTest.kt +│   │   │   ├── [8.0K Feb 18 16:11] SdkInitTests.kt +│   │   │   ├── [3.5K Feb 18 16:11] UserInteractionTests.kt +│   │   │   └── [ 160 Feb 18 16:11] mockservers +│   │   │   ├── [1.9K Feb 18 16:11] EnvelopeAsserter.kt +│   │   │   ├── [4.9K Feb 18 16:11] MockRelay.kt +│   │   │   └── [5.0K Feb 18 16:11] RelayAsserter.kt +│   │   └── [ 160 Feb 18 16:11] main +│   │   ├── [1.2K Feb 18 16:11] AndroidManifest.xml +│   │   ├── [ 96 Feb 18 16:11] java +│   │   │   └── [ 96 Feb 18 16:11] io +│   │   │   └── [ 96 Feb 18 16:11] sentry +│   │   │   └── [ 96 Feb 18 16:11] uitest +│   │   │   └── [ 192 Feb 18 16:11] android +│   │   │   ├── [2.3K Feb 18 16:11] ComposeActivity.kt +│   │   │   ├── [ 261 Feb 18 16:11] EmptyActivity.kt +│   │   │   ├── [4.0K Feb 18 16:11] ProfilingSampleActivity.kt +│   │   │   └── [ 96 Feb 18 16:11] utils +│   │   │   └── [ 944 Feb 18 16:11] BooleanIdlingResource.kt +│   │   └── [ 128 Feb 18 16:11] res +│   │   ├── [ 128 Feb 18 16:11] layout +│   │   │   ├── [ 389 Feb 18 16:11] activity_profiling_sample.xml +│   │   │   └── [ 923 Feb 18 16:11] profiling_sample_item_list.xml +│   │   └── [ 96 Feb 18 16:11] values +│   │   └── [ 211 Feb 18 16:11] values.xml +│   ├── [ 192 May 27 19:34] sentry-uitest-android-benchmark +│   │   ├── [ 7 Feb 18 16:11] .gitignore +│   │   ├── [1.1K Feb 18 16:11] benchmark-proguard-rules.pro +│   │   ├── [4.5K May 27 19:34] build.gradle.kts +│   │   └── [ 128 Feb 18 16:11] src +│   │   ├── [ 96 Feb 18 16:11] androidTest +│   │   │   └── [ 96 Feb 18 16:11] java +│   │   │   └── [ 96 Feb 18 16:11] io +│   │   │   └── [ 96 Feb 18 16:11] sentry +│   │   │   └── [ 96 Feb 18 16:11] uitest +│   │   │   └── [ 96 Feb 18 16:11] android +│   │   │   └── [ 160 Feb 18 16:11] benchmark +│   │   │   ├── [1.2K Feb 18 16:11] BaseBenchmarkTest.kt +│   │   │   ├── [5.9K Feb 18 16:11] SentryBenchmarkTest.kt +│   │   │   └── [ 160 Feb 18 16:11] util +│   │   │   ├── [6.5K Feb 18 16:11] BenchmarkComparisonResult.kt +│   │   │   ├── [7.3K Feb 18 16:11] BenchmarkOperation.kt +│   │   │   └── [3.0K Feb 18 16:11] BenchmarkOperationComparable.kt +│   │   └── [ 160 Feb 18 16:11] main +│   │   ├── [1.0K Feb 18 16:11] AndroidManifest.xml +│   │   ├── [ 96 Feb 18 16:11] java +│   │   │   └── [ 96 Feb 18 16:11] io +│   │   │   └── [ 96 Feb 18 16:11] sentry +│   │   │   └── [ 96 Feb 18 16:11] uitest +│   │   │   └── [ 96 Feb 18 16:11] android +│   │   │   └── [ 128 Feb 18 16:11] benchmark +│   │   │   ├── [3.3K Feb 18 16:11] BenchmarkActivity.kt +│   │   │   └── [1.8K Feb 18 16:11] BenchmarkTransactionListAdapter.kt +│   │   └── [ 96 Feb 18 16:11] res +│   │   └── [ 128 Feb 18 16:11] layout +│   │   ├── [1.2K Feb 18 16:11] activity_benchmark.xml +│   │   └── [1.3K Feb 18 16:11] benchmark_item_list.xml +│   ├── [ 224 May 27 19:34] sentry-uitest-android-critical +│   │   ├── [ 21 Feb 18 16:11] .gitignore +│   │   ├── [1.8K May 27 19:34] build.gradle.kts +│   │   ├── [ 128 Feb 18 16:11] maestro +│   │   │   ├── [ 294 Feb 18 16:11] corruptEnvelope.yaml +│   │   │   └── [ 114 Feb 18 16:11] crash.yaml +│   │   ├── [ 751 Feb 18 16:11] proguard-rules.pro +│   │   └── [ 96 Feb 18 16:11] src +│   │   └── [ 128 Feb 18 16:11] main +│   │   ├── [ 861 Feb 18 16:11] AndroidManifest.xml +│   │   └── [ 96 Feb 18 16:11] java +│   │   └── [ 96 Feb 18 16:11] io +│   │   └── [ 96 Feb 18 16:11] sentry +│   │   └── [ 96 Feb 18 16:11] uitest +│   │   └── [ 96 Feb 18 16:11] android +│   │   └── [ 96 Feb 18 16:11] critical +│   │   └── [2.0K Feb 18 16:11] MainActivity.kt +│   ├── [ 192 May 27 19:34] test-app-plain +│   │   ├── [ 6 Feb 18 16:11] .gitignore +│   │   ├── [1.7K May 27 19:34] build.gradle.kts +│   │   ├── [ 840 Feb 18 16:11] proguard-rules.pro +│   │   └── [ 96 Feb 18 16:11] src +│   │   └── [ 160 Feb 18 16:11] main +│   │   ├── [1.0K Feb 18 16:11] AndroidManifest.xml +│   │   ├── [ 96 Feb 18 16:11] java +│   │   │   └── [ 96 Feb 18 16:11] io +│   │   │   └── [ 96 Feb 18 16:11] sentry +│   │   │   └── [ 96 Feb 18 16:11] java +│   │   │   └── [ 96 Feb 18 16:11] tests +│   │   │   └── [ 96 Feb 18 16:11] perf +│   │   │   └── [ 160 Feb 18 16:11] appplain +│   │   │   ├── [1.2K Feb 18 16:11] FirstFragment.java +│   │   │   ├── [2.4K Feb 18 16:11] MainActivity.java +│   │   │   └── [1.2K Feb 18 16:11] SecondFragment.java +│   │   └── [ 608 Feb 18 16:11] res +│   │   ├── [ 96 Feb 18 16:11] drawable +│   │   │   └── [5.5K Feb 18 16:11] ic_launcher_background.xml +│   │   ├── [ 96 Feb 18 16:11] drawable-v24 +│   │   │   └── [1.7K Feb 18 16:11] ic_launcher_foreground.xml +│   │   ├── [ 192 Feb 18 16:11] layout +│   │   │   ├── [1.5K Feb 18 16:11] activity_main.xml +│   │   │   ├── [ 907 Feb 18 16:11] content_main.xml +│   │   │   ├── [1.2K Feb 18 16:11] fragment_first.xml +│   │   │   └── [1.2K Feb 18 16:11] fragment_second.xml +│   │   ├── [ 96 Feb 18 16:11] menu +│   │   │   └── [ 422 Feb 18 16:11] menu_main.xml +│   │   ├── [ 128 Feb 18 16:11] mipmap-anydpi-v26 +│   │   │   ├── [ 272 Feb 18 16:11] ic_launcher.xml +│   │   │   └── [ 272 Feb 18 16:11] ic_launcher_round.xml +│   │   ├── [ 128 Feb 18 16:11] mipmap-hdpi +│   │   │   ├── [2.9K Feb 18 16:11] ic_launcher.png +│   │   │   └── [4.8K Feb 18 16:11] ic_launcher_round.png +│   │   ├── [ 128 Feb 18 16:11] mipmap-mdpi +│   │   │   ├── [2.0K Feb 18 16:11] ic_launcher.png +│   │   │   └── [2.7K Feb 18 16:11] ic_launcher_round.png +│   │   ├── [ 128 Feb 18 16:11] mipmap-xhdpi +│   │   │   ├── [4.4K Feb 18 16:11] ic_launcher.png +│   │   │   └── [6.7K Feb 18 16:11] ic_launcher_round.png +│   │   ├── [ 128 Feb 18 16:11] mipmap-xxhdpi +│   │   │   ├── [6.2K Feb 18 16:11] ic_launcher.png +│   │   │   └── [ 10K Feb 18 16:11] ic_launcher_round.png +│   │   ├── [ 128 Feb 18 16:11] mipmap-xxxhdpi +│   │   │   ├── [8.9K Feb 18 16:11] ic_launcher.png +│   │   │   └── [ 15K Feb 18 16:11] ic_launcher_round.png +│   │   ├── [ 96 Feb 18 16:11] navigation +│   │   │   └── [1.0K Feb 18 16:11] nav_graph.xml +│   │   ├── [ 192 Feb 18 16:11] values +│   │   │   ├── [ 378 Feb 18 16:11] colors.xml +│   │   │   ├── [ 66 Feb 18 16:11] dimens.xml +│   │   │   ├── [ 550 Feb 18 16:11] strings.xml +│   │   │   └── [1.2K Feb 18 16:11] themes.xml +│   │   ├── [ 96 Feb 18 16:11] values-land +│   │   │   └── [ 66 Feb 18 16:11] dimens.xml +│   │   ├── [ 96 Feb 18 16:11] values-night +│   │   │   └── [ 834 Feb 18 16:11] themes.xml +│   │   ├── [ 96 Feb 18 16:11] values-w1240dp +│   │   │   └── [ 67 Feb 18 16:11] dimens.xml +│   │   ├── [ 96 Feb 18 16:11] values-w600dp +│   │   │   └── [ 66 Feb 18 16:11] dimens.xml +│   │   └── [ 128 Feb 18 16:11] xml +│   │   ├── [ 478 Feb 18 16:11] backup_rules.xml +│   │   └── [ 551 Feb 18 16:11] data_extraction_rules.xml +│   └── [ 192 May 27 19:34] test-app-sentry +│   ├── [ 6 Feb 18 16:11] .gitignore +│   ├── [1.7K May 27 19:34] build.gradle.kts +│   ├── [ 839 Feb 18 16:11] proguard-rules.pro +│   └── [ 96 Feb 18 16:11] src +│   └── [ 160 Feb 18 16:11] main +│   ├── [1.2K Feb 18 16:11] AndroidManifest.xml +│   ├── [ 96 Feb 18 16:11] java +│   │   └── [ 96 Feb 18 16:11] io +│   │   └── [ 96 Feb 18 16:11] sentry +│   │   └── [ 96 Feb 18 16:11] java +│   │   └── [ 96 Feb 18 16:11] tests +│   │   └── [ 96 Feb 18 16:11] perf +│   │   └── [ 160 Feb 18 16:11] appsentry +│   │   ├── [1.2K Feb 18 16:11] FirstFragment.java +│   │   ├── [2.4K Feb 18 16:11] MainActivity.java +│   │   └── [1.2K Feb 18 16:11] SecondFragment.java +│   └── [ 608 Feb 18 16:11] res +│   ├── [ 96 Feb 18 16:11] drawable +│   │   └── [5.5K Feb 18 16:11] ic_launcher_background.xml +│   ├── [ 96 Feb 18 16:11] drawable-v24 +│   │   └── [1.7K Feb 18 16:11] ic_launcher_foreground.xml +│   ├── [ 192 Feb 18 16:11] layout +│   │   ├── [1.5K Feb 18 16:11] activity_main.xml +│   │   ├── [ 907 Feb 18 16:11] content_main.xml +│   │   ├── [1.2K Feb 18 16:11] fragment_first.xml +│   │   └── [1.2K Feb 18 16:11] fragment_second.xml +│   ├── [ 96 Feb 18 16:11] menu +│   │   └── [ 424 Feb 18 16:11] menu_main.xml +│   ├── [ 128 Feb 18 16:11] mipmap-anydpi-v26 +│   │   ├── [ 272 Feb 18 16:11] ic_launcher.xml +│   │   └── [ 272 Feb 18 16:11] ic_launcher_round.xml +│   ├── [ 128 Feb 18 16:11] mipmap-hdpi +│   │   ├── [2.9K Feb 18 16:11] ic_launcher.png +│   │   └── [4.8K Feb 18 16:11] ic_launcher_round.png +│   ├── [ 128 Feb 18 16:11] mipmap-mdpi +│   │   ├── [2.0K Feb 18 16:11] ic_launcher.png +│   │   └── [2.7K Feb 18 16:11] ic_launcher_round.png +│   ├── [ 128 Feb 18 16:11] mipmap-xhdpi +│   │   ├── [4.4K Feb 18 16:11] ic_launcher.png +│   │   └── [6.7K Feb 18 16:11] ic_launcher_round.png +│   ├── [ 128 Feb 18 16:11] mipmap-xxhdpi +│   │   ├── [6.2K Feb 18 16:11] ic_launcher.png +│   │   └── [ 10K Feb 18 16:11] ic_launcher_round.png +│   ├── [ 128 Feb 18 16:11] mipmap-xxxhdpi +│   │   ├── [8.9K Feb 18 16:11] ic_launcher.png +│   │   └── [ 15K Feb 18 16:11] ic_launcher_round.png +│   ├── [ 96 Feb 18 16:11] navigation +│   │   └── [1.0K Feb 18 16:11] nav_graph.xml +│   ├── [ 192 Feb 18 16:11] values +│   │   ├── [ 378 Feb 18 16:11] colors.xml +│   │   ├── [ 66 Feb 18 16:11] dimens.xml +│   │   ├── [ 552 Feb 18 16:11] strings.xml +│   │   └── [1.2K Feb 18 16:11] themes.xml +│   ├── [ 96 Feb 18 16:11] values-land +│   │   └── [ 66 Feb 18 16:11] dimens.xml +│   ├── [ 96 Feb 18 16:11] values-night +│   │   └── [ 836 Feb 18 16:11] themes.xml +│   ├── [ 96 Feb 18 16:11] values-w1240dp +│   │   └── [ 67 Feb 18 16:11] dimens.xml +│   ├── [ 96 Feb 18 16:11] values-w600dp +│   │   └── [ 66 Feb 18 16:11] dimens.xml +│   └── [ 128 Feb 18 16:11] xml +│   ├── [ 478 Feb 18 16:11] backup_rules.xml +│   └── [ 551 Feb 18 16:11] data_extraction_rules.xml +├── [ 256 May 27 19:34] sentry-android-navigation +│   ├── [ 7 Feb 18 16:11] .gitignore +│   ├── [ 96 Feb 18 16:11] api +│   │   └── [1.1K Feb 18 16:11] sentry-android-navigation.api +│   ├── [2.2K May 27 19:34] build.gradle.kts +│   ├── [ 306 Feb 18 16:11] proguard-rules.pro +│   └── [ 128 Feb 18 16:11] src +│   ├── [ 128 Feb 18 16:11] main +│   │   ├── [ 96 Feb 18 16:11] java +│   │   │   └── [ 96 Feb 18 16:11] io +│   │   │   └── [ 96 Feb 18 16:11] sentry +│   │   │   └── [ 96 Feb 18 16:11] android +│   │   │   └── [ 96 May 27 19:34] navigation +│   │   │   └── [6.8K May 27 19:34] SentryNavigationListener.kt +│   │   └── [ 96 Feb 18 16:11] res +│   │   └── [ 96 Feb 18 16:11] values +│   │   └── [ 77 Feb 18 16:11] public.xml +│   └── [ 96 Feb 18 16:11] test +│   └── [ 96 Feb 18 16:11] java +│   └── [ 96 Feb 18 16:11] io +│   └── [ 96 Feb 18 16:11] sentry +│   └── [ 96 Feb 18 16:11] android +│   └── [ 96 Feb 18 16:11] navigation +│   └── [ 15K Feb 18 16:11] SentryNavigationListenerTest.kt +├── [ 224 May 27 19:34] sentry-android-ndk +│   ├── [ 96 May 27 19:34] api +│   │   └── [1.3K May 27 19:34] sentry-android-ndk.api +│   ├── [2.4K May 27 19:34] build.gradle.kts +│   ├── [ 781 Feb 18 16:11] proguard-rules.pro +│   └── [ 128 Feb 18 16:11] src +│   ├── [ 128 Feb 18 16:11] main +│   │   ├── [ 96 Feb 18 16:11] java +│   │   │   └── [ 96 Feb 18 16:11] io +│   │   │   └── [ 96 Feb 18 16:11] sentry +│   │   │   └── [ 96 Feb 18 16:11] android +│   │   │   └── [ 192 May 27 19:34] ndk +│   │   │   ├── [6.6K Feb 18 16:11] DebugImagesLoader.java +│   │   │   ├── [4.7K May 27 19:34] NdkScopeObserver.java +│   │   │   ├── [3.8K May 27 19:34] SentryNdk.java +│   │   │   └── [ 821 May 27 19:34] SentryNdkUtil.java +│   │   └── [ 96 Feb 18 16:11] res +│   │   └── [ 96 Feb 18 16:11] values +│   │   └── [ 77 Feb 18 16:11] public.xml +│   └── [ 128 Feb 18 16:11] test +│   ├── [ 96 Feb 18 16:11] java +│   │   └── [ 96 Feb 18 16:11] io +│   │   └── [ 96 Feb 18 16:11] sentry +│   │   └── [ 96 Feb 18 16:11] android +│   │   └── [ 192 May 27 19:34] ndk +│   │   ├── [5.3K Feb 18 16:11] DebugImagesLoaderTest.kt +│   │   ├── [4.4K Feb 18 16:11] NdkScopeObserverTest.kt +│   │   ├── [2.1K May 27 19:34] SentryNdkTest.kt +│   │   └── [ 844 Feb 18 16:11] SentryNdkUtilTest.kt +│   └── [ 96 Feb 18 16:11] resources +│   └── [ 96 Feb 18 16:11] mockito-extensions +│   └── [ 19 Feb 18 16:11] org.mockito.plugins.MockMaker +├── [ 256 May 27 19:34] sentry-android-replay +│   ├── [ 6 Feb 18 16:11] .gitignore +│   ├── [ 96 May 27 19:34] api +│   │   └── [8.2K May 27 19:34] sentry-android-replay.api +│   ├── [3.1K May 27 19:34] build.gradle.kts +│   ├── [1.3K Feb 18 16:11] proguard-rules.pro +│   └── [ 128 Feb 18 16:11] src +│   ├── [ 160 Feb 18 16:11] main +│   │   ├── [ 96 Feb 18 16:11] java +│   │   │   └── [ 96 Feb 18 16:11] io +│   │   │   └── [ 96 Feb 18 16:11] sentry +│   │   │   └── [ 96 Feb 18 16:11] android +│   │   │   └── [ 576 May 27 19:34] replay +│   │   │   ├── [6.4K Feb 18 16:11] DefaultReplayBreadcrumbConverter.kt +│   │   │   ├── [ 785 Feb 18 16:11] ModifierExtensions.kt +│   │   │   ├── [ 523 Feb 18 16:11] Recorder.kt +│   │   │   ├── [ 17K Feb 18 16:11] ReplayCache.kt +│   │   │   ├── [ 18K May 27 19:34] ReplayIntegration.kt +│   │   │   ├── [2.0K Feb 18 16:11] ReplayLifecycle.kt +│   │   │   ├── [ 14K May 27 19:34] ScreenshotRecorder.kt +│   │   │   ├── [1.2K Feb 18 16:11] SessionReplayOptions.kt +│   │   │   ├── [ 411 Feb 18 16:11] ViewExtensions.kt +│   │   │   ├── [3.6K Feb 18 16:11] WindowRecorder.kt +│   │   │   ├── [7.7K Feb 18 16:11] Windows.kt +│   │   │   ├── [ 192 May 27 19:34] capture +│   │   │   │   ├── [8.8K Feb 18 16:11] BaseCaptureStrategy.kt +│   │   │   │   ├── [8.2K Feb 18 16:11] BufferCaptureStrategy.kt +│   │   │   │   ├── [8.5K May 27 19:34] CaptureStrategy.kt +│   │   │   │   └── [6.1K May 27 19:34] SessionCaptureStrategy.kt +│   │   │   ├── [ 128 Feb 18 16:11] gestures +│   │   │   │   ├── [3.0K Feb 18 16:11] GestureRecorder.kt +│   │   │   │   └── [6.1K Feb 18 16:11] ReplayGestureConverter.kt +│   │   │   ├── [ 384 May 27 19:34] util +│   │   │   │   ├── [ 139 Feb 18 16:11] Context.kt +│   │   │   │   ├── [2.8K May 27 19:34] DebugOverlayDrawable.kt +│   │   │   │   ├── [2.6K Feb 18 16:11] Executors.kt +│   │   │   │   ├── [6.2K Feb 18 16:11] FixedWindowCallback.java +│   │   │   │   ├── [ 276 Feb 18 16:11] MainLooperHandler.kt +│   │   │   │   ├── [8.7K Feb 18 16:11] Nodes.kt +│   │   │   │   ├── [2.0K Feb 18 16:11] Persistable.kt +│   │   │   │   ├── [ 226 Feb 18 16:11] Sampling.kt +│   │   │   │   ├── [ 728 Feb 18 16:11] TextLayout.kt +│   │   │   │   └── [7.7K Feb 18 16:11] Views.kt +│   │   │   ├── [ 160 May 27 19:34] video +│   │   │   │   ├── [2.1K Feb 18 16:11] SimpleFrameMuxer.kt +│   │   │   │   ├── [3.6K Feb 18 16:11] SimpleMp4FrameMuxer.kt +│   │   │   │   └── [ 13K May 27 19:34] SimpleVideoEncoder.kt +│   │   │   └── [ 128 May 27 19:34] viewhierarchy +│   │   │   ├── [9.5K May 27 19:34] ComposeViewHierarchyNode.kt +│   │   │   └── [ 13K Feb 18 16:11] ViewHierarchyNode.kt +│   │   ├── [ 96 Feb 18 16:11] res +│   │   │   └── [ 96 Feb 18 16:11] values +│   │   │   └── [ 166 Feb 18 16:11] public.xml +│   │   └── [ 96 Feb 18 16:11] resources +│   │   └── [ 96 Feb 18 16:11] META-INF +│   │   └── [ 96 Feb 18 16:11] io +│   │   └── [ 96 Feb 18 16:11] sentry +│   │   └── [ 96 Feb 18 16:11] sentry-android-replay +│   │   └── [ 140 Feb 18 16:11] verification.properties +│   └── [ 160 Feb 18 16:11] test +│   ├── [ 756 Feb 18 16:11] AndroidManifest.xml +│   ├── [ 96 Feb 18 16:11] java +│   │   └── [ 96 Feb 18 16:11] io +│   │   └── [ 96 Feb 18 16:11] sentry +│   │   └── [ 96 Feb 18 16:11] android +│   │   └── [ 416 May 27 19:34] replay +│   │   ├── [ 10K Feb 18 16:11] AnrWithReplayIntegrationTest.kt +│   │   ├── [9.3K Feb 18 16:11] DefaultReplayBreadcrumbConverterTest.kt +│   │   ├── [ 19K Feb 18 16:11] ReplayCacheTest.kt +│   │   ├── [ 31K May 27 19:34] ReplayIntegrationTest.kt +│   │   ├── [6.3K Feb 18 16:11] ReplayIntegrationWithRecorderTest.kt +│   │   ├── [4.0K Feb 18 16:11] ReplayLifecycleTest.kt +│   │   ├── [9.8K Feb 18 16:11] ReplaySmokeTest.kt +│   │   ├── [ 128 May 27 19:34] capture +│   │   │   ├── [ 10K Feb 18 16:11] BufferCaptureStrategyTest.kt +│   │   │   └── [ 17K May 27 19:34] SessionCaptureStrategyTest.kt +│   │   ├── [ 128 Feb 18 16:11] gestures +│   │   │   ├── [4.7K Feb 18 16:11] GestureRecorderTest.kt +│   │   │   └── [9.3K Feb 18 16:11] ReplayGestureConverterTest.kt +│   │   ├── [ 128 Feb 18 16:11] util +│   │   │   ├── [1.9K Feb 18 16:11] ReplayShadowMediaCodec.kt +│   │   │   └── [3.8K Feb 18 16:11] TextViewDominantColorTest.kt +│   │   └── [ 160 May 27 19:34] viewhierarchy +│   │   ├── [ 12K May 27 19:34] ComposeMaskingOptionsTest.kt +│   │   ├── [8.2K Feb 18 16:11] ContainerMaskingOptionsTest.kt +│   │   └── [ 10K Feb 18 16:11] MaskingOptionsTest.kt +│   └── [ 96 Feb 18 16:11] resources +│   └── [234K Feb 18 16:11] Tongariro.jpg +├── [ 192 May 27 19:34] sentry-android-sqlite +│   ├── [ 96 Feb 18 16:11] api +│   │   └── [1.2K Feb 18 16:11] sentry-android-sqlite.api +│   ├── [2.1K May 27 19:34] build.gradle.kts +│   ├── [ 304 Feb 18 16:11] proguard-rules.pro +│   └── [ 128 Feb 18 16:11] src +│   ├── [ 128 Feb 18 16:11] main +│   │   ├── [ 96 Feb 18 16:11] java +│   │   │   └── [ 96 Feb 18 16:11] io +│   │   │   └── [ 96 Feb 18 16:11] sentry +│   │   │   └── [ 96 Feb 18 16:11] android +│   │   │   └── [ 224 Feb 18 16:11] sqlite +│   │   │   ├── [3.2K Feb 18 16:11] SQLiteSpanManager.kt +│   │   │   ├── [1.7K Feb 18 16:11] SentryCrossProcessCursor.kt +│   │   │   ├── [2.7K Feb 18 16:11] SentrySupportSQLiteDatabase.kt +│   │   │   ├── [2.3K Feb 18 16:11] SentrySupportSQLiteOpenHelper.kt +│   │   │   └── [1.5K Feb 18 16:11] SentrySupportSQLiteStatement.kt +│   │   └── [ 96 Feb 18 16:11] res +│   │   └── [ 96 Feb 18 16:11] values +│   │   └── [ 77 Feb 18 16:11] public.xml +│   └── [ 128 Feb 18 16:11] test +│   ├── [ 96 Feb 18 16:11] java +│   │   └── [ 96 Feb 18 16:11] io +│   │   └── [ 96 Feb 18 16:11] sentry +│   │   └── [ 96 Feb 18 16:11] android +│   │   └── [ 224 May 27 19:34] sqlite +│   │   ├── [5.5K May 27 19:34] SQLiteSpanManagerTest.kt +│   │   ├── [3.9K Feb 18 16:11] SentryCrossProcessCursorTest.kt +│   │   ├── [8.0K Feb 18 16:11] SentrySupportSQLiteDatabaseTest.kt +│   │   ├── [2.2K Feb 18 16:11] SentrySupportSQLiteOpenHelperTest.kt +│   │   └── [6.2K Feb 18 16:11] SentrySupportSQLiteStatementTest.kt +│   └── [ 96 Feb 18 16:11] resources +│   └── [ 96 Feb 18 16:11] mockito-extensions +│   └── [ 18 Feb 18 16:11] org.mockito.plugins.MockMaker +├── [ 224 May 27 19:34] sentry-android-timber +│   ├── [ 96 Feb 18 16:11] api +│   │   └── [2.3K Feb 18 16:11] sentry-android-timber.api +│   ├── [2.3K May 27 19:34] build.gradle.kts +│   ├── [ 503 Feb 18 16:11] proguard-rules.pro +│   └── [ 128 Feb 18 16:11] src +│   ├── [ 128 Feb 18 16:11] main +│   │   ├── [ 96 Feb 18 16:11] java +│   │   │   └── [ 96 Feb 18 16:11] io +│   │   │   └── [ 96 Feb 18 16:11] sentry +│   │   │   └── [ 96 Feb 18 16:11] android +│   │   │   └── [ 128 May 27 19:34] timber +│   │   │   ├── [1.5K May 27 19:34] SentryTimberIntegration.kt +│   │   │   └── [8.4K Feb 18 16:11] SentryTimberTree.kt +│   │   └── [ 96 Feb 18 16:11] res +│   │   └── [ 96 Feb 18 16:11] values +│   │   └── [ 77 Feb 18 16:11] public.xml +│   └── [ 128 Feb 18 16:11] test +│   ├── [ 96 Feb 18 16:11] java +│   │   └── [ 96 Feb 18 16:11] io +│   │   └── [ 96 Feb 18 16:11] sentry +│   │   └── [ 96 Feb 18 16:11] android +│   │   └── [ 128 Feb 18 16:11] timber +│   │   ├── [3.1K Feb 18 16:11] SentryTimberIntegrationTest.kt +│   │   └── [7.7K Feb 18 16:11] SentryTimberTreeTest.kt +│   └── [ 96 Feb 18 16:11] resources +│   └── [ 96 Feb 18 16:11] mockito-extensions +│   └── [ 18 Feb 18 16:11] org.mockito.plugins.MockMaker +├── [ 160 May 27 19:34] sentry-apache-http-client-5 +│   ├── [ 96 Feb 18 16:11] api +│   │   └── [ 776 Feb 18 16:11] sentry-apache-http-client-5.api +│   ├── [1.7K May 27 19:34] build.gradle.kts +│   └── [ 128 Feb 18 16:11] src +│   ├── [ 96 Feb 18 16:11] main +│   │   └── [ 96 Feb 18 16:11] java +│   │   └── [ 96 Feb 18 16:11] io +│   │   └── [ 96 Feb 18 16:11] sentry +│   │   └── [ 96 Feb 18 16:11] transport +│   │   └── [ 128 Feb 18 16:11] apache +│   │   ├── [8.8K Feb 18 16:11] ApacheHttpClientTransport.java +│   │   └── [3.3K Feb 18 16:11] ApacheHttpClientTransportFactory.java +│   └── [ 128 Feb 18 16:11] test +│   ├── [ 96 Feb 18 16:11] kotlin +│   │   └── [ 96 Feb 18 16:11] io +│   │   └── [ 128 Feb 18 16:11] sentry +│   │   ├── [ 318 Feb 18 16:11] SentryOptionsManipulator.kt +│   │   └── [ 96 Feb 18 16:11] transport +│   │   └── [ 160 Feb 18 16:11] apache +│   │   ├── [9.4K Feb 18 16:11] ApacheHttpClientTransportClientReportTest.kt +│   │   ├── [1.5K Feb 18 16:11] ApacheHttpClientTransportFactoryTest.kt +│   │   └── [8.3K Feb 18 16:11] ApacheHttpClientTransportTest.kt +│   └── [ 96 Feb 18 16:11] resources +│   └── [ 96 Feb 18 16:11] mockito-extensions +│   └── [ 18 Feb 18 16:11] org.mockito.plugins.MockMaker +├── [ 192 May 27 20:08] sentry-apollo +│   ├── [ 96 Feb 18 16:11] api +│   │   └── [1.3K Feb 18 16:11] sentry-apollo.api +│   ├── [2.2K May 27 19:34] build.gradle.kts +│   └── [ 128 Feb 18 16:11] src +│   ├── [ 96 Feb 18 16:11] main +│   │   └── [ 96 Feb 18 16:11] java +│   │   └── [ 96 Feb 18 16:11] io +│   │   └── [ 96 Feb 18 16:11] sentry +│   │   └── [ 96 May 27 19:34] apollo +│   │   └── [8.7K May 27 19:34] SentryApolloInterceptor.kt +│   └── [ 128 Feb 18 16:11] test +│   ├── [ 96 Feb 18 16:11] java +│   │   └── [ 96 Feb 18 16:11] io +│   │   └── [ 128 Feb 18 16:11] sentry +│   │   ├── [ 160 Feb 18 16:11] apollo +│   │   │   ├── [ 16K Feb 18 16:11] LaunchDetailsQuery.java +│   │   │   ├── [ 10K Feb 18 16:11] SentryApolloInterceptorTest.kt +│   │   │   └── [ 96 Feb 18 16:11] type +│   │   │   └── [ 485 Feb 18 16:11] CustomType.java +│   │   └── [ 96 Feb 18 16:11] util +│   │   └── [ 158 Feb 18 16:11] ApolloPlatformTestManipulator.kt +│   └── [ 96 Feb 18 16:11] resources +│   └── [ 96 Feb 18 16:11] mockito-extensions +│   └── [ 18 Feb 18 16:11] org.mockito.plugins.MockMaker +├── [ 192 May 27 20:08] sentry-apollo-3 +│   ├── [ 96 Feb 18 16:11] api +│   │   └── [4.0K Feb 18 16:11] sentry-apollo-3.api +│   ├── [2.2K May 27 19:34] build.gradle.kts +│   └── [ 128 Feb 18 16:11] src +│   ├── [ 96 Feb 18 16:11] main +│   │   └── [ 96 Feb 18 16:11] java +│   │   └── [ 96 Feb 18 16:11] io +│   │   └── [ 96 Feb 18 16:11] sentry +│   │   └── [ 192 May 27 19:34] apollo3 +│   │   ├── [ 353 Feb 18 16:11] SentryApollo3ClientException.kt +│   │   ├── [ 16K May 27 19:34] SentryApollo3HttpInterceptor.kt +│   │   ├── [1.8K Feb 18 16:11] SentryApollo3Interceptor.kt +│   │   └── [1.4K Feb 18 16:11] SentryApolloBuilderExtensions.kt +│   └── [ 128 Feb 18 16:11] test +│   ├── [ 96 Feb 18 16:11] java +│   │   └── [ 96 Feb 18 16:11] io +│   │   └── [ 128 Feb 18 16:11] sentry +│   │   ├── [ 288 May 27 19:34] apollo3 +│   │   │   ├── [2.7K Feb 18 16:11] LaunchDetailsQuery.kt +│   │   │   ├── [ 12K Feb 18 16:11] SentryApollo3InterceptorClientErrors.kt +│   │   │   ├── [ 14K May 27 19:34] SentryApollo3InterceptorTest.kt +│   │   │   ├── [6.2K Feb 18 16:11] SentryApollo3InterceptorWithVariablesTest.kt +│   │   │   ├── [ 128 Feb 18 16:11] adapter +│   │   │   │   ├── [6.0K Feb 18 16:11] LaunchDetailsQuery_ResponseAdapter.kt +│   │   │   │   └── [1015 Feb 18 16:11] LaunchDetailsQuery_VariablesAdapter.kt +│   │   │   ├── [ 96 Feb 18 16:11] selections +│   │   │   │   └── [2.3K Feb 18 16:11] LaunchDetailsQuerySelections.kt +│   │   │   └── [ 288 Feb 18 16:11] type +│   │   │   ├── [ 437 Feb 18 16:11] GraphQLBoolean.kt +│   │   │   ├── [ 712 Feb 18 16:11] GraphQLID.kt +│   │   │   ├── [ 563 Feb 18 16:11] GraphQLString.kt +│   │   │   ├── [ 332 Feb 18 16:11] Launch.kt +│   │   │   ├── [ 334 Feb 18 16:11] Mission.kt +│   │   │   ├── [ 330 Feb 18 16:11] Query.kt +│   │   │   └── [ 332 Feb 18 16:11] Rocket.kt +│   │   └── [ 96 Feb 18 16:11] util +│   │   └── [ 159 Feb 18 16:11] Apollo3PlatformTestManipulator.kt +│   └── [ 96 Feb 18 16:11] resources +│   └── [ 96 Feb 18 16:11] mockito-extensions +│   └── [ 18 Feb 18 16:11] org.mockito.plugins.MockMaker +├── [ 224 May 27 20:08] sentry-apollo-4 +│   ├── [ 381 May 27 19:34] README.md +│   ├── [ 96 May 27 19:34] api +│   │   └── [3.9K May 27 19:34] sentry-apollo-4.api +│   ├── [2.4K May 27 19:34] build.gradle.kts +│   └── [ 128 May 27 19:34] src +│   ├── [ 96 May 27 19:34] main +│   │   └── [ 96 May 27 19:34] java +│   │   └── [ 96 May 27 19:34] io +│   │   └── [ 96 May 27 19:34] sentry +│   │   └── [ 224 May 27 19:34] apollo4 +│   │   ├── [ 589 May 27 19:34] SentryApollo4.kt +│   │   ├── [ 353 May 27 19:34] SentryApollo4ClientException.kt +│   │   ├── [ 16K May 27 19:34] SentryApollo4HttpInterceptor.kt +│   │   ├── [2.2K May 27 19:34] SentryApollo4Interceptor.kt +│   │   └── [1.4K May 27 19:34] SentryApolloBuilderExtensions.kt +│   └── [ 128 May 27 19:34] test +│   ├── [ 96 May 27 19:34] java +│   │   └── [ 96 May 27 19:34] io +│   │   └── [ 128 May 27 19:34] sentry +│   │   ├── [ 192 May 27 19:34] apollo4 +│   │   │   ├── [ 12K May 27 19:34] SentryApollo4BuilderExtensionsClientErrorsTest.kt +│   │   │   ├── [7.3K May 27 19:34] SentryApollo4BuilderExtensionsTest.kt +│   │   │   ├── [ 14K May 27 19:34] SentryApollo4HttpInterceptorTest.kt +│   │   │   └── [ 192 May 27 19:34] generated +│   │   │   ├── [2.8K May 27 19:34] LaunchDetailsQuery.kt +│   │   │   ├── [ 128 May 27 19:34] adapter +│   │   │   │   ├── [6.0K May 27 19:34] LaunchDetailsQuery_ResponseAdapter.kt +│   │   │   │   └── [ 994 May 27 19:34] LaunchDetailsQuery_VariablesAdapter.kt +│   │   │   ├── [ 96 May 27 19:34] selections +│   │   │   │   └── [2.5K May 27 19:34] LaunchDetailsQuerySelections.kt +│   │   │   └── [ 288 May 27 19:34] type +│   │   │   ├── [ 446 May 27 19:34] GraphQLBoolean.kt +│   │   │   ├── [ 721 May 27 19:34] GraphQLID.kt +│   │   │   ├── [ 572 May 27 19:34] GraphQLString.kt +│   │   │   ├── [ 357 May 27 19:34] Launch.kt +│   │   │   ├── [ 359 May 27 19:34] Mission.kt +│   │   │   ├── [ 355 May 27 19:34] Query.kt +│   │   │   └── [ 357 May 27 19:34] Rocket.kt +│   │   └── [ 96 May 27 19:34] util +│   │   └── [ 159 May 27 19:34] Apollo4PlatformTestManipulator.kt +│   └── [ 96 May 27 19:34] resources +│   └── [ 96 May 27 19:34] mockito-extensions +│   └── [ 18 May 27 19:34] org.mockito.plugins.MockMaker +├── [ 96 May 27 19:34] sentry-bom +│   └── [ 945 May 27 19:34] build.gradle.kts +├── [ 320 May 27 19:34] sentry-compose +│   ├── [ 7 Feb 18 16:11] .gitignore +│   ├── [ 163 Feb 18 16:11] README.md +│   ├── [ 128 Feb 18 16:11] api +│   │   ├── [ 96 May 27 19:34] android +│   │   │   └── [2.3K May 27 19:34] sentry-compose.api +│   │   └── [ 96 Feb 18 16:11] desktop +│   │   └── [ 0 Feb 18 16:11] sentry-compose.api +│   ├── [3.8K May 27 19:34] build.gradle.kts +│   ├── [ 76 Feb 18 16:11] gradle.properties +│   ├── [1.6K May 27 19:34] proguard-rules.pro +│   └── [ 128 Feb 18 16:11] src +│   ├── [ 128 Feb 18 16:11] androidMain +│   │   ├── [ 96 Feb 18 16:11] kotlin +│   │   │   └── [ 96 Feb 18 16:11] io +│   │   │   └── [ 96 Feb 18 16:11] sentry +│   │   │   └── [ 256 May 27 19:34] compose +│   │   │   ├── [6.2K May 27 19:34] SentryComposeHelper.kt +│   │   │   ├── [3.2K Feb 18 16:11] SentryComposeTracing.kt +│   │   │   ├── [2.2K May 27 19:34] SentryModifier.kt +│   │   │   ├── [4.6K May 27 19:34] SentryNavigationIntegration.kt +│   │   │   ├── [ 96 May 27 19:34] gestures +│   │   │   │   └── [5.0K May 27 19:34] ComposeGestureTargetLocator.kt +│   │   │   └── [ 96 May 27 19:34] viewhierarchy +│   │   │   └── [2.9K May 27 19:34] ComposeViewHierarchyExporter.kt +│   │   └── [ 96 Feb 18 16:11] res +│   │   └── [ 96 Feb 18 16:11] values +│   │   └── [ 77 Feb 18 16:11] public.xml +│   └── [ 96 Feb 18 16:11] androidUnitTest +│   └── [ 96 Feb 18 16:11] kotlin +│   └── [ 96 Feb 18 16:11] io +│   └── [ 96 Feb 18 16:11] sentry +│   └── [ 192 May 27 19:34] compose +│   ├── [3.6K May 27 19:34] ComposeIntegrationTests.kt +│   ├── [1.3K Feb 18 16:11] SentryLifecycleObserverTest.kt +│   ├── [1.9K May 27 19:34] SentryModifierComposeTest.kt +│   └── [ 96 May 27 19:34] viewhierarchy +│   └── [4.4K May 27 19:34] ComposeViewHierarchyExporterTest.kt +├── [ 96 May 27 19:34] sentry-compose-helper +├── [ 192 May 27 20:08] sentry-graphql +│   ├── [ 96 Feb 18 16:11] api +│   │   └── [1.9K Feb 18 16:11] sentry-graphql.api +│   ├── [2.8K May 27 19:34] build.gradle.kts +│   └── [ 128 Feb 18 16:11] src +│   ├── [ 96 Feb 18 16:11] main +│   │   └── [ 96 Feb 18 16:11] java +│   │   └── [ 96 Feb 18 16:11] io +│   │   └── [ 96 Feb 18 16:11] sentry +│   │   └── [ 96 May 27 19:34] graphql +│   │   └── [6.6K May 27 19:34] SentryInstrumentation.java +│   └── [ 96 Feb 18 16:11] test +│   └── [ 96 Feb 18 16:11] kotlin +│   └── [ 96 Feb 18 16:11] io +│   └── [ 96 Feb 18 16:11] sentry +│   └── [ 128 Feb 18 16:11] graphql +│   ├── [ 18K Feb 18 16:11] SentryInstrumentationAnotherTest.kt +│   └── [8.8K Feb 18 16:11] SentryInstrumentationTest.kt +├── [ 192 May 27 20:08] sentry-graphql-22 +│   ├── [ 96 Feb 18 16:11] api +│   │   └── [2.2K Feb 18 16:11] sentry-graphql-22.api +│   ├── [2.8K May 27 19:34] build.gradle.kts +│   └── [ 128 Feb 18 16:11] src +│   ├── [ 96 Feb 18 16:11] main +│   │   └── [ 96 Feb 18 16:11] java +│   │   └── [ 96 Feb 18 16:11] io +│   │   └── [ 96 Feb 18 16:11] sentry +│   │   └── [ 96 May 27 19:34] graphql22 +│   │   └── [7.1K May 27 19:34] SentryInstrumentation.java +│   └── [ 96 Feb 18 16:11] test +│   └── [ 96 Feb 18 16:11] kotlin +│   └── [ 96 Feb 18 16:11] io +│   └── [ 96 Feb 18 16:11] sentry +│   └── [ 128 Feb 18 16:11] graphql22 +│   ├── [ 19K Feb 18 16:11] SentryInstrumentationAnotherTest.kt +│   └── [9.2K Feb 18 16:11] SentryInstrumentationTest.kt +├── [ 192 May 27 20:08] sentry-graphql-core +│   ├── [ 96 Feb 18 16:11] api +│   │   └── [4.6K Feb 18 16:11] sentry-graphql-core.api +│   ├── [2.7K May 27 19:34] build.gradle.kts +│   └── [ 128 Feb 18 16:11] src +│   ├── [ 96 Feb 18 16:11] main +│   │   └── [ 96 Feb 18 16:11] java +│   │   └── [ 96 Feb 18 16:11] io +│   │   └── [ 96 Feb 18 16:11] sentry +│   │   └── [ 288 Feb 18 16:11] graphql +│   │   ├── [5.7K Feb 18 16:11] ExceptionReporter.java +│   │   ├── [1.1K Feb 18 16:11] GraphqlStringUtils.java +│   │   ├── [ 761 Feb 18 16:11] NoOpSubscriptionHandler.java +│   │   ├── [1.9K Feb 18 16:11] SentryGenericDataFetcherExceptionHandler.java +│   │   ├── [2.0K Feb 18 16:11] SentryGraphqlExceptionHandler.java +│   │   ├── [ 13K Feb 18 16:11] SentryGraphqlInstrumentation.java +│   │   └── [ 452 Feb 18 16:11] SentrySubscriptionHandler.java +│   └── [ 96 Feb 18 16:11] test +│   └── [ 96 Feb 18 16:11] kotlin +│   └── [ 96 Feb 18 16:11] io +│   └── [ 96 Feb 18 16:11] sentry +│   └── [ 160 Feb 18 16:11] graphql +│   ├── [9.7K Feb 18 16:11] ExceptionReporterTest.kt +│   ├── [1.8K Feb 18 16:11] GraphqlStringUtilsTest.kt +│   └── [1.5K Feb 18 16:11] SentryGenericDataFetcherExceptionHandlerTest.kt +├── [ 192 May 27 20:08] sentry-jdbc +│   ├── [ 96 Feb 18 16:11] api +│   │   └── [1.0K Feb 18 16:11] sentry-jdbc.api +│   ├── [2.4K May 27 19:34] build.gradle.kts +│   └── [ 128 Feb 18 16:11] src +│   ├── [ 128 Feb 18 16:11] main +│   │   ├── [ 96 Feb 18 16:11] java +│   │   │   └── [ 96 Feb 18 16:11] io +│   │   │   └── [ 96 Feb 18 16:11] sentry +│   │   │   └── [ 128 May 27 19:34] jdbc +│   │   │   ├── [5.4K Feb 18 16:11] DatabaseUtils.java +│   │   │   └── [3.7K May 27 19:34] SentryJdbcEventListener.java +│   │   └── [ 96 Feb 18 16:11] resources +│   │   └── [ 96 Feb 18 16:11] META-INF +│   │   └── [ 96 Feb 18 16:11] services +│   │   └── [ 39 Feb 18 16:11] com.p6spy.engine.event.JdbcEventListener +│   └── [ 96 Feb 18 16:11] test +│   └── [ 96 Feb 18 16:11] kotlin +│   └── [ 96 Feb 18 16:11] io +│   └── [ 96 Feb 18 16:11] sentry +│   └── [ 128 Feb 18 16:11] jdbc +│   ├── [8.4K Feb 18 16:11] DatabaseUtilsTest.kt +│   └── [6.2K Feb 18 16:11] SentryJdbcEventListenerTest.kt +├── [ 192 May 27 20:08] sentry-jul +│   ├── [ 96 May 27 19:34] api +│   │   └── [ 901 May 27 19:34] sentry-jul.api +│   ├── [2.6K May 27 19:34] build.gradle.kts +│   └── [ 128 Feb 18 16:11] src +│   ├── [ 96 Feb 18 16:11] main +│   │   └── [ 96 Feb 18 16:11] java +│   │   └── [ 96 Feb 18 16:11] io +│   │   └── [ 96 Feb 18 16:11] sentry +│   │   └── [ 96 May 27 19:34] jul +│   │   └── [ 12K May 27 19:34] SentryHandler.java +│   └── [ 128 Feb 18 16:11] test +│   ├── [ 96 Feb 18 16:11] kotlin +│   │   └── [ 96 Feb 18 16:11] io +│   │   └── [ 96 Feb 18 16:11] sentry +│   │   └── [ 96 May 27 19:34] jul +│   │   └── [ 14K May 27 19:34] SentryHandlerTest.kt +│   └── [ 160 Feb 18 16:11] resources +│   ├── [ 289 Feb 18 16:11] logging.properties +│   ├── [ 96 Feb 18 16:11] mockito-extensions +│   │   └── [ 18 Feb 18 16:11] org.mockito.plugins.MockMaker +│   └── [ 39 Feb 18 16:11] sentry.properties +├── [ 160 May 27 19:34] sentry-kotlin-extensions +│   ├── [ 96 May 27 19:34] api +│   │   └── [1.3K May 27 19:34] sentry-kotlin-extensions.api +│   ├── [2.2K May 27 19:34] build.gradle.kts +│   └── [ 128 Feb 18 16:11] src +│   ├── [ 96 Feb 18 16:11] main +│   │   └── [ 96 Feb 18 16:11] java +│   │   └── [ 96 Feb 18 16:11] io +│   │   └── [ 96 Feb 18 16:11] sentry +│   │   └── [ 128 May 27 19:34] kotlin +│   │   ├── [1.3K Feb 18 16:11] SentryContext.kt +│   │   └── [1.3K May 27 19:34] SentryCoroutineExceptionHandler.kt +│   └── [ 96 Feb 18 16:11] test +│   └── [ 96 Feb 18 16:11] java +│   └── [ 96 Feb 18 16:11] io +│   └── [ 96 Feb 18 16:11] sentry +│   └── [ 128 May 27 19:34] kotlin +│   ├── [9.8K Feb 18 16:11] SentryContextTest.kt +│   └── [2.1K May 27 19:34] SentryCoroutineExceptionHandlerTest.kt +├── [ 192 May 27 20:08] sentry-log4j2 +│   ├── [ 96 Feb 18 16:11] api +│   │   └── [1.1K Feb 18 16:11] sentry-log4j2.api +│   ├── [2.5K May 27 19:34] build.gradle.kts +│   └── [ 128 Feb 18 16:11] src +│   ├── [ 96 Feb 18 16:11] main +│   │   └── [ 96 Feb 18 16:11] java +│   │   └── [ 96 Feb 18 16:11] io +│   │   └── [ 96 Feb 18 16:11] sentry +│   │   └── [ 96 May 27 19:34] log4j2 +│   │   └── [ 10K May 27 19:34] SentryAppender.java +│   └── [ 128 Feb 18 16:11] test +│   ├── [ 96 Feb 18 16:11] kotlin +│   │   └── [ 96 Feb 18 16:11] io +│   │   └── [ 96 Feb 18 16:11] sentry +│   │   └── [ 96 Feb 18 16:11] log4j2 +│   │   └── [ 15K Feb 18 16:11] SentryAppenderTest.kt +│   └── [ 128 Feb 18 16:11] resources +│   ├── [ 96 Feb 18 16:11] mockito-extensions +│   │   └── [ 18 Feb 18 16:11] org.mockito.plugins.MockMaker +│   └── [ 39 Feb 18 16:11] sentry.properties +├── [ 192 May 27 20:08] sentry-logback +│   ├── [ 96 Feb 18 16:11] api +│   │   └── [1.1K Feb 18 16:11] sentry-logback.api +│   ├── [2.4K May 27 19:34] build.gradle.kts +│   └── [ 128 Feb 18 16:11] src +│   ├── [ 96 Feb 18 16:11] main +│   │   └── [ 96 Feb 18 16:11] java +│   │   └── [ 96 Feb 18 16:11] io +│   │   └── [ 96 Feb 18 16:11] sentry +│   │   └── [ 96 May 27 19:34] logback +│   │   └── [9.5K May 27 19:34] SentryAppender.java +│   └── [ 128 Feb 18 16:11] test +│   ├── [ 96 Feb 18 16:11] kotlin +│   │   └── [ 96 Feb 18 16:11] io +│   │   └── [ 96 Feb 18 16:11] sentry +│   │   └── [ 96 May 27 19:34] logback +│   │   └── [ 19K May 27 19:34] SentryAppenderTest.kt +│   └── [ 128 Feb 18 16:11] resources +│   ├── [ 96 Feb 18 16:11] mockito-extensions +│   │   └── [ 18 Feb 18 16:11] org.mockito.plugins.MockMaker +│   └── [ 39 Feb 18 16:11] sentry.properties +├── [ 192 May 27 19:34] sentry-okhttp +│   ├── [ 96 Feb 18 16:11] api +│   │   └── [3.7K Feb 18 16:11] sentry-okhttp.api +│   ├── [2.6K May 27 19:34] build.gradle.kts +│   └── [ 128 Feb 18 16:11] src +│   ├── [ 128 Feb 18 16:11] main +│   │   ├── [ 96 Feb 18 16:11] java +│   │   │   └── [ 96 Feb 18 16:11] io +│   │   │   └── [ 96 Feb 18 16:11] sentry +│   │   │   └── [ 192 May 27 19:34] okhttp +│   │   │   ├── [6.5K May 27 19:34] SentryOkHttpEvent.kt +│   │   │   ├── [ 14K Feb 18 16:11] SentryOkHttpEventListener.kt +│   │   │   ├── [9.7K May 27 19:34] SentryOkHttpInterceptor.kt +│   │   │   └── [3.4K Feb 18 16:11] SentryOkHttpUtils.kt +│   │   └── [ 96 Feb 18 16:11] resources +│   │   └── [ 96 Feb 18 16:11] META-INF +│   │   └── [ 96 Feb 18 16:11] proguard +│   │   └── [ 706 Feb 18 16:11] sentry-okhttp.pro +│   └── [ 128 Feb 18 16:11] test +│   ├── [ 96 Feb 18 16:11] java +│   │   └── [ 96 Feb 18 16:11] io +│   │   └── [ 96 Feb 18 16:11] sentry +│   │   └── [ 192 May 27 19:34] okhttp +│   │   ├── [ 15K Feb 18 16:11] SentryOkHttpEventListenerTest.kt +│   │   ├── [ 15K May 27 19:34] SentryOkHttpEventTest.kt +│   │   ├── [ 24K May 27 19:34] SentryOkHttpInterceptorTest.kt +│   │   └── [4.8K Feb 18 16:11] SentryOkHttpUtilsTest.kt +│   └── [ 96 Feb 18 16:11] resources +│   └── [ 96 Feb 18 16:11] mockito-extensions +│   └── [ 18 Feb 18 16:11] org.mockito.plugin.MockMaker +├── [ 160 May 27 19:34] sentry-openfeign +│   ├── [ 96 Feb 18 16:11] api +│   │   └── [ 793 Feb 18 16:11] sentry-openfeign.api +│   ├── [2.2K May 27 19:34] build.gradle.kts +│   └── [ 128 Feb 18 16:11] src +│   ├── [ 96 Feb 18 16:11] main +│   │   └── [ 96 Feb 18 16:11] java +│   │   └── [ 96 Feb 18 16:11] io +│   │   └── [ 96 Feb 18 16:11] sentry +│   │   └── [ 128 May 27 19:34] openfeign +│   │   ├── [1.0K Feb 18 16:11] SentryCapability.java +│   │   └── [6.8K May 27 19:34] SentryFeignClient.java +│   └── [ 128 Feb 18 16:11] test +│   ├── [ 96 Feb 18 16:11] kotlin +│   │   └── [ 96 Feb 18 16:11] io +│   │   └── [ 96 Feb 18 16:11] sentry +│   │   └── [ 96 Feb 18 16:11] openfeign +│   │   └── [ 12K Feb 18 16:11] SentryFeignClientTest.kt +│   └── [ 96 Feb 18 16:11] resources +│   └── [ 96 Feb 18 16:11] mockito-extensions +│   └── [ 18 Feb 18 16:11] org.mockito.plugins.MockMaker +├── [ 288 Feb 18 16:11] sentry-opentelemetry +│   ├── [3.3K Feb 18 16:11] README.md +│   ├── [ 160 May 27 19:34] sentry-opentelemetry-agent +│   │   ├── [3.3K Feb 18 16:11] README.md +│   │   ├── [7.0K May 27 19:34] build.gradle.kts +│   │   └── [ 96 Feb 18 16:11] src +│   │   └── [ 96 Feb 18 16:11] main +│   │   └── [ 96 Feb 18 16:11] java +│   │   └── [ 96 Feb 18 16:11] io +│   │   └── [ 96 Feb 18 16:11] sentry +│   │   └── [ 96 Feb 18 16:11] opentelemetry +│   │   └── [ 96 Feb 18 16:11] agent +│   │   └── [ 197 Feb 18 16:11] AgentMarker.java +│   ├── [ 160 May 27 19:34] sentry-opentelemetry-agentcustomization +│   │   ├── [ 96 Feb 18 16:11] api +│   │   │   └── [ 999 Feb 18 16:11] sentry-opentelemetry-agentcustomization.api +│   │   ├── [2.6K May 27 19:34] build.gradle.kts +│   │   └── [ 96 Feb 18 16:11] src +│   │   └── [ 128 Feb 18 16:11] main +│   │   ├── [ 96 Feb 18 16:11] java +│   │   │   └── [ 96 Feb 18 16:11] io +│   │   │   └── [ 96 Feb 18 16:11] sentry +│   │   │   └── [ 160 May 27 19:34] opentelemetry +│   │   │   ├── [4.1K May 27 19:34] SentryAutoConfigurationCustomizerProvider.java +│   │   │   ├── [ 819 Feb 18 16:11] SentryBootstrapPackagesProvider.java +│   │   │   └── [ 519 Feb 18 16:11] SentryPropagatorProvider.java +│   │   └── [ 96 Feb 18 16:11] resources +│   │   └── [ 96 Feb 18 16:11] META-INF +│   │   └── [ 160 Feb 18 16:11] services +│   │   ├── [ 56 Feb 18 16:11] io.opentelemetry.javaagent.tooling.bootstrap.BootstrapPackagesConfigurer +│   │   ├── [ 66 Feb 18 16:11] io.opentelemetry.sdk.autoconfigure.spi.AutoConfigurationCustomizerProvider +│   │   └── [ 49 Feb 18 16:11] io.opentelemetry.sdk.autoconfigure.spi.ConfigurablePropagatorProvider +│   ├── [ 192 May 27 20:08] sentry-opentelemetry-agentless +│   │   ├── [1.9K Feb 18 16:11] README.md +│   │   ├── [1.2K May 27 19:34] build.gradle.kts +│   │   └── [ 96 Feb 18 16:11] src +│   │   └── [ 96 Feb 18 16:11] main +│   │   └── [ 96 Feb 18 16:11] java +│   │   └── [ 96 Feb 18 16:11] io +│   │   └── [ 96 Feb 18 16:11] sentry +│   │   └── [ 96 Feb 18 16:11] opentelemetry +│   │   └── [ 96 May 27 19:34] agent +│   │   └── [ 346 May 27 19:34] AgentlessMarker.java +│   ├── [ 192 May 27 20:08] sentry-opentelemetry-agentless-spring +│   │   ├── [ 976 Feb 18 16:11] README.md +│   │   ├── [1.3K May 27 19:34] build.gradle.kts +│   │   └── [ 96 Feb 18 16:11] src +│   │   └── [ 96 Feb 18 16:11] main +│   │   └── [ 96 Feb 18 16:11] java +│   │   └── [ 96 Feb 18 16:11] io +│   │   └── [ 96 Feb 18 16:11] sentry +│   │   └── [ 96 Feb 18 16:11] opentelemetry +│   │   └── [ 96 May 27 19:34] agent +│   │   └── [ 379 May 27 19:34] AgentlessSpringMarker.java +│   ├── [ 160 May 27 19:34] sentry-opentelemetry-bootstrap +│   │   ├── [ 96 May 27 19:34] api +│   │   │   └── [ 11K May 27 19:34] sentry-opentelemetry-bootstrap.api +│   │   ├── [2.3K May 27 19:34] build.gradle.kts +│   │   └── [ 96 Feb 18 16:11] src +│   │   └── [ 128 Feb 18 16:11] main +│   │   ├── [ 96 Feb 18 16:11] java +│   │   │   └── [ 96 Feb 18 16:11] io +│   │   │   └── [ 96 Feb 18 16:11] sentry +│   │   │   └── [ 480 May 27 19:34] opentelemetry +│   │   │   ├── [1.2K May 27 19:34] IOtelSpanWrapper.java +│   │   │   ├── [1.2K Feb 18 16:11] InternalSemanticAttributes.java +│   │   │   ├── [1.8K Feb 18 16:11] OtelContextScopesStorage.java +│   │   │   ├── [7.0K May 27 19:34] OtelSpanFactory.java +│   │   │   ├── [ 483 Feb 18 16:11] OtelStorageToken.java +│   │   │   ├── [7.9K May 27 19:34] OtelStrongRefSpanWrapper.java +│   │   │   ├── [7.8K May 27 19:34] OtelTransactionSpanForwarder.java +│   │   │   ├── [1.4K Feb 18 16:11] SentryContextStorage.java +│   │   │   ├── [1.1K May 27 19:34] SentryContextStorageProvider.java +│   │   │   ├── [3.1K Feb 18 16:11] SentryContextWrapper.java +│   │   │   ├── [ 658 Feb 18 16:11] SentryOtelKeys.java +│   │   │   ├── [2.5K Feb 18 16:11] SentryOtelThreadLocalStorage.java +│   │   │   └── [1.7K May 27 19:34] SentryWeakSpanStorage.java +│   │   └── [ 96 Feb 18 16:11] resources +│   │   └── [ 96 Feb 18 16:11] META-INF +│   │   └── [ 96 Feb 18 16:11] services +│   │   └── [ 53 Feb 18 16:11] io.opentelemetry.context.ContextStorageProvider +│   └── [ 160 May 27 19:34] sentry-opentelemetry-core +│   ├── [ 96 May 27 19:34] api +│   │   └── [9.1K May 27 19:34] sentry-opentelemetry-core.api +│   ├── [2.8K May 27 19:34] build.gradle.kts +│   └── [ 128 Feb 18 16:11] src +│   ├── [ 96 Feb 18 16:11] main +│   │   └── [ 96 Feb 18 16:11] java +│   │   └── [ 96 Feb 18 16:11] io +│   │   └── [ 96 Feb 18 16:11] sentry +│   │   └── [ 608 May 27 19:34] opentelemetry +│   │   ├── [5.4K May 27 19:34] OpenTelemetryAttributesExtractor.java +│   │   ├── [3.8K Feb 18 16:11] OpenTelemetryLinkErrorEventProcessor.java +│   │   ├── [1.8K May 27 19:34] OtelInternalSpanDetectionUtil.java +│   │   ├── [1.2K Feb 18 16:11] OtelSamplingUtil.java +│   │   ├── [5.4K May 27 19:34] OtelSentryPropagator.java +│   │   ├── [8.5K May 27 19:34] OtelSentrySpanProcessor.java +│   │   ├── [3.7K Feb 18 16:11] OtelSpanContext.java +│   │   ├── [ 939 Feb 18 16:11] OtelSpanInfo.java +│   │   ├── [ 15K May 27 19:34] OtelSpanWrapper.java +│   │   ├── [4.6K Feb 18 16:11] SentryPropagator.java +│   │   ├── [5.8K May 27 19:34] SentrySampler.java +│   │   ├── [1.4K Feb 18 16:11] SentrySamplingResult.java +│   │   ├── [ 20K May 27 19:34] SentrySpanExporter.java +│   │   ├── [ 13K Feb 18 16:11] SentrySpanProcessor.java +│   │   ├── [4.2K May 27 19:34] SpanDescriptionExtractor.java +│   │   ├── [1.3K Feb 18 16:11] SpanNode.java +│   │   └── [1.3K Feb 18 16:11] TraceData.java +│   └── [ 96 Feb 18 16:11] test +│   └── [ 224 May 27 19:34] kotlin +│   ├── [ 12K May 27 19:34] OpenTelemetryAttributesExtractorTest.kt +│   ├── [7.7K May 27 19:34] OtelInternalSpanDetectionUtilTest.kt +│   ├── [ 13K May 27 19:34] OtelSentryPropagatorTest.kt +│   ├── [ 18K May 27 19:34] SentrySpanProcessorTest.kt +│   └── [ 10K May 27 19:34] SpanDescriptionExtractorTest.kt +├── [ 192 May 27 20:08] sentry-quartz +│   ├── [ 96 Feb 18 16:11] api +│   │   └── [ 809 Feb 18 16:11] sentry-quartz.api +│   ├── [2.4K May 27 19:34] build.gradle.kts +│   └── [ 96 Feb 18 16:11] src +│   └── [ 96 Feb 18 16:11] main +│   └── [ 96 Feb 18 16:11] java +│   └── [ 96 Feb 18 16:11] io +│   └── [ 96 Feb 18 16:11] sentry +│   └── [ 96 May 27 19:34] quartz +│   └── [4.0K May 27 19:34] SentryJobListener.java +├── [ 224 May 27 20:08] sentry-reactor +│   ├── [3.2K Feb 18 16:11] README.md +│   ├── [ 96 Feb 18 16:11] api +│   │   └── [1.3K Feb 18 16:11] sentry-reactor.api +│   ├── [2.8K May 27 19:34] build.gradle.kts +│   └── [ 128 Feb 18 16:11] src +│   ├── [ 128 Feb 18 16:11] main +│   │   ├── [ 96 Feb 18 16:11] java +│   │   │   └── [ 96 Feb 18 16:11] io +│   │   │   └── [ 96 Feb 18 16:11] sentry +│   │   │   └── [ 128 Feb 18 16:11] reactor +│   │   │   ├── [ 674 Feb 18 16:11] SentryReactorThreadLocalAccessor.java +│   │   │   └── [4.9K Feb 18 16:11] SentryReactorUtils.java +│   │   └── [ 96 Feb 18 16:11] resources +│   │   └── [ 96 Feb 18 16:11] META-INF +│   │   └── [ 96 Feb 18 16:11] services +│   │   └── [ 51 Feb 18 16:11] io.micrometer.context.ThreadLocalAccessor +│   └── [ 96 Feb 18 16:11] test +│   └── [ 96 Feb 18 16:11] kotlin +│   └── [ 96 Feb 18 16:11] io +│   └── [ 96 Feb 18 16:11] sentry +│   └── [ 96 Feb 18 16:11] reactor +│   └── [3.5K Feb 18 16:11] SentryReactorUtilsTest.kt +├── [ 672 Feb 18 16:11] sentry-samples +│   ├── [ 288 May 27 19:34] sentry-samples-android +│   │   ├── [ 128 Feb 18 16:30] .cxx +│   │   │   ├── [ 96 Feb 18 16:30] Debug +│   │   │   │   └── [ 192 Feb 18 18:57] 3u6s1g3o +│   │   │   │   ├── [ 608 Feb 18 18:59] arm64-v8a +│   │   │   │   │   ├── [ 96 Feb 18 18:57] .cmake +│   │   │   │   │   │   └── [ 96 Feb 18 18:57] api +│   │   │   │   │   │   └── [ 128 Feb 18 18:57] v1 +│   │   │   │   │   │   ├── [ 96 Feb 18 18:57] query +│   │   │   │   │   │   │   └── [ 160 Feb 18 18:57] client-agp +│   │   │   │   │   │   │   ├── [ 0 Feb 18 18:57] cache-v2 +│   │   │   │   │   │   │   ├── [ 0 Feb 18 18:57] cmakeFiles-v1 +│   │   │   │   │   │   │   └── [ 0 Feb 18 18:57] codemodel-v2 +│   │   │   │   │   │   └── [ 256 Feb 18 18:57] reply +│   │   │   │   │   │   ├── [ 28K Feb 18 18:57] cache-v2-ece011d78c9b9ef6c4b7.json +│   │   │   │   │   │   ├── [ 27K Feb 18 18:57] cmakeFiles-v1-213036d95f83728f40a0.json +│   │   │   │   │   │   ├── [1.0K Feb 18 18:57] codemodel-v2-1a805ddcfedad5226927.json +│   │   │   │   │   │   ├── [ 154 Feb 18 18:57] directory-.-Debug-f5ebdc15457944623624.json +│   │   │   │   │   │   ├── [1.6K Feb 18 18:57] index-2025-02-18T10-57-50-0477.json +│   │   │   │   │   │   └── [3.2K Feb 18 18:57] target-native-sample-Debug-be2475b9900f92f0eea9.json +│   │   │   │   │   ├── [7.0K Feb 18 18:57] .ninja_deps +│   │   │   │   │   ├── [ 248 Feb 18 18:57] .ninja_log +│   │   │   │   │   ├── [ 17K Feb 18 18:57] CMakeCache.txt +│   │   │   │   │   ├── [ 288 Feb 18 18:57] CMakeFiles +│   │   │   │   │   │   ├── [ 288 Feb 18 18:57] 3.22.1-g37088a8 +│   │   │   │   │   │   │   ├── [3.5K Feb 18 18:57] CMakeCCompiler.cmake +│   │   │   │   │   │   │   ├── [6.4K Feb 18 18:57] CMakeCXXCompiler.cmake +│   │   │   │   │   │   │   ├── [7.7K Feb 18 18:57] CMakeDetermineCompilerABI_C.bin +│   │   │   │   │   │   │   ├── [7.9K Feb 18 18:57] CMakeDetermineCompilerABI_CXX.bin +│   │   │   │   │   │   │   ├── [ 451 Feb 18 18:57] CMakeSystem.cmake +│   │   │   │   │   │   │   ├── [ 160 Feb 18 18:57] CompilerIdC +│   │   │   │   │   │   │   │   ├── [ 24K Feb 18 18:57] CMakeCCompilerId.c +│   │   │   │   │   │   │   │   ├── [5.9K Feb 18 18:57] CMakeCCompilerId.o +│   │   │   │   │   │   │   │   └── [ 64 Feb 18 18:57] tmp +│   │   │   │   │   │   │   └── [ 160 Feb 18 18:57] CompilerIdCXX +│   │   │   │   │   │   │   ├── [ 24K Feb 18 18:57] CMakeCXXCompilerId.cpp +│   │   │   │   │   │   │   ├── [5.9K Feb 18 18:57] CMakeCXXCompilerId.o +│   │   │   │   │   │   │   └── [ 64 Feb 18 18:57] tmp +│   │   │   │   │   │   ├── [ 41K Feb 18 18:57] CMakeOutput.log +│   │   │   │   │   │   ├── [ 64 Feb 18 18:57] CMakeTmp +│   │   │   │   │   │   ├── [ 411 Feb 18 18:57] TargetDirectories.txt +│   │   │   │   │   │   ├── [ 85 Feb 18 18:57] cmake.check_cache +│   │   │   │   │   │   ├── [ 96 Feb 18 18:57] native-sample.dir +│   │   │   │   │   │   │   └── [ 96 Feb 18 18:57] src +│   │   │   │   │   │   │   └── [ 96 Feb 18 18:57] main +│   │   │   │   │   │   │   └── [ 96 Feb 18 18:57] cpp +│   │   │   │   │   │   │   └── [ 62K Feb 18 18:57] native-sample.cpp.o +│   │   │   │   │   │   └── [2.7K Feb 18 18:57] rules.ninja +│   │   │   │   │   ├── [ 0 Feb 18 18:57] additional_project_files.txt +│   │   │   │   │   ├── [2.2K Feb 18 18:57] android_gradle_build.json +│   │   │   │   │   ├── [1.8K Feb 18 18:57] android_gradle_build_mini.json +│   │   │   │   │   ├── [ 29K Feb 18 18:57] build.ninja +│   │   │   │   │   ├── [ 506 Feb 18 18:57] build_file_index.txt +│   │   │   │   │   ├── [1.8K Feb 18 18:57] cmake_install.cmake +│   │   │   │   │   ├── [1.1K Feb 18 18:57] compile_commands.json +│   │   │   │   │   ├── [1.2K Feb 18 18:57] compile_commands.json.bin +│   │   │   │   │   ├── [2.4K Feb 18 18:59] configure_fingerprint.bin +│   │   │   │   │   ├── [1.3K Feb 18 18:57] metadata_generation_command.txt +│   │   │   │   │   ├── [ 327 Feb 18 18:57] prefab_config.json +│   │   │   │   │   └── [ 131 Feb 18 18:57] symbol_folder_index.txt +│   │   │   │   ├── [1.3K Feb 18 16:30] hash_key.txt +│   │   │   │   ├── [ 128 Feb 18 18:57] prefab +│   │   │   │   │   ├── [ 96 Feb 18 18:57] arm64-v8a +│   │   │   │   │   │   └── [ 96 Feb 18 18:57] prefab +│   │   │   │   │   │   └── [ 96 Feb 18 18:57] lib +│   │   │   │   │   │   └── [ 96 Feb 18 18:57] aarch64-linux-android +│   │   │   │   │   │   └── [ 96 Feb 18 18:57] cmake +│   │   │   │   │   │   └── [ 128 Feb 18 18:57] sentry-native-ndk +│   │   │   │   │   │   ├── [1017 Feb 18 18:57] sentry-native-ndkConfig.cmake +│   │   │   │   │   │   └── [ 310 Feb 18 18:57] sentry-native-ndkConfigVersion.cmake +│   │   │   │   │   └── [ 96 Feb 18 16:30] x86 +│   │   │   │   │   └── [ 96 Feb 18 16:30] prefab +│   │   │   │   │   └── [ 96 Feb 18 16:30] lib +│   │   │   │   │   └── [ 96 Feb 18 16:30] i686-linux-android +│   │   │   │   │   └── [ 96 Feb 18 16:30] cmake +│   │   │   │   │   └── [ 128 Feb 18 16:30] sentry-native-ndk +│   │   │   │   │   ├── [1002 May 27 20:17] sentry-native-ndkConfig.cmake +│   │   │   │   │   └── [ 309 May 27 20:17] sentry-native-ndkConfigVersion.cmake +│   │   │   │   └── [ 544 May 27 20:17] x86 +│   │   │   │   ├── [ 96 Feb 18 16:30] .cmake +│   │   │   │   │   └── [ 96 Feb 18 16:30] api +│   │   │   │   │   └── [ 128 May 27 20:17] v1 +│   │   │   │   │   ├── [ 96 Feb 18 16:30] query +│   │   │   │   │   │   └── [ 160 Feb 18 16:30] client-agp +│   │   │   │   │   │   ├── [ 0 May 27 20:17] cache-v2 +│   │   │   │   │   │   ├── [ 0 May 27 20:17] cmakeFiles-v1 +│   │   │   │   │   │   └── [ 0 May 27 20:17] codemodel-v2 +│   │   │   │   │   └── [ 256 May 27 20:17] reply +│   │   │   │   │   ├── [ 27K May 27 20:17] cache-v2-f7db95325bc434cc697a.json +│   │   │   │   │   ├── [6.0K May 27 20:17] cmakeFiles-v1-d6783107011ca9e52149.json +│   │   │   │   │   ├── [1.0K May 27 20:17] codemodel-v2-fd2b991cf02633ea977f.json +│   │   │   │   │   ├── [ 154 Feb 18 16:30] directory-.-Debug-f5ebdc15457944623624.json +│   │   │   │   │   ├── [1.6K May 27 20:17] index-2025-05-27T12-17-10-0384.json +│   │   │   │   │   └── [3.2K May 27 20:17] target-native-sample-Debug-97067de7b903026bf6ad.json +│   │   │   │   ├── [ 17K May 27 20:17] CMakeCache.txt +│   │   │   │   ├── [ 288 May 27 20:17] CMakeFiles +│   │   │   │   │   ├── [ 288 Feb 18 16:30] 3.22.1-g37088a8 +│   │   │   │   │   │   ├── [3.5K Feb 18 16:30] CMakeCCompiler.cmake +│   │   │   │   │   │   ├── [6.3K Feb 18 16:30] CMakeCXXCompiler.cmake +│   │   │   │   │   │   ├── [5.6K Feb 18 16:30] CMakeDetermineCompilerABI_C.bin +│   │   │   │   │   │   ├── [5.7K Feb 18 16:30] CMakeDetermineCompilerABI_CXX.bin +│   │   │   │   │   │   ├── [ 448 Feb 18 16:30] CMakeSystem.cmake +│   │   │   │   │   │   ├── [ 160 Feb 18 16:30] CompilerIdC +│   │   │   │   │   │   │   ├── [ 24K Feb 18 16:30] CMakeCCompilerId.c +│   │   │   │   │   │   │   ├── [3.9K Feb 18 16:30] CMakeCCompilerId.o +│   │   │   │   │   │   │   └── [ 64 Feb 18 16:30] tmp +│   │   │   │   │   │   └── [ 160 Feb 18 16:30] CompilerIdCXX +│   │   │   │   │   │   ├── [ 24K Feb 18 16:30] CMakeCXXCompilerId.cpp +│   │   │   │   │   │   ├── [3.9K Feb 18 16:30] CMakeCXXCompilerId.o +│   │   │   │   │   │   └── [ 64 Feb 18 16:30] tmp +│   │   │   │   │   ├── [ 40K Feb 18 16:30] CMakeOutput.log +│   │   │   │   │   ├── [ 64 Feb 18 16:30] CMakeTmp +│   │   │   │   │   ├── [ 393 May 27 20:17] TargetDirectories.txt +│   │   │   │   │   ├── [ 85 May 27 20:17] cmake.check_cache +│   │   │   │   │   ├── [ 96 Feb 18 16:30] native-sample.dir +│   │   │   │   │   │   └── [ 96 Feb 18 16:30] src +│   │   │   │   │   │   └── [ 96 Feb 18 16:30] main +│   │   │   │   │   │   └── [ 64 Feb 18 16:30] cpp +│   │   │   │   │   └── [2.7K May 27 20:17] rules.ninja +│   │   │   │   ├── [ 0 May 27 20:17] additional_project_files.txt +│   │   │   │   ├── [2.1K May 27 20:17] android_gradle_build.json +│   │   │   │   ├── [1.7K May 27 20:17] android_gradle_build_mini.json +│   │   │   │   ├── [ 13K May 27 20:17] build.ninja +│   │   │   │   ├── [ 488 Feb 18 16:30] build_file_index.txt +│   │   │   │   ├── [1.8K Feb 18 16:30] cmake_install.cmake +│   │   │   │   ├── [1.1K May 27 20:17] compile_commands.json +│   │   │   │   ├── [1.2K May 27 20:17] compile_commands.json.bin +│   │   │   │   ├── [2.4K May 27 20:17] configure_fingerprint.bin +│   │   │   │   ├── [1.3K Feb 18 16:30] metadata_generation_command.txt +│   │   │   │   ├── [ 326 May 27 20:17] prefab_config.json +│   │   │   │   └── [ 125 Feb 18 16:30] symbol_folder_index.txt +│   │   │   └── [ 96 Feb 18 16:30] tools +│   │   │   └── [ 128 Feb 18 18:57] debug +│   │   │   ├── [ 96 Feb 18 18:57] arm64-v8a +│   │   │   │   └── [1.1K Feb 18 18:57] compile_commands.json +│   │   │   └── [ 96 May 27 20:17] x86 +│   │   │   └── [1.1K May 27 20:17] compile_commands.json +│   │   ├── [ 7 Feb 18 16:11] .gitignore +│   │   ├── [ 522 Feb 18 16:11] CMakeLists.txt +│   │   ├── [6.6K May 27 19:34] build.gradle.kts +│   │   ├── [1.4K Feb 18 16:11] proguard-rules.pro +│   │   └── [ 96 Feb 18 16:11] src +│   │   └── [ 192 May 27 19:34] main +│   │   ├── [8.3K May 27 19:34] AndroidManifest.xml +│   │   ├── [ 96 May 27 19:34] cpp +│   │   │   └── [1.3K May 27 19:34] native-sample.cpp +│   │   ├── [ 96 Feb 18 16:11] java +│   │   │   └── [ 96 Feb 18 16:11] io +│   │   │   └── [ 96 Feb 18 16:11] sentry +│   │   │   └── [ 96 Feb 18 16:11] samples +│   │   │   └── [ 608 May 27 19:34] android +│   │   │   ├── [ 450 May 27 19:34] CoroutinesUtil.kt +│   │   │   ├── [8.4K Feb 18 16:11] FrameDataForSpansActivity.kt +│   │   │   ├── [3.7K Feb 18 16:11] GesturesActivity.kt +│   │   │   ├── [ 446 Feb 18 16:11] GitHubService.kt +│   │   │   ├── [ 919 Feb 18 16:11] GithubAPI.kt +│   │   │   ├── [ 10K May 27 19:34] MainActivity.java +│   │   │   ├── [1.3K May 27 19:34] MyApplication.java +│   │   │   ├── [ 331 May 27 19:34] NativeSample.java +│   │   │   ├── [1.5K Feb 18 16:11] PermissionsActivity.kt +│   │   │   ├── [6.5K Feb 18 16:11] ProfilingActivity.kt +│   │   │   ├── [1.4K Feb 18 16:11] ProfilingListAdapter.kt +│   │   │   ├── [ 777 Feb 18 16:11] SampleFragment.kt +│   │   │   ├── [ 826 Feb 18 16:11] SampleInnerFragment.kt +│   │   │   ├── [2.6K Feb 18 16:11] SecondActivity.kt +│   │   │   ├── [ 692 Feb 18 16:11] ThirdActivityFragment.kt +│   │   │   ├── [1.2K Feb 18 16:11] ThirdFragment.kt +│   │   │   └── [ 96 May 27 19:34] compose +│   │   │   └── [6.9K May 27 19:34] ComposeActivity.kt +│   │   └── [ 448 Feb 18 16:11] res +│   │   ├── [ 128 Feb 18 16:11] drawable +│   │   │   ├── [4.9K Feb 18 16:11] ic_launcher_background.xml +│   │   │   └── [ 808 Feb 18 16:11] sentry_glyph.xml +│   │   ├── [ 96 Feb 18 16:11] drawable-v24 +│   │   │   └── [1.7K Feb 18 16:11] ic_launcher_foreground.xml +│   │   ├── [ 448 May 27 19:34] layout +│   │   │   ├── [ 650 Feb 18 16:11] activity_gestures.xml +│   │   │   ├── [5.0K May 27 19:34] activity_main.xml +│   │   │   ├── [ 779 Feb 18 16:11] activity_permissions.xml +│   │   │   ├── [1.8K Feb 18 16:11] activity_profiling.xml +│   │   │   ├── [1.2K Feb 18 16:11] activity_second.xml +│   │   │   ├── [ 527 Feb 18 16:11] activity_third_fragment.xml +│   │   │   ├── [ 259 Feb 18 16:11] fragment_recycler.xml +│   │   │   ├── [ 304 Feb 18 16:11] fragment_sample.xml +│   │   │   ├── [ 483 Feb 18 16:11] fragment_sample_inner.xml +│   │   │   ├── [ 493 Feb 18 16:11] fragment_scrolling.xml +│   │   │   ├── [ 512 Feb 18 16:11] profiling_item_list.xml +│   │   │   └── [ 423 Feb 18 16:11] third_fragment.xml +│   │   ├── [ 128 Feb 18 16:11] mipmap-anydpi-v26 +│   │   │   ├── [ 269 Feb 18 16:11] ic_launcher.xml +│   │   │   └── [ 269 Feb 18 16:11] ic_launcher_round.xml +│   │   ├── [ 128 Feb 18 16:11] mipmap-hdpi +│   │   │   ├── [2.9K Feb 18 16:11] ic_launcher.png +│   │   │   └── [4.8K Feb 18 16:11] ic_launcher_round.png +│   │   ├── [ 128 Feb 18 16:11] mipmap-mdpi +│   │   │   ├── [2.0K Feb 18 16:11] ic_launcher.png +│   │   │   └── [2.7K Feb 18 16:11] ic_launcher_round.png +│   │   ├── [ 128 Feb 18 16:11] mipmap-xhdpi +│   │   │   ├── [4.4K Feb 18 16:11] ic_launcher.png +│   │   │   └── [6.7K Feb 18 16:11] ic_launcher_round.png +│   │   ├── [ 128 Feb 18 16:11] mipmap-xxhdpi +│   │   │   ├── [6.2K Feb 18 16:11] ic_launcher.png +│   │   │   └── [ 10K Feb 18 16:11] ic_launcher_round.png +│   │   ├── [ 128 Feb 18 16:11] mipmap-xxxhdpi +│   │   │   ├── [8.9K Feb 18 16:11] ic_launcher.png +│   │   │   └── [ 15K Feb 18 16:11] ic_launcher_round.png +│   │   ├── [ 96 Feb 18 16:11] raw +│   │   │   └── [3.5K Feb 18 16:11] sentry.png +│   │   ├── [ 160 May 27 19:34] values +│   │   │   ├── [ 202 Feb 18 16:11] colors.xml +│   │   │   ├── [5.4K May 27 19:34] strings.xml +│   │   │   └── [ 361 Feb 18 16:11] styles.xml +│   │   └── [ 96 Feb 18 16:11] xml +│   │   └── [ 425 Feb 18 16:11] network.xml +│   ├── [ 160 May 27 19:34] sentry-samples-console +│   │   ├── [ 391 Feb 18 16:11] README.md +│   │   ├── [ 336 May 27 19:34] build.gradle.kts +│   │   └── [ 96 Feb 18 16:11] src +│   │   └── [ 96 Feb 18 16:11] main +│   │   └── [ 96 Feb 18 16:11] java +│   │   └── [ 96 Feb 18 16:11] io +│   │   └── [ 96 Feb 18 16:11] sentry +│   │   └── [ 96 Feb 18 16:11] samples +│   │   └── [ 96 Feb 18 16:11] console +│   │   └── [7.2K Feb 18 16:11] Main.java +│   ├── [ 160 May 27 19:34] sentry-samples-console-opentelemetry-noagent +│   │   ├── [ 433 Feb 18 16:11] README.md +│   │   ├── [ 378 May 27 19:34] build.gradle.kts +│   │   └── [ 96 Feb 18 16:11] src +│   │   └── [ 96 Feb 18 16:11] main +│   │   └── [ 96 Feb 18 16:11] java +│   │   └── [ 96 Feb 18 16:11] io +│   │   └── [ 96 Feb 18 16:11] sentry +│   │   └── [ 96 Feb 18 16:11] samples +│   │   └── [ 96 Feb 18 16:11] console +│   │   └── [8.1K Feb 18 16:11] Main.java +│   ├── [ 160 May 27 19:34] sentry-samples-jul +│   │   ├── [ 352 Feb 18 16:11] README.md +│   │   ├── [ 380 May 27 19:34] build.gradle.kts +│   │   └── [ 96 Feb 18 16:11] src +│   │   └── [ 128 Feb 18 16:11] main +│   │   ├── [ 96 Feb 18 16:11] java +│   │   │   └── [ 96 Feb 18 16:11] io +│   │   │   └── [ 96 Feb 18 16:11] sentry +│   │   │   └── [ 96 Feb 18 16:11] samples +│   │   │   └── [ 96 Feb 18 16:11] jul +│   │   │   └── [1.1K Feb 18 16:11] Main.java +│   │   └── [ 128 Feb 18 16:11] resources +│   │   ├── [ 246 Feb 18 16:11] logging.properties +│   │   └── [ 285 Feb 18 16:11] sentry.properties +│   ├── [ 160 May 27 19:34] sentry-samples-log4j2 +│   │   ├── [ 380 Feb 18 16:11] README.md +│   │   ├── [ 383 May 27 19:34] build.gradle.kts +│   │   └── [ 96 Feb 18 16:11] src +│   │   └── [ 128 Feb 18 16:11] main +│   │   ├── [ 96 Feb 18 16:11] java +│   │   │   └── [ 96 Feb 18 16:11] io +│   │   │   └── [ 96 Feb 18 16:11] sentry +│   │   │   └── [ 96 Feb 18 16:11] samples +│   │   │   └── [ 96 Feb 18 16:11] log4j2 +│   │   │   └── [1.2K Feb 18 16:11] Main.java +│   │   └── [ 128 Feb 18 16:11] resources +│   │   ├── [1020 Feb 18 16:11] log4j2.xml +│   │   └── [ 36 Feb 18 16:11] sentry.properties +│   ├── [ 160 May 27 19:34] sentry-samples-logback +│   │   ├── [ 368 Feb 18 16:11] README.md +│   │   ├── [ 390 May 27 19:34] build.gradle.kts +│   │   └── [ 96 Feb 18 16:11] src +│   │   └── [ 128 Feb 18 16:11] main +│   │   ├── [ 96 Feb 18 16:11] java +│   │   │   └── [ 96 Feb 18 16:11] io +│   │   │   └── [ 96 Feb 18 16:11] sentry +│   │   │   └── [ 96 Feb 18 16:11] samples +│   │   │   └── [ 96 Feb 18 16:11] logback +│   │   │   └── [ 876 Feb 18 16:11] Main.java +│   │   └── [ 128 Feb 18 16:11] resources +│   │   ├── [1.1K Feb 18 16:11] logback.xml +│   │   └── [ 36 Feb 18 16:11] sentry.properties +│   ├── [ 160 May 27 19:34] sentry-samples-netflix-dgs +│   │   ├── [1.1K Feb 18 16:11] README.md +│   │   ├── [1.4K May 27 19:34] build.gradle.kts +│   │   └── [ 96 Feb 18 16:11] src +│   │   └── [ 128 Feb 18 16:11] main +│   │   ├── [ 96 Feb 18 16:11] java +│   │   │   └── [ 96 Feb 18 16:11] io +│   │   │   └── [ 96 Feb 18 16:11] sentry +│   │   │   └── [ 96 Feb 18 16:11] samples +│   │   │   └── [ 96 Feb 18 16:11] netflix +│   │   │   └── [ 192 Feb 18 16:11] dgs +│   │   │   ├── [ 995 Feb 18 16:11] ActorsDataloader.java +│   │   │   ├── [1.0K Feb 18 16:11] NetlixDgsApplication.java +│   │   │   ├── [2.8K Feb 18 16:11] ShowsDatafetcher.java +│   │   │   └── [ 128 Feb 18 16:11] graphql +│   │   │   ├── [1.1K Feb 18 16:11] DgsConstants.java +│   │   │   └── [ 128 Feb 18 16:11] types +│   │   │   ├── [1.4K Feb 18 16:11] Actor.java +│   │   │   └── [2.8K Feb 18 16:11] Show.java +│   │   └── [ 128 Feb 18 16:11] resources +│   │   ├── [ 385 Feb 18 16:11] application.properties +│   │   └── [ 96 Feb 18 16:11] schema +│   │   └── [ 316 Feb 18 16:11] schema.graphqls +│   ├── [ 160 May 27 19:34] sentry-samples-openfeign +│   │   ├── [ 392 Feb 18 16:11] README.md +│   │   ├── [ 467 May 27 19:34] build.gradle.kts +│   │   └── [ 96 Feb 18 16:11] src +│   │   └── [ 96 Feb 18 16:11] main +│   │   └── [ 96 Feb 18 16:11] java +│   │   └── [ 96 Feb 18 16:11] io +│   │   └── [ 96 Feb 18 16:11] sentry +│   │   └── [ 96 Feb 18 16:11] samples +│   │   └── [ 96 Feb 18 16:11] openfeign +│   │   └── [2.7K Feb 18 16:11] Main.java +│   ├── [ 160 May 27 19:34] sentry-samples-servlet +│   │   ├── [ 543 Feb 18 16:11] README.md +│   │   ├── [ 408 May 27 19:34] build.gradle.kts +│   │   └── [ 96 Feb 18 16:11] src +│   │   └── [ 128 Feb 18 16:11] main +│   │   ├── [ 96 Feb 18 16:11] java +│   │   │   └── [ 96 Feb 18 16:11] io +│   │   │   └── [ 96 Feb 18 16:11] sentry +│   │   │   └── [ 96 Feb 18 16:11] samples +│   │   │   └── [ 128 Feb 18 16:11] servlet +│   │   │   ├── [ 754 Feb 18 16:11] SampleServlet.java +│   │   │   └── [2.7K Feb 18 16:11] SentryInitializer.java +│   │   └── [ 96 Feb 18 16:11] webapp +│   │   └── [ 96 Feb 18 16:11] WEB-INF +│   │   └── [ 616 Feb 18 16:11] web.xml +│   ├── [ 160 May 27 19:34] sentry-samples-spring +│   │   ├── [ 612 Feb 18 16:11] README.md +│   │   ├── [1.6K May 27 19:34] build.gradle.kts +│   │   └── [ 96 Feb 18 16:11] src +│   │   └── [ 128 Feb 18 16:11] main +│   │   ├── [ 96 Feb 18 16:11] java +│   │   │   └── [ 96 Feb 18 16:11] io +│   │   │   └── [ 96 Feb 18 16:11] sentry +│   │   │   └── [ 96 Feb 18 16:11] samples +│   │   │   └── [ 256 Feb 18 16:11] spring +│   │   │   ├── [ 602 Feb 18 16:11] AppConfig.java +│   │   │   ├── [1.6K Feb 18 16:11] AppInitializer.java +│   │   │   ├── [1.8K Feb 18 16:11] SecurityConfiguration.java +│   │   │   ├── [1.2K Feb 18 16:11] SentryConfig.java +│   │   │   ├── [1.2K Feb 18 16:11] WebConfig.java +│   │   │   └── [ 160 May 27 19:34] web +│   │   │   ├── [ 512 Feb 18 16:11] Person.java +│   │   │   ├── [1.4K May 27 19:34] PersonController.java +│   │   │   └── [ 951 Feb 18 16:11] PersonService.java +│   │   └── [ 128 Feb 18 16:11] resources +│   │   ├── [ 470 Feb 18 16:11] logback.xml +│   │   └── [ 47 Feb 18 16:11] sentry.properties +│   ├── [ 192 May 27 19:34] sentry-samples-spring-boot +│   │   ├── [1.7K Feb 18 16:11] README.md +│   │   ├── [3.0K May 27 19:34] build.gradle.kts +│   │   └── [ 128 Feb 18 16:11] src +│   │   ├── [ 128 Feb 18 16:11] main +│   │   │   ├── [ 96 Feb 18 16:11] java +│   │   │   │   └── [ 96 Feb 18 16:11] io +│   │   │   │   └── [ 96 Feb 18 16:11] sentry +│   │   │   │   └── [ 96 Feb 18 16:11] samples +│   │   │   │   └── [ 96 Feb 18 16:11] spring +│   │   │   │   └── [ 416 May 27 19:34] boot +│   │   │   │   ├── [ 821 Feb 18 16:11] CustomJob.java +│   │   │   │   ├── [1.7K May 27 19:34] DistributedTracingController.java +│   │   │   │   ├── [ 523 Feb 18 16:11] Person.java +│   │   │   │   ├── [1.2K May 27 19:34] PersonController.java +│   │   │   │   ├── [1.0K Feb 18 16:11] PersonService.java +│   │   │   │   ├── [1.7K Feb 18 16:11] SecurityConfiguration.java +│   │   │   │   ├── [2.3K Feb 18 16:11] SentryDemoApplication.java +│   │   │   │   ├── [ 447 Feb 18 16:11] Todo.java +│   │   │   │   ├── [1.0K Feb 18 16:11] TodoController.java +│   │   │   │   ├── [ 192 Feb 18 16:11] graphql +│   │   │   │   │   ├── [1.2K Feb 18 16:11] AssigneeController.java +│   │   │   │   │   ├── [ 512 Feb 18 16:11] GreetingController.java +│   │   │   │   │   ├── [4.5K Feb 18 16:11] ProjectController.java +│   │   │   │   │   └── [1.8K Feb 18 16:11] TaskCreatorController.java +│   │   │   │   └── [ 96 Feb 18 16:11] quartz +│   │   │   │   └── [ 498 Feb 18 16:11] SampleJob.java +│   │   │   └── [ 160 May 27 19:34] resources +│   │   │   ├── [1.1K May 27 19:34] application.properties +│   │   │   ├── [ 96 Feb 18 16:11] graphql +│   │   │   │   └── [1.3K Feb 18 16:11] schema.graphqls +│   │   │   └── [ 129 Feb 18 16:11] schema.sql +│   │   └── [ 128 May 27 19:34] test +│   │   ├── [ 96 Feb 18 16:11] kotlin +│   │   │   └── [ 96 Feb 18 16:11] io +│   │   │   └── [ 128 Feb 18 16:11] sentry +│   │   │   ├── [ 236 Feb 18 16:11] DummyTest.kt +│   │   │   └── [ 256 May 27 19:34] systemtest +│   │   │   ├── [7.1K May 27 19:34] DistributedTracingSystemTest.kt +│   │   │   ├── [1.1K May 27 19:34] GraphqlGreetingSystemTest.kt +│   │   │   ├── [1.8K May 27 19:34] GraphqlProjectSystemTest.kt +│   │   │   ├── [1.3K May 27 19:34] GraphqlTaskSystemTest.kt +│   │   │   ├── [1.7K May 27 19:34] PersonSystemTest.kt +│   │   │   └── [1.0K May 27 19:34] TodoSystemTest.kt +│   │   └── [ 96 Feb 18 16:11] resources +│   │   └── [ 501 Feb 18 16:11] logback.xml +│   ├── [ 256 May 27 19:34] sentry-samples-spring-boot-jakarta +│   │   ├── [1.8K Feb 18 16:11] README.md +│   │   ├── [3.0K May 27 19:34] build.gradle.kts +│   │   ├── [ 105 Feb 18 16:11] sentry-jakarta-text-master.properties +│   │   ├── [ 144 Feb 18 16:11] sentry-package-rename.properties +│   │   └── [ 128 Feb 18 16:11] src +│   │   ├── [ 128 Feb 18 16:11] main +│   │   │   ├── [ 96 Feb 18 16:11] java +│   │   │   │   └── [ 96 Feb 18 16:11] io +│   │   │   │   └── [ 96 Feb 18 16:11] sentry +│   │   │   │   └── [ 96 Feb 18 16:11] samples +│   │   │   │   └── [ 96 Feb 18 16:11] spring +│   │   │   │   └── [ 96 Feb 18 16:11] boot +│   │   │   │   └── [ 448 May 27 19:34] jakarta +│   │   │   │   ├── [1.0K Feb 18 16:11] CustomEventProcessor.java +│   │   │   │   ├── [ 789 Feb 18 16:11] CustomJob.java +│   │   │   │   ├── [1.6K May 27 19:34] DistributedTracingController.java +│   │   │   │   ├── [ 521 Feb 18 16:11] Person.java +│   │   │   │   ├── [1.7K May 27 19:34] PersonController.java +│   │   │   │   ├── [1.1K Feb 18 16:11] PersonService.java +│   │   │   │   ├── [1.6K Feb 18 16:11] SecurityConfiguration.java +│   │   │   │   ├── [2.4K Feb 18 16:11] SentryDemoApplication.java +│   │   │   │   ├── [ 453 Feb 18 16:11] Todo.java +│   │   │   │   ├── [2.0K Feb 18 16:11] TodoController.java +│   │   │   │   ├── [ 192 May 27 19:34] graphql +│   │   │   │   │   ├── [1.2K Feb 18 16:11] AssigneeController.java +│   │   │   │   │   ├── [ 520 Feb 18 16:11] GreetingController.java +│   │   │   │   │   ├── [4.5K Feb 18 16:11] ProjectController.java +│   │   │   │   │   └── [1.9K May 27 19:34] TaskCreatorController.java +│   │   │   │   └── [ 96 Feb 18 16:11] quartz +│   │   │   │   └── [ 506 Feb 18 16:11] SampleJob.java +│   │   │   └── [ 192 May 27 19:34] resources +│   │   │   ├── [1.4K May 27 19:34] application.properties +│   │   │   ├── [ 96 Feb 18 16:11] graphql +│   │   │   │   └── [1.2K Feb 18 16:11] schema.graphqls +│   │   │   ├── [ 55 Feb 18 16:11] quartz.properties +│   │   │   └── [ 129 Feb 18 16:11] schema.sql +│   │   └── [ 128 May 27 19:34] test +│   │   ├── [ 96 Feb 18 16:11] kotlin +│   │   │   └── [ 96 Feb 18 16:11] io +│   │   │   └── [ 128 Feb 18 16:11] sentry +│   │   │   ├── [ 236 Feb 18 16:11] DummyTest.kt +│   │   │   └── [ 256 May 27 19:34] systemtest +│   │   │   ├── [7.1K May 27 19:34] DistributedTracingSystemTest.kt +│   │   │   ├── [1.1K May 27 19:34] GraphqlGreetingSystemTest.kt +│   │   │   ├── [1.8K May 27 19:34] GraphqlProjectSystemTest.kt +│   │   │   ├── [1.3K May 27 19:34] GraphqlTaskSystemTest.kt +│   │   │   ├── [1.7K May 27 19:34] PersonSystemTest.kt +│   │   │   └── [1.4K May 27 19:34] TodoSystemTest.kt +│   │   └── [ 96 Feb 18 16:11] resources +│   │   └── [ 501 Feb 18 16:11] logback.xml +│   ├── [ 192 May 27 19:34] sentry-samples-spring-boot-jakarta-opentelemetry +│   │   ├── [2.0K Feb 18 16:11] README.md +│   │   ├── [4.0K May 27 19:34] build.gradle.kts +│   │   └── [ 128 Feb 18 16:11] src +│   │   ├── [ 128 Feb 18 16:11] main +│   │   │   ├── [ 96 Feb 18 16:11] java +│   │   │   │   └── [ 96 Feb 18 16:11] io +│   │   │   │   └── [ 96 Feb 18 16:11] sentry +│   │   │   │   └── [ 96 Feb 18 16:11] samples +│   │   │   │   └── [ 96 Feb 18 16:11] spring +│   │   │   │   └── [ 96 Feb 18 16:11] boot +│   │   │   │   └── [ 448 May 27 19:34] jakarta +│   │   │   │   ├── [1.0K Feb 18 16:11] CustomEventProcessor.java +│   │   │   │   ├── [ 789 Feb 18 16:11] CustomJob.java +│   │   │   │   ├── [1.6K May 27 19:34] DistributedTracingController.java +│   │   │   │   ├── [ 521 Feb 18 16:11] Person.java +│   │   │   │   ├── [2.2K May 27 19:34] PersonController.java +│   │   │   │   ├── [1.1K Feb 18 16:11] PersonService.java +│   │   │   │   ├── [1.6K Feb 18 16:11] SecurityConfiguration.java +│   │   │   │   ├── [2.6K Feb 18 16:11] SentryDemoApplication.java +│   │   │   │   ├── [ 453 Feb 18 16:11] Todo.java +│   │   │   │   ├── [2.9K Feb 18 16:11] TodoController.java +│   │   │   │   ├── [ 192 Feb 18 16:11] graphql +│   │   │   │   │   ├── [1.2K Feb 18 16:11] AssigneeController.java +│   │   │   │   │   ├── [ 520 Feb 18 16:11] GreetingController.java +│   │   │   │   │   ├── [4.5K Feb 18 16:11] ProjectController.java +│   │   │   │   │   └── [1.8K Feb 18 16:11] TaskCreatorController.java +│   │   │   │   └── [ 96 Feb 18 16:11] quartz +│   │   │   │   └── [ 506 Feb 18 16:11] SampleJob.java +│   │   │   └── [ 192 May 27 19:34] resources +│   │   │   ├── [1.4K May 27 19:34] application.properties +│   │   │   ├── [ 96 Feb 18 16:11] graphql +│   │   │   │   └── [1.2K Feb 18 16:11] schema.graphqls +│   │   │   ├── [ 55 Feb 18 16:11] quartz.properties +│   │   │   └── [ 129 Feb 18 16:11] schema.sql +│   │   └── [ 128 May 27 19:34] test +│   │   ├── [ 96 Feb 18 16:11] kotlin +│   │   │   └── [ 96 Feb 18 16:11] io +│   │   │   └── [ 128 Feb 18 16:11] sentry +│   │   │   ├── [ 236 Feb 18 16:11] DummyTest.kt +│   │   │   └── [ 256 May 27 19:34] systemtest +│   │   │   ├── [7.1K May 27 19:34] DistributedTracingSystemTest.kt +│   │   │   ├── [1.1K May 27 19:34] GraphqlGreetingSystemTest.kt +│   │   │   ├── [1.8K May 27 19:34] GraphqlProjectSystemTest.kt +│   │   │   ├── [1.1K May 27 19:34] GraphqlTaskSystemTest.kt +│   │   │   ├── [1.8K May 27 19:34] PersonSystemTest.kt +│   │   │   └── [1.8K May 27 19:34] TodoSystemTest.kt +│   │   └── [ 96 Feb 18 16:11] resources +│   │   └── [ 501 Feb 18 16:11] logback.xml +│   ├── [ 192 May 27 19:34] sentry-samples-spring-boot-jakarta-opentelemetry-noagent +│   │   ├── [1.9K Feb 18 16:11] README.md +│   │   ├── [3.1K May 27 19:34] build.gradle.kts +│   │   └── [ 128 Feb 18 16:11] src +│   │   ├── [ 128 Feb 18 16:11] main +│   │   │   ├── [ 96 Feb 18 16:11] java +│   │   │   │   └── [ 96 Feb 18 16:11] io +│   │   │   │   └── [ 96 Feb 18 16:11] sentry +│   │   │   │   └── [ 96 Feb 18 16:11] samples +│   │   │   │   └── [ 96 Feb 18 16:11] spring +│   │   │   │   └── [ 96 Feb 18 16:11] boot +│   │   │   │   └── [ 448 May 27 19:34] jakarta +│   │   │   │   ├── [1.0K Feb 18 16:11] CustomEventProcessor.java +│   │   │   │   ├── [ 845 Feb 18 16:11] CustomJob.java +│   │   │   │   ├── [1.7K May 27 19:34] DistributedTracingController.java +│   │   │   │   ├── [ 521 Feb 18 16:11] Person.java +│   │   │   │   ├── [2.4K May 27 19:34] PersonController.java +│   │   │   │   ├── [1.1K Feb 18 16:11] PersonService.java +│   │   │   │   ├── [1.6K Feb 18 16:11] SecurityConfiguration.java +│   │   │   │   ├── [2.6K Feb 18 16:11] SentryDemoApplication.java +│   │   │   │   ├── [ 453 Feb 18 16:11] Todo.java +│   │   │   │   ├── [2.9K Feb 18 16:11] TodoController.java +│   │   │   │   ├── [ 192 Feb 18 16:11] graphql +│   │   │   │   │   ├── [1.2K Feb 18 16:11] AssigneeController.java +│   │   │   │   │   ├── [ 520 Feb 18 16:11] GreetingController.java +│   │   │   │   │   ├── [4.5K Feb 18 16:11] ProjectController.java +│   │   │   │   │   └── [1.8K Feb 18 16:11] TaskCreatorController.java +│   │   │   │   └── [ 96 Feb 18 16:11] quartz +│   │   │   │   └── [ 506 Feb 18 16:11] SampleJob.java +│   │   │   └── [ 192 May 27 19:34] resources +│   │   │   ├── [1.5K May 27 19:34] application.properties +│   │   │   ├── [ 96 Feb 18 16:11] graphql +│   │   │   │   └── [1.2K Feb 18 16:11] schema.graphqls +│   │   │   ├── [ 55 Feb 18 16:11] quartz.properties +│   │   │   └── [ 129 Feb 18 16:11] schema.sql +│   │   └── [ 128 May 27 19:34] test +│   │   ├── [ 96 Feb 18 16:11] kotlin +│   │   │   └── [ 96 Feb 18 16:11] io +│   │   │   └── [ 128 Feb 18 16:11] sentry +│   │   │   ├── [ 236 Feb 18 16:11] DummyTest.kt +│   │   │   └── [ 256 May 27 19:34] systemtest +│   │   │   ├── [7.1K May 27 19:34] DistributedTracingSystemTest.kt +│   │   │   ├── [1.1K May 27 19:34] GraphqlGreetingSystemTest.kt +│   │   │   ├── [1.8K May 27 19:34] GraphqlProjectSystemTest.kt +│   │   │   ├── [1.1K May 27 19:34] GraphqlTaskSystemTest.kt +│   │   │   ├── [1.8K May 27 19:34] PersonSystemTest.kt +│   │   │   └── [1.8K May 27 19:34] TodoSystemTest.kt +│   │   └── [ 96 Feb 18 16:11] resources +│   │   └── [ 501 Feb 18 16:11] logback.xml +│   ├── [ 192 May 27 19:34] sentry-samples-spring-boot-opentelemetry +│   │   ├── [2.0K Feb 18 16:11] README.md +│   │   ├── [4.0K May 27 19:34] build.gradle.kts +│   │   └── [ 128 Feb 18 16:11] src +│   │   ├── [ 128 Feb 18 16:11] main +│   │   │   ├── [ 96 Feb 18 16:11] java +│   │   │   │   └── [ 96 Feb 18 16:11] io +│   │   │   │   └── [ 96 Feb 18 16:11] sentry +│   │   │   │   └── [ 96 Feb 18 16:11] samples +│   │   │   │   └── [ 96 Feb 18 16:11] spring +│   │   │   │   └── [ 416 May 27 19:34] boot +│   │   │   │   ├── [ 821 Feb 18 16:11] CustomJob.java +│   │   │   │   ├── [1.7K May 27 19:34] DistributedTracingController.java +│   │   │   │   ├── [ 523 Feb 18 16:11] Person.java +│   │   │   │   ├── [2.2K May 27 19:34] PersonController.java +│   │   │   │   ├── [1.0K Feb 18 16:11] PersonService.java +│   │   │   │   ├── [1.7K Feb 18 16:11] SecurityConfiguration.java +│   │   │   │   ├── [2.5K Feb 18 16:11] SentryDemoApplication.java +│   │   │   │   ├── [ 447 Feb 18 16:11] Todo.java +│   │   │   │   ├── [1.6K Feb 18 16:11] TodoController.java +│   │   │   │   ├── [ 192 Feb 18 16:11] graphql +│   │   │   │   │   ├── [1.2K Feb 18 16:11] AssigneeController.java +│   │   │   │   │   ├── [ 512 Feb 18 16:11] GreetingController.java +│   │   │   │   │   ├── [4.5K Feb 18 16:11] ProjectController.java +│   │   │   │   │   └── [1.8K Feb 18 16:11] TaskCreatorController.java +│   │   │   │   └── [ 96 Feb 18 16:11] quartz +│   │   │   │   └── [ 498 Feb 18 16:11] SampleJob.java +│   │   │   └── [ 160 May 27 19:34] resources +│   │   │   ├── [1.1K May 27 19:34] application.properties +│   │   │   ├── [ 96 Feb 18 16:11] graphql +│   │   │   │   └── [1.3K Feb 18 16:11] schema.graphqls +│   │   │   └── [ 129 Feb 18 16:11] schema.sql +│   │   └── [ 128 May 27 19:34] test +│   │   ├── [ 96 Feb 18 16:11] kotlin +│   │   │   └── [ 96 Feb 18 16:11] io +│   │   │   └── [ 128 Feb 18 16:11] sentry +│   │   │   ├── [ 236 Feb 18 16:11] DummyTest.kt +│   │   │   └── [ 256 May 27 19:34] systemtest +│   │   │   ├── [7.1K May 27 19:34] DistributedTracingSystemTest.kt +│   │   │   ├── [1.1K May 27 19:34] GraphqlGreetingSystemTest.kt +│   │   │   ├── [1.8K May 27 19:34] GraphqlProjectSystemTest.kt +│   │   │   ├── [1.1K May 27 19:34] GraphqlTaskSystemTest.kt +│   │   │   ├── [3.9K May 27 19:34] PersonSystemTest.kt +│   │   │   └── [1.2K May 27 19:34] TodoSystemTest.kt +│   │   └── [ 96 Feb 18 16:11] resources +│   │   └── [ 501 Feb 18 16:11] logback.xml +│   ├── [ 192 May 27 19:34] sentry-samples-spring-boot-opentelemetry-noagent +│   │   ├── [1.9K Feb 18 16:11] README.md +│   │   ├── [3.2K May 27 19:34] build.gradle.kts +│   │   └── [ 128 Feb 18 16:11] src +│   │   ├── [ 128 Feb 18 16:11] main +│   │   │   ├── [ 96 Feb 18 16:11] java +│   │   │   │   └── [ 96 Feb 18 16:11] io +│   │   │   │   └── [ 96 Feb 18 16:11] sentry +│   │   │   │   └── [ 96 Feb 18 16:11] samples +│   │   │   │   └── [ 96 Feb 18 16:11] spring +│   │   │   │   └── [ 416 May 27 19:34] boot +│   │   │   │   ├── [ 821 Feb 18 16:11] CustomJob.java +│   │   │   │   ├── [1.7K May 27 19:34] DistributedTracingController.java +│   │   │   │   ├── [ 523 Feb 18 16:11] Person.java +│   │   │   │   ├── [2.3K May 27 19:34] PersonController.java +│   │   │   │   ├── [1.0K Feb 18 16:11] PersonService.java +│   │   │   │   ├── [1.7K Feb 18 16:11] SecurityConfiguration.java +│   │   │   │   ├── [2.5K Feb 18 16:11] SentryDemoApplication.java +│   │   │   │   ├── [ 447 Feb 18 16:11] Todo.java +│   │   │   │   ├── [1.6K Feb 18 16:11] TodoController.java +│   │   │   │   ├── [ 192 Feb 18 16:11] graphql +│   │   │   │   │   ├── [1.2K Feb 18 16:11] AssigneeController.java +│   │   │   │   │   ├── [ 512 Feb 18 16:11] GreetingController.java +│   │   │   │   │   ├── [4.5K Feb 18 16:11] ProjectController.java +│   │   │   │   │   └── [1.8K Feb 18 16:11] TaskCreatorController.java +│   │   │   │   └── [ 96 Feb 18 16:11] quartz +│   │   │   │   └── [ 498 Feb 18 16:11] SampleJob.java +│   │   │   └── [ 160 May 27 19:34] resources +│   │   │   ├── [1.2K May 27 19:34] application.properties +│   │   │   ├── [ 96 Feb 18 16:11] graphql +│   │   │   │   └── [1.3K Feb 18 16:11] schema.graphqls +│   │   │   └── [ 129 Feb 18 16:11] schema.sql +│   │   └── [ 128 May 27 19:34] test +│   │   ├── [ 96 Feb 18 16:11] kotlin +│   │   │   └── [ 96 Feb 18 16:11] io +│   │   │   └── [ 128 Feb 18 16:11] sentry +│   │   │   ├── [ 236 Feb 18 16:11] DummyTest.kt +│   │   │   └── [ 256 May 27 19:34] systemtest +│   │   │   ├── [7.1K May 27 19:34] DistributedTracingSystemTest.kt +│   │   │   ├── [1.1K May 27 19:34] GraphqlGreetingSystemTest.kt +│   │   │   ├── [1.8K May 27 19:34] GraphqlProjectSystemTest.kt +│   │   │   ├── [1.1K May 27 19:34] GraphqlTaskSystemTest.kt +│   │   │   ├── [1.8K May 27 19:34] PersonSystemTest.kt +│   │   │   └── [1.2K May 27 19:34] TodoSystemTest.kt +│   │   └── [ 96 Feb 18 16:11] resources +│   │   └── [ 501 Feb 18 16:11] logback.xml +│   ├── [ 192 May 27 19:34] sentry-samples-spring-boot-webflux +│   │   ├── [1002 Feb 18 16:11] README.md +│   │   ├── [2.2K May 27 19:34] build.gradle.kts +│   │   └── [ 128 Feb 18 16:11] src +│   │   ├── [ 128 Feb 18 16:11] main +│   │   │   ├── [ 96 Feb 18 16:11] java +│   │   │   │   └── [ 96 Feb 18 16:11] io +│   │   │   │   └── [ 96 Feb 18 16:11] sentry +│   │   │   │   └── [ 96 Feb 18 16:11] samples +│   │   │   │   └── [ 96 Feb 18 16:11] spring +│   │   │   │   └── [ 320 May 27 19:34] boot +│   │   │   │   ├── [1.8K May 27 19:34] DistributedTracingController.java +│   │   │   │   ├── [ 523 Feb 18 16:11] Person.java +│   │   │   │   ├── [1.3K May 27 19:34] PersonController.java +│   │   │   │   ├── [ 490 Feb 18 16:11] PersonService.java +│   │   │   │   ├── [ 543 Feb 18 16:11] SentryDemoApplication.java +│   │   │   │   ├── [ 447 Feb 18 16:11] Todo.java +│   │   │   │   ├── [ 783 Feb 18 16:11] TodoController.java +│   │   │   │   └── [ 96 Feb 18 16:11] graphql +│   │   │   │   └── [ 652 Feb 18 16:11] GreetingController.java +│   │   │   └── [ 128 May 27 19:34] resources +│   │   │   ├── [ 737 May 27 19:34] application.properties +│   │   │   └── [ 96 Feb 18 16:11] graphql +│   │   │   └── [1.3K Feb 18 16:11] schema.graphqls +│   │   └── [ 128 May 27 19:34] test +│   │   ├── [ 96 Feb 18 16:11] kotlin +│   │   │   └── [ 96 Feb 18 16:11] io +│   │   │   └── [ 128 Feb 18 16:11] sentry +│   │   │   ├── [ 236 Feb 18 16:11] DummyTest.kt +│   │   │   └── [ 192 May 27 19:34] systemtest +│   │   │   ├── [7.1K May 27 19:34] DistributedTracingSystemTest.kt +│   │   │   ├── [1.1K May 27 19:34] GraphqlGreetingSystemTest.kt +│   │   │   ├── [1.6K May 27 19:34] PersonSystemTest.kt +│   │   │   └── [ 716 May 27 19:34] TodoSystemTest.kt +│   │   └── [ 96 Feb 18 16:11] resources +│   │   └── [ 501 Feb 18 16:11] logback.xml +│   ├── [ 192 May 27 19:34] sentry-samples-spring-boot-webflux-jakarta +│   │   ├── [1004 Feb 18 16:11] README.md +│   │   ├── [2.3K May 27 19:34] build.gradle.kts +│   │   └── [ 128 Feb 18 16:11] src +│   │   ├── [ 128 Feb 18 16:11] main +│   │   │   ├── [ 96 Feb 18 16:11] java +│   │   │   │   └── [ 96 Feb 18 16:11] io +│   │   │   │   └── [ 96 Feb 18 16:11] sentry +│   │   │   │   └── [ 96 Feb 18 16:11] samples +│   │   │   │   └── [ 96 Feb 18 16:11] spring +│   │   │   │   └── [ 96 Feb 18 16:11] boot +│   │   │   │   └── [ 320 May 27 19:34] jakarta +│   │   │   │   ├── [1.8K May 27 19:34] DistributedTracingController.java +│   │   │   │   ├── [ 521 Feb 18 16:11] Person.java +│   │   │   │   ├── [1.3K May 27 19:34] PersonController.java +│   │   │   │   ├── [ 542 Feb 18 16:11] PersonService.java +│   │   │   │   ├── [ 551 Feb 18 16:11] SentryDemoApplication.java +│   │   │   │   ├── [ 453 Feb 18 16:11] Todo.java +│   │   │   │   ├── [ 791 Feb 18 16:11] TodoController.java +│   │   │   │   └── [ 96 Feb 18 16:11] graphql +│   │   │   │   └── [ 660 Feb 18 16:11] GreetingController.java +│   │   │   └── [ 128 May 27 19:34] resources +│   │   │   ├── [ 669 May 27 19:34] application.properties +│   │   │   └── [ 96 Feb 18 16:11] graphql +│   │   │   └── [1.3K Feb 18 16:11] schema.graphqls +│   │   └── [ 128 May 27 19:34] test +│   │   ├── [ 96 Feb 18 16:11] kotlin +│   │   │   └── [ 96 Feb 18 16:11] io +│   │   │   └── [ 128 Feb 18 16:11] sentry +│   │   │   ├── [ 236 Feb 18 16:11] DummyTest.kt +│   │   │   └── [ 192 May 27 19:34] systemtest +│   │   │   ├── [7.1K May 27 19:34] DistributedTracingSystemTest.kt +│   │   │   ├── [1.1K May 27 19:34] GraphqlGreetingSystemTest.kt +│   │   │   ├── [1.6K May 27 19:34] PersonSystemTest.kt +│   │   │   └── [ 716 May 27 19:34] TodoSystemTest.kt +│   │   └── [ 96 Feb 18 16:11] resources +│   │   └── [ 501 Feb 18 16:11] logback.xml +│   └── [ 224 May 27 19:34] sentry-samples-spring-jakarta +│   ├── [ 660 Feb 18 16:11] README.md +│   ├── [1.7K May 27 19:34] build.gradle.kts +│   ├── [ 105 Feb 18 16:11] sentry-jakarta-text-master.properties +│   ├── [ 134 Feb 18 16:11] sentry-package-rename.properties +│   └── [ 96 Feb 18 16:11] src +│   └── [ 128 Feb 18 16:11] main +│   ├── [ 96 Feb 18 16:11] java +│   │   └── [ 96 Feb 18 16:11] io +│   │   └── [ 96 Feb 18 16:11] sentry +│   │   └── [ 96 Feb 18 16:11] samples +│   │   └── [ 96 Feb 18 16:11] spring +│   │   └── [ 256 Feb 18 16:11] jakarta +│   │   ├── [ 626 Feb 18 16:11] AppConfig.java +│   │   ├── [1.6K Feb 18 16:11] AppInitializer.java +│   │   ├── [1.7K Feb 18 16:11] SecurityConfiguration.java +│   │   ├── [1.2K Feb 18 16:11] SentryConfig.java +│   │   ├── [1.3K Feb 18 16:11] WebConfig.java +│   │   └── [ 160 May 27 19:34] web +│   │   ├── [ 520 Feb 18 16:11] Person.java +│   │   ├── [1.4K May 27 19:34] PersonController.java +│   │   └── [ 984 Feb 18 16:11] PersonService.java +│   └── [ 128 Feb 18 16:11] resources +│   ├── [ 470 Feb 18 16:11] logback.xml +│   └── [ 47 Feb 18 16:11] sentry.properties +├── [ 192 May 27 20:08] sentry-servlet +│   ├── [ 96 Feb 18 16:11] api +│   │   └── [ 777 Feb 18 16:11] sentry-servlet.api +│   ├── [2.5K May 27 19:34] build.gradle.kts +│   └── [ 128 Feb 18 16:11] src +│   ├── [ 128 Feb 18 16:11] main +│   │   ├── [ 96 Feb 18 16:11] java +│   │   │   └── [ 96 Feb 18 16:11] io +│   │   │   └── [ 96 Feb 18 16:11] sentry +│   │   │   └── [ 160 May 27 19:34] servlet +│   │   │   ├── [2.3K Feb 18 16:11] SentryRequestHttpServletRequestProcessor.java +│   │   │   ├── [ 992 May 27 19:34] SentryServletContainerInitializer.java +│   │   │   └── [2.3K Feb 18 16:11] SentryServletRequestListener.java +│   │   └── [ 96 Feb 18 16:11] resources +│   │   └── [ 96 Feb 18 16:11] META-INF +│   │   └── [ 96 Feb 18 16:11] services +│   │   └── [ 52 Feb 18 16:11] javax.servlet.ServletContainerInitializer +│   └── [ 96 Feb 18 16:11] test +│   └── [ 96 Feb 18 16:11] kotlin +│   └── [ 96 Feb 18 16:11] io +│   └── [ 96 Feb 18 16:11] sentry +│   └── [ 160 Feb 18 16:11] servlet +│   ├── [3.8K Feb 18 16:11] SentryRequestHttpServletRequestProcessorTest.kt +│   ├── [ 763 Feb 18 16:11] SentryServletContainerInitializerTest.kt +│   └── [2.2K Feb 18 16:11] SentryServletRequestListenerTest.kt +├── [ 192 May 27 20:08] sentry-servlet-jakarta +│   ├── [ 96 Feb 18 16:11] api +│   │   └── [ 819 Feb 18 16:11] sentry-servlet-jakarta.api +│   ├── [2.5K May 27 19:34] build.gradle.kts +│   └── [ 128 Feb 18 16:11] src +│   ├── [ 128 Feb 18 16:11] main +│   │   ├── [ 96 Feb 18 16:11] java +│   │   │   └── [ 96 Feb 18 16:11] io +│   │   │   └── [ 96 Feb 18 16:11] sentry +│   │   │   └── [ 96 Feb 18 16:11] servlet +│   │   │   └── [ 160 May 27 19:34] jakarta +│   │   │   ├── [2.3K Feb 18 16:11] SentryRequestHttpServletRequestProcessor.java +│   │   │   ├── [1022 May 27 19:34] SentryServletContainerInitializer.java +│   │   │   └── [2.3K Feb 18 16:11] SentryServletRequestListener.java +│   │   └── [ 96 Feb 18 16:11] resources +│   │   └── [ 96 Feb 18 16:11] META-INF +│   │   └── [ 96 Feb 18 16:11] services +│   │   └── [ 60 Feb 18 16:11] jakarta.servlet.ServletContainerInitializer +│   └── [ 96 Feb 18 16:11] test +│   └── [ 96 Feb 18 16:11] kotlin +│   └── [ 96 Feb 18 16:11] io +│   └── [ 96 Feb 18 16:11] sentry +│   └── [ 96 Feb 18 16:11] servlet +│   └── [ 160 Feb 18 16:11] jakarta +│   ├── [5.2K Feb 18 16:11] SentryRequestHttpServletRequestProcessorTest.kt +│   ├── [ 755 Feb 18 16:11] SentryServletContainerInitializerTest.kt +│   └── [2.2K Feb 18 16:11] SentryServletRequestListenerTest.kt +├── [ 192 May 27 20:08] sentry-spring +│   ├── [ 96 May 27 19:34] api +│   │   └── [ 18K May 27 19:34] sentry-spring.api +│   ├── [3.7K May 27 19:34] build.gradle.kts +│   └── [ 128 Feb 18 16:11] src +│   ├── [ 128 Feb 18 16:11] main +│   │   ├── [ 96 Feb 18 16:11] java +│   │   │   └── [ 96 Feb 18 16:11] io +│   │   │   └── [ 96 Feb 18 16:11] sentry +│   │   │   └── [ 800 May 27 19:34] spring +│   │   │   ├── [1.4K Feb 18 16:11] ContextTagsEventProcessor.java +│   │   │   ├── [1.7K Feb 18 16:11] EnableSentry.java +│   │   │   ├── [2.1K Feb 18 16:11] HttpServletRequestSentryUserProvider.java +│   │   │   ├── [1.3K Feb 18 16:11] RequestPayloadExtractor.java +│   │   │   ├── [3.2K Feb 18 16:11] SentryExceptionResolver.java +│   │   │   ├── [4.4K May 27 19:34] SentryHubRegistrar.java +│   │   │   ├── [3.1K Feb 18 16:11] SentryInitBeanPostProcessor.java +│   │   │   ├── [1.4K Feb 18 16:11] SentryRequestHttpServletRequestProcessor.java +│   │   │   ├── [4.3K Feb 18 16:11] SentryRequestResolver.java +│   │   │   ├── [6.7K Feb 18 16:11] SentrySpringFilter.java +│   │   │   ├── [1012 Feb 18 16:11] SentrySpringServletContainerInitializer.java +│   │   │   ├── [1009 Feb 18 16:11] SentryTaskDecorator.java +│   │   │   ├── [3.1K Feb 18 16:11] SentryUserFilter.java +│   │   │   ├── [ 339 Feb 18 16:11] SentryUserProvider.java +│   │   │   ├── [ 747 Feb 18 16:11] SentryWebConfiguration.java +│   │   │   ├── [1.6K May 27 19:34] SpringProfilesEventProcessor.java +│   │   │   ├── [1.2K Feb 18 16:11] SpringSecuritySentryUserProvider.java +│   │   │   ├── [ 256 Feb 18 16:11] checkin +│   │   │   │   ├── [1.1K Feb 18 16:11] SentryCheckIn.java +│   │   │   │   ├── [4.1K Feb 18 16:11] SentryCheckInAdvice.java +│   │   │   │   ├── [1.3K Feb 18 16:11] SentryCheckInAdviceConfiguration.java +│   │   │   │   ├── [1.2K Feb 18 16:11] SentryCheckInPointcutConfiguration.java +│   │   │   │   ├── [ 709 Feb 18 16:11] SentryQuartzConfiguration.java +│   │   │   │   └── [ 559 Feb 18 16:11] SentrySchedulerFactoryBeanCustomizer.java +│   │   │   ├── [ 224 Feb 18 16:11] exception +│   │   │   │   ├── [ 536 Feb 18 16:11] SentryCaptureExceptionParameter.java +│   │   │   │   ├── [2.2K Feb 18 16:11] SentryCaptureExceptionParameterAdvice.java +│   │   │   │   ├── [ 556 Feb 18 16:11] SentryCaptureExceptionParameterConfiguration.java +│   │   │   │   ├── [1.3K Feb 18 16:11] SentryCaptureExceptionParameterPointcutConfiguration.java +│   │   │   │   └── [1.5K Feb 18 16:11] SentryExceptionParameterAdviceConfiguration.java +│   │   │   ├── [ 256 Feb 18 16:11] graphql +│   │   │   │   ├── [4.1K Feb 18 16:11] SentryBatchLoaderRegistry.java +│   │   │   │   ├── [1.5K Feb 18 16:11] SentryDataFetcherExceptionResolverAdapter.java +│   │   │   │   ├── [1.3K Feb 18 16:11] SentryDgsSubscriptionHandler.java +│   │   │   │   ├── [ 812 Feb 18 16:11] SentryGraphqlBeanPostProcessor.java +│   │   │   │   ├── [2.6K Feb 18 16:11] SentryGraphqlConfiguration.java +│   │   │   │   └── [1.4K Feb 18 16:11] SentrySpringSubscriptionHandler.java +│   │   │   ├── [ 128 Feb 18 16:11] opentelemetry +│   │   │   │   ├── [1017 Feb 18 16:11] SentryOpenTelemetryAgentWithoutAutoInitConfiguration.java +│   │   │   │   └── [1.4K Feb 18 16:11] SentryOpenTelemetryNoAgentConfiguration.java +│   │   │   ├── [ 576 May 27 19:34] tracing +│   │   │   │   ├── [1.8K May 27 19:34] CombinedTransactionNameProvider.java +│   │   │   │   ├── [1.8K Feb 18 16:11] SentryAdviceConfiguration.java +│   │   │   │   ├── [1.1K Feb 18 16:11] SentrySpan.java +│   │   │   │   ├── [3.1K Feb 18 16:11] SentrySpanAdvice.java +│   │   │   │   ├── [4.9K Feb 18 16:11] SentrySpanClientHttpRequestInterceptor.java +│   │   │   │   ├── [4.7K Feb 18 16:11] SentrySpanClientWebRequestFilter.java +│   │   │   │   ├── [1.2K Feb 18 16:11] SentrySpanPointcutConfiguration.java +│   │   │   │   ├── [ 562 Feb 18 16:11] SentryTracingConfiguration.java +│   │   │   │   ├── [9.6K May 27 19:34] SentryTracingFilter.java +│   │   │   │   ├── [1.0K Feb 18 16:11] SentryTransaction.java +│   │   │   │   ├── [4.6K Feb 18 16:11] SentryTransactionAdvice.java +│   │   │   │   ├── [1.2K Feb 18 16:11] SentryTransactionPointcutConfiguration.java +│   │   │   │   ├── [1.3K Feb 18 16:11] SpringMvcTransactionNameProvider.java +│   │   │   │   ├── [ 786 Feb 18 16:11] SpringServletTransactionNameProvider.java +│   │   │   │   ├── [1.2K May 27 19:34] TransactionNameProvider.java +│   │   │   │   └── [ 848 May 27 19:34] TransactionNameWithSource.java +│   │   │   └── [ 224 Feb 18 16:11] webflux +│   │   │   ├── [2.5K Feb 18 16:11] SentryRequestResolver.java +│   │   │   ├── [ 858 Feb 18 16:11] SentryScheduleHook.java +│   │   │   ├── [2.3K Feb 18 16:11] SentryWebExceptionHandler.java +│   │   │   ├── [6.8K Feb 18 16:11] SentryWebFilter.java +│   │   │   └── [1.2K Feb 18 16:11] TransactionNameProvider.java +│   │   └── [ 96 Feb 18 16:11] resources +│   │   └── [ 96 Feb 18 16:11] META-INF +│   │   └── [ 96 Feb 18 16:11] services +│   │   └── [ 57 Feb 18 16:11] javax.servlet.ServletContainerInitializer +│   └── [ 128 Feb 18 16:11] test +│   ├── [ 96 Feb 18 16:11] kotlin +│   │   └── [ 96 Feb 18 16:11] io +│   │   └── [ 96 Feb 18 16:11] sentry +│   │   └── [ 608 May 27 19:34] spring +│   │   ├── [2.3K Feb 18 16:11] ContextTagsEventProcessorTest.kt +│   │   ├── [9.2K Feb 18 16:11] EnableSentryTest.kt +│   │   ├── [2.3K Feb 18 16:11] HttpServletRequestSentryUserProviderTest.kt +│   │   ├── [ 12K Feb 18 16:11] SentryCheckInAdviceTest.kt +│   │   ├── [4.8K Feb 18 16:11] SentryExceptionResolverTest.kt +│   │   ├── [ 860 Feb 18 16:11] SentryInitBeanPostProcessorTest.kt +│   │   ├── [2.3K Feb 18 16:11] SentryRequestHttpServletRequestProcessorTest.kt +│   │   ├── [ 11K Feb 18 16:11] SentrySpringFilterTest.kt +│   │   ├── [1.6K Feb 18 16:11] SentryTaskDecoratorTest.kt +│   │   ├── [5.0K Feb 18 16:11] SentryUserFilterTest.kt +│   │   ├── [3.2K May 27 19:34] SpringProfilesEventProcessorTest.kt +│   │   ├── [2.0K Feb 18 16:11] SpringSecuritySentryUserProviderTest.kt +│   │   ├── [ 96 Feb 18 16:11] exception +│   │   │   └── [2.3K Feb 18 16:11] SentryCaptureExceptionParameterAdviceTest.kt +│   │   ├── [ 96 Feb 18 16:11] graphql +│   │   │   └── [3.4K Feb 18 16:11] SentrySpringSubscriptionHandlerTest.kt +│   │   ├── [ 96 Feb 18 16:11] mvc +│   │   │   └── [ 18K Feb 18 16:11] SentrySpringIntegrationTest.kt +│   │   ├── [ 160 May 27 19:34] tracing +│   │   │   ├── [6.4K Feb 18 16:11] SentrySpanAdviceTest.kt +│   │   │   ├── [ 17K May 27 19:34] SentryTracingFilterTest.kt +│   │   │   └── [6.8K Feb 18 16:11] SentryTransactionAdviceTest.kt +│   │   └── [ 160 May 27 19:34] webflux +│   │   ├── [1.6K Feb 18 16:11] SentryScheduleHookTest.kt +│   │   ├── [ 13K May 27 19:34] SentryWebFluxTracingFilterTest.kt +│   │   └── [6.4K Feb 18 16:11] SentryWebfluxIntegrationTest.kt +│   └── [ 96 Feb 18 16:11] resources +│   └── [ 96 Feb 18 16:11] mockito-extensions +│   └── [ 18 Feb 18 16:11] org.mockito.plugins.MockMaker +├── [ 224 May 27 20:08] sentry-spring-boot +│   ├── [ 381 Feb 18 16:11] README.md +│   ├── [ 96 May 27 19:34] api +│   │   └── [3.6K May 27 19:34] sentry-spring-boot.api +│   ├── [4.6K May 27 19:34] build.gradle.kts +│   └── [ 128 Feb 18 16:11] src +│   ├── [ 128 Feb 18 16:11] main +│   │   ├── [ 96 Feb 18 16:11] java +│   │   │   └── [ 96 Feb 18 16:11] io +│   │   │   └── [ 96 Feb 18 16:11] sentry +│   │   │   └── [ 96 Feb 18 16:11] spring +│   │   │   └── [ 384 May 27 19:34] boot +│   │   │   ├── [1.6K Feb 18 16:11] InAppIncludesResolver.java +│   │   │   ├── [ 19K May 27 19:34] SentryAutoConfiguration.java +│   │   │   ├── [1.1K Feb 18 16:11] SentryLogbackAppenderAutoConfiguration.java +│   │   │   ├── [3.1K Feb 18 16:11] SentryLogbackInitializer.java +│   │   │   ├── [4.8K May 27 19:34] SentryProperties.java +│   │   │   ├── [1.2K Feb 18 16:11] SentrySpanRestTemplateCustomizer.java +│   │   │   ├── [ 767 Feb 18 16:11] SentrySpanWebClientCustomizer.java +│   │   │   ├── [1.6K Feb 18 16:11] SentrySpringVersionChecker.java +│   │   │   ├── [2.1K Feb 18 16:11] SentryWebfluxAutoConfiguration.java +│   │   │   └── [ 96 Feb 18 16:11] graphql +│   │   │   └── [3.1K Feb 18 16:11] SentryGraphqlAutoConfiguration.java +│   │   └── [ 96 Feb 18 16:11] resources +│   │   └── [ 128 Feb 18 16:11] META-INF +│   │   ├── [ 96 Feb 18 16:11] native-image +│   │   │   └── [ 96 Feb 18 16:11] io.sentry +│   │   │   └── [ 96 Feb 18 16:11] sentry +│   │   │   └── [ 273 Feb 18 16:11] proxy-config.json +│   │   └── [ 327 Feb 18 16:11] spring.factories +│   └── [ 128 Feb 18 16:11] test +│   ├── [ 128 Feb 18 16:11] kotlin +│   │   ├── [ 96 Feb 18 16:11] com +│   │   │   └── [ 96 Feb 18 16:11] acme +│   │   │   └── [ 135 Feb 18 16:11] MainBootClass.kt +│   │   └── [ 96 Feb 18 16:11] io +│   │   └── [ 96 Feb 18 16:11] sentry +│   │   └── [ 96 Feb 18 16:11] spring +│   │   └── [ 256 May 27 19:34] boot +│   │   ├── [ 49K May 27 19:34] SentryAutoConfigurationTest.kt +│   │   ├── [5.5K Feb 18 16:11] SentryLogbackAppenderAutoConfigurationTest.kt +│   │   ├── [ 13K Feb 18 16:11] SentrySpanRestTemplateCustomizerTest.kt +│   │   ├── [ 14K Feb 18 16:11] SentrySpanWebClientCustomizerTest.kt +│   │   ├── [2.1K Feb 18 16:11] SentryWebfluxAutoConfigurationTest.kt +│   │   └── [ 96 Feb 18 16:11] it +│   │   └── [ 11K Feb 18 16:11] SentrySpringIntegrationTest.kt +│   └── [ 96 Feb 18 16:11] resources +│   └── [ 96 Feb 18 16:11] mockito-extensions +│   └── [ 18 Feb 18 16:11] org.mockito.plugins.MockMaker +├── [ 320 May 27 20:08] sentry-spring-boot-jakarta +│   ├── [ 6 Feb 18 16:11] .gitignore +│   ├── [ 96 May 27 19:34] api +│   │   └── [4.7K May 27 19:34] sentry-spring-boot-jakarta.api +│   ├── [5.4K May 27 19:34] build.gradle.kts +│   ├── [ 135 Feb 18 16:11] sentry-jakarta-text-master.properties +│   ├── [ 128 Feb 18 16:11] sentry-package-rename.properties +│   ├── [ 109 Feb 18 16:11] spring-test-rename.properties +│   └── [ 128 Feb 18 16:11] src +│   ├── [ 128 Feb 18 16:11] main +│   │   ├── [ 96 Feb 18 16:11] java +│   │   │   └── [ 96 Feb 18 16:11] io +│   │   │   └── [ 96 Feb 18 16:11] sentry +│   │   │   └── [ 96 Feb 18 16:11] spring +│   │   │   └── [ 96 Feb 18 16:11] boot +│   │   │   └── [ 416 May 27 19:34] jakarta +│   │   │   ├── [1.6K Feb 18 16:11] InAppIncludesResolver.java +│   │   │   ├── [ 21K May 27 19:34] SentryAutoConfiguration.java +│   │   │   ├── [1.1K Feb 18 16:11] SentryLogbackAppenderAutoConfiguration.java +│   │   │   ├── [3.1K Feb 18 16:11] SentryLogbackInitializer.java +│   │   │   ├── [6.0K May 27 19:34] SentryProperties.java +│   │   │   ├── [1.2K Feb 18 16:11] SentrySpanRestClientCustomizer.java +│   │   │   ├── [1.2K Feb 18 16:11] SentrySpanRestTemplateCustomizer.java +│   │   │   ├── [ 783 Feb 18 16:11] SentrySpanWebClientCustomizer.java +│   │   │   ├── [1.6K Feb 18 16:11] SentrySpringVersionChecker.java +│   │   │   ├── [4.7K Feb 18 16:11] SentryWebfluxAutoConfiguration.java +│   │   │   └── [ 128 Feb 18 16:11] graphql +│   │   │   ├── [3.2K Feb 18 16:11] SentryGraphql22AutoConfiguration.java +│   │   │   └── [3.2K Feb 18 16:11] SentryGraphqlAutoConfiguration.java +│   │   └── [ 96 Feb 18 16:11] resources +│   │   └── [ 160 Feb 18 16:11] META-INF +│   │   ├── [ 96 Feb 18 16:11] native-image +│   │   │   └── [ 96 Feb 18 16:11] io.sentry +│   │   │   └── [ 96 Feb 18 16:11] sentry +│   │   │   └── [ 273 Feb 18 16:11] proxy-config.json +│   │   ├── [ 96 Feb 18 16:11] spring +│   │   │   └── [ 184 Feb 18 16:11] org.springframework.boot.autoconfigure.AutoConfiguration.imports +│   │   └── [ 105 Feb 18 16:11] spring.factories +│   └── [ 128 Feb 18 16:11] test +│   ├── [ 128 Feb 18 16:11] kotlin +│   │   ├── [ 96 Feb 18 16:11] com +│   │   │   └── [ 96 Feb 18 16:11] acme +│   │   │   └── [ 135 Feb 18 16:11] MainBootClass.kt +│   │   └── [ 96 Feb 18 16:11] io +│   │   └── [ 96 Feb 18 16:11] sentry +│   │   └── [ 96 Feb 18 16:11] spring +│   │   └── [ 96 Feb 18 16:11] boot +│   │   └── [ 288 May 27 19:34] jakarta +│   │   ├── [ 52K May 27 19:34] SentryAutoConfigurationTest.kt +│   │   ├── [5.5K Feb 18 16:11] SentryLogbackAppenderAutoConfigurationTest.kt +│   │   ├── [ 14K Feb 18 16:11] SentrySpanRestClientCustomizerTest.kt +│   │   ├── [ 13K Feb 18 16:11] SentrySpanRestTemplateCustomizerTest.kt +│   │   ├── [ 14K Feb 18 16:11] SentrySpanWebClientCustomizerTest.kt +│   │   ├── [3.8K Feb 18 16:11] SentryWebfluxAutoConfigurationTest.kt +│   │   └── [ 96 Feb 18 16:11] it +│   │   └── [ 11K Feb 18 16:11] SentrySpringIntegrationTest.kt +│   └── [ 96 Feb 18 16:11] resources +│   └── [ 96 Feb 18 16:11] mockito-extensions +│   └── [ 18 Feb 18 16:11] org.mockito.plugins.MockMaker +├── [ 128 May 27 19:34] sentry-spring-boot-starter +│   ├── [ 96 Feb 18 16:11] api +│   │   └── [ 0 Feb 18 16:11] sentry-spring-boot-starter.api +│   └── [2.2K May 27 19:34] build.gradle.kts +├── [ 160 May 27 19:34] sentry-spring-boot-starter-jakarta +│   ├── [ 6 Feb 18 16:11] .gitignore +│   ├── [ 96 Feb 18 16:11] api +│   │   └── [ 0 Feb 18 16:11] sentry-spring-boot-starter-jakarta.api +│   └── [2.4K May 27 19:34] build.gradle.kts +├── [ 288 May 27 20:08] sentry-spring-jakarta +│   ├── [ 96 May 27 19:34] api +│   │   └── [ 21K May 27 19:34] sentry-spring-jakarta.api +│   ├── [4.2K May 27 19:34] build.gradle.kts +│   ├── [ 135 Feb 18 16:11] sentry-jakarta-text-master.properties +│   ├── [ 42 Feb 18 16:11] sentry-package-rename.properties +│   ├── [ 109 Feb 18 16:11] spring-test-rename.properties +│   └── [ 128 Feb 18 16:11] src +│   ├── [ 128 Feb 18 16:11] main +│   │   ├── [ 96 Feb 18 16:11] java +│   │   │   └── [ 96 Feb 18 16:11] io +│   │   │   └── [ 96 Feb 18 16:11] sentry +│   │   │   └── [ 96 Feb 18 16:11] spring +│   │   │   └── [ 800 May 27 19:34] jakarta +│   │   │   ├── [1.5K Feb 18 16:11] ContextTagsEventProcessor.java +│   │   │   ├── [1.7K Feb 18 16:11] EnableSentry.java +│   │   │   ├── [2.1K Feb 18 16:11] HttpServletRequestSentryUserProvider.java +│   │   │   ├── [1.2K Feb 18 16:11] RequestPayloadExtractor.java +│   │   │   ├── [3.2K Feb 18 16:11] SentryExceptionResolver.java +│   │   │   ├── [4.4K May 27 19:34] SentryHubRegistrar.java +│   │   │   ├── [3.1K Feb 18 16:11] SentryInitBeanPostProcessor.java +│   │   │   ├── [1.4K Feb 18 16:11] SentryRequestHttpServletRequestProcessor.java +│   │   │   ├── [4.3K Feb 18 16:11] SentryRequestResolver.java +│   │   │   ├── [6.8K Feb 18 16:11] SentrySpringFilter.java +│   │   │   ├── [1.3K Feb 18 16:11] SentrySpringServletContainerInitializer.java +│   │   │   ├── [ 992 Feb 18 16:11] SentryTaskDecorator.java +│   │   │   ├── [3.1K Feb 18 16:11] SentryUserFilter.java +│   │   │   ├── [ 347 Feb 18 16:11] SentryUserProvider.java +│   │   │   ├── [ 755 Feb 18 16:11] SentryWebConfiguration.java +│   │   │   ├── [1.6K May 27 19:34] SpringProfilesEventProcessor.java +│   │   │   ├── [1.2K Feb 18 16:11] SpringSecuritySentryUserProvider.java +│   │   │   ├── [ 256 Feb 18 16:11] checkin +│   │   │   │   ├── [1.1K Feb 18 16:11] SentryCheckIn.java +│   │   │   │   ├── [4.1K Feb 18 16:11] SentryCheckInAdvice.java +│   │   │   │   ├── [1.3K Feb 18 16:11] SentryCheckInAdviceConfiguration.java +│   │   │   │   ├── [1.3K Feb 18 16:11] SentryCheckInPointcutConfiguration.java +│   │   │   │   ├── [ 717 Feb 18 16:11] SentryQuartzConfiguration.java +│   │   │   │   └── [ 567 Feb 18 16:11] SentrySchedulerFactoryBeanCustomizer.java +│   │   │   ├── [ 224 Feb 18 16:11] exception +│   │   │   │   ├── [ 544 Feb 18 16:11] SentryCaptureExceptionParameter.java +│   │   │   │   ├── [2.2K Feb 18 16:11] SentryCaptureExceptionParameterAdvice.java +│   │   │   │   ├── [ 564 Feb 18 16:11] SentryCaptureExceptionParameterConfiguration.java +│   │   │   │   ├── [1.3K Feb 18 16:11] SentryCaptureExceptionParameterPointcutConfiguration.java +│   │   │   │   └── [1.5K Feb 18 16:11] SentryExceptionParameterAdviceConfiguration.java +│   │   │   ├── [ 288 May 27 19:34] graphql +│   │   │   │   ├── [4.1K May 27 19:34] SentryBatchLoaderRegistry.java +│   │   │   │   ├── [1.5K Feb 18 16:11] SentryDataFetcherExceptionResolverAdapter.java +│   │   │   │   ├── [1.3K Feb 18 16:11] SentryDgsSubscriptionHandler.java +│   │   │   │   ├── [2.7K Feb 18 16:11] SentryGraphql22Configuration.java +│   │   │   │   ├── [ 821 Feb 18 16:11] SentryGraphqlBeanPostProcessor.java +│   │   │   │   ├── [2.6K Feb 18 16:11] SentryGraphqlConfiguration.java +│   │   │   │   └── [1.4K Feb 18 16:11] SentrySpringSubscriptionHandler.java +│   │   │   ├── [ 128 Feb 18 16:11] opentelemetry +│   │   │   │   ├── [1.0K Feb 18 16:11] SentryOpenTelemetryAgentWithoutAutoInitConfiguration.java +│   │   │   │   └── [1.4K Feb 18 16:11] SentryOpenTelemetryNoAgentConfiguration.java +│   │   │   ├── [ 576 May 27 19:34] tracing +│   │   │   │   ├── [1.8K May 27 19:34] CombinedTransactionNameProvider.java +│   │   │   │   ├── [1.8K Feb 18 16:11] SentryAdviceConfiguration.java +│   │   │   │   ├── [1.1K Feb 18 16:11] SentrySpan.java +│   │   │   │   ├── [3.1K Feb 18 16:11] SentrySpanAdvice.java +│   │   │   │   ├── [5.3K Feb 18 16:11] SentrySpanClientHttpRequestInterceptor.java +│   │   │   │   ├── [4.6K Feb 18 16:11] SentrySpanClientWebRequestFilter.java +│   │   │   │   ├── [1.2K Feb 18 16:11] SentrySpanPointcutConfiguration.java +│   │   │   │   ├── [ 570 Feb 18 16:11] SentryTracingConfiguration.java +│   │   │   │   ├── [9.7K May 27 19:34] SentryTracingFilter.java +│   │   │   │   ├── [1.0K Feb 18 16:11] SentryTransaction.java +│   │   │   │   ├── [4.6K Feb 18 16:11] SentryTransactionAdvice.java +│   │   │   │   ├── [1.2K Feb 18 16:11] SentryTransactionPointcutConfiguration.java +│   │   │   │   ├── [1.3K Feb 18 16:11] SpringMvcTransactionNameProvider.java +│   │   │   │   ├── [ 796 Feb 18 16:11] SpringServletTransactionNameProvider.java +│   │   │   │   ├── [1.2K May 27 19:34] TransactionNameProvider.java +│   │   │   │   └── [ 856 May 27 19:34] TransactionNameWithSource.java +│   │   │   └── [ 320 Feb 18 16:11] webflux +│   │   │   ├── [6.6K Feb 18 16:11] AbstractSentryWebFilter.java +│   │   │   ├── [2.6K Feb 18 16:11] SentryRequestResolver.java +│   │   │   ├── [ 866 Feb 18 16:11] SentryScheduleHook.java +│   │   │   ├── [3.2K Feb 18 16:11] SentryWebExceptionHandler.java +│   │   │   ├── [1.6K Feb 18 16:11] SentryWebFilter.java +│   │   │   ├── [2.0K Feb 18 16:11] SentryWebFilterWithThreadLocalAccessor.java +│   │   │   ├── [1.2K Feb 18 16:11] TransactionNameProvider.java +│   │   │   └── [ 96 Feb 18 16:11] reactor +│   │   │   └── [ 242 Feb 18 16:11] ReactorUtils.java +│   │   └── [ 96 Feb 18 16:11] resources +│   │   └── [ 96 Feb 18 16:11] META-INF +│   │   └── [ 96 Feb 18 16:11] services +│   │   └── [ 65 Feb 18 16:11] jakarta.servlet.ServletContainerInitializer +│   └── [ 96 Feb 18 16:11] test +│   └── [ 96 Feb 18 16:11] kotlin +│   └── [ 96 Feb 18 16:11] io +│   └── [ 96 Feb 18 16:11] sentry +│   └── [ 96 Feb 18 16:11] spring +│   └── [ 608 May 27 19:34] jakarta +│   ├── [2.3K Feb 18 16:11] ContextTagsEventProcessorTest.kt +│   ├── [9.2K Feb 18 16:11] EnableSentryTest.kt +│   ├── [2.3K Feb 18 16:11] HttpServletRequestSentryUserProviderTest.kt +│   ├── [ 12K Feb 18 16:11] SentryCheckInAdviceTest.kt +│   ├── [4.8K Feb 18 16:11] SentryExceptionResolverTest.kt +│   ├── [ 868 Feb 18 16:11] SentryInitBeanPostProcessorTest.kt +│   ├── [2.3K Feb 18 16:11] SentryRequestHttpServletRequestProcessorTest.kt +│   ├── [ 11K Feb 18 16:11] SentrySpringFilterTest.kt +│   ├── [1.6K Feb 18 16:11] SentryTaskDecoratorTest.kt +│   ├── [5.1K Feb 18 16:11] SentryUserFilterTest.kt +│   ├── [3.2K May 27 19:34] SpringProfilesEventProcessorTest.kt +│   ├── [2.0K Feb 18 16:11] SpringSecuritySentryUserProviderTest.kt +│   ├── [ 96 Feb 18 16:11] exception +│   │   └── [2.3K Feb 18 16:11] SentryCaptureExceptionParameterAdviceTest.kt +│   ├── [ 96 May 27 19:34] graphql +│   │   └── [3.4K May 27 19:34] SentrySpringSubscriptionHandlerTest.kt +│   ├── [ 96 Feb 18 16:11] mvc +│   │   └── [ 18K Feb 18 16:11] SentrySpringIntegrationTest.kt +│   ├── [ 160 May 27 19:34] tracing +│   │   ├── [6.4K Feb 18 16:11] SentrySpanAdviceTest.kt +│   │   ├── [ 17K May 27 19:34] SentryTracingFilterTest.kt +│   │   └── [6.8K Feb 18 16:11] SentryTransactionAdviceTest.kt +│   └── [ 160 May 27 19:34] webflux +│   ├── [1.6K Feb 18 16:11] SentryScheduleHookTest.kt +│   ├── [ 13K May 27 19:34] SentryWebFluxTracingFilterTest.kt +│   └── [6.4K Feb 18 16:11] SentryWebfluxIntegrationTest.kt +├── [ 192 May 27 20:08] sentry-system-test-support +│   ├── [ 96 May 27 19:34] api +│   │   └── [ 37K May 27 19:34] sentry-system-test-support.api +│   ├── [1.5K May 27 19:34] build.gradle.kts +│   └── [ 96 May 27 19:34] src +│   └── [ 128 May 27 19:34] main +│   ├── [ 192 May 27 19:34] graphql +│   │   ├── [ 66 May 27 19:34] greeting.graphql +│   │   ├── [ 182 May 27 19:34] project.graphql +│   │   ├── [1.3K May 27 19:34] schema.graphqls +│   │   └── [ 265 May 27 19:34] task.graphql +│   └── [ 96 May 27 19:34] kotlin +│   └── [ 96 May 27 19:34] io +│   └── [ 96 May 27 19:34] sentry +│   └── [ 160 May 27 19:34] systemtest +│   ├── [ 286 May 27 19:34] ResponseTypes.kt +│   ├── [ 96 May 27 19:34] graphql +│   │   └── [1.5K May 27 19:34] GraphqlTestClient.kt +│   └── [ 192 May 27 19:34] util +│   ├── [2.6K May 27 19:34] LoggingInsecureRestClient.kt +│   ├── [1.7K May 27 19:34] RestTestClient.kt +│   ├── [ 972 May 27 19:34] SentryMockServerClient.kt +│   └── [8.3K May 27 19:34] TestHelper.kt +├── [ 160 May 27 19:34] sentry-test-support +│   ├── [ 96 Feb 18 16:11] api +│   │   └── [3.4K Feb 18 16:11] sentry-test-support.api +│   ├── [ 892 May 27 19:34] build.gradle.kts +│   └── [ 96 Feb 18 16:11] src +│   └── [ 96 Feb 18 16:11] main +│   └── [ 96 Feb 18 16:11] kotlin +│   └── [ 96 Feb 18 16:11] io +│   └── [ 128 Feb 18 16:11] sentry +│   ├── [3.7K Feb 18 16:11] Assertions.kt +│   └── [ 160 May 27 19:34] test +│   ├── [ 853 Feb 18 16:11] Init.kt +│   ├── [3.3K May 27 19:34] Mocks.kt +│   └── [2.4K Feb 18 16:11] Reflection.kt +├── [ 69 Feb 18 16:11] sentry.properties +├── [3.0K May 27 19:34] settings.gradle.kts +└── [ 288 May 27 19:34] test + ├── [ 879 May 27 19:34] system-test-run-all.sh + ├── [1.0K May 27 19:34] system-test-run.sh + ├── [ 128 Feb 18 16:11] system-test-sentry-server-start.sh + ├── [ 53 Feb 18 16:11] system-test-sentry-server-stop.sh + ├── [4.6K Feb 18 16:11] system-test-sentry-server.py + ├── [ 867 May 27 19:34] system-test-spring-server-start.sh + └── [ 213 Feb 18 16:11] wait-for-spring.sh + +1185 directories, 2120 files pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy