Expo CLI 结构化事件日志(2g):定义、发射与自动化消费指南

Expo CLI 结构化事件日志(2g):定义、发射与自动化消费指南 Expo CLI 结构化事件日志2g定义、发射与自动化消费指南【免费下载链接】expoAn open-source framework for making universal native apps with React. Expo runs on Android, iOS, and the web.项目地址: https://gitcode.com/GitHub_Trending/ex/expoExpo CLIpackages/expo/cli基于低开销会话日志库2g向外部输出结构化 JSONL 事件流供自动化工具和 Agent 发现、回放、追踪和导出。本篇基于仓库内文档 events.md 展开覆盖事件日志的激活机制、事件注册声明合并、发射方式含 span 计时与惰性序列化辅助、调试事件分级、子进程事件捕获测试以及终端噪音降级的实现细节——读完后你可以在自己的工具链中直接消费expo命令的会话事件流。一、定位与总览2g是一个低开销的会话日志器session logger每个进程一份会话事件以 JSONL每行一个 JSON 对象形式写入目标文件或标准输出流。Expo CLI直接使用该库仓库内没有额外封装层docs/events.md 明确声明 The CLI uses the library directly — there is no in-tree wrapper因此理解事件系统的入口就是 CLI 的源码本身。2g的依赖版本在 package.json 中声明为^0.4.3。本系统面向写入方设计事件名与 payload 均受 TypeScript 类型检查约束对读取方2g自带自描述 CLI2g --help覆盖选择器、过滤器、输出格式以及跟踪实时会话 / 保存 Chrome/OTLP trace / 对一次性命令进行运行并追踪等场景的用法官方文档刻意不复述这部分以避免复制内容过期失真。二、激活机制installEventLogger()每进程只调用一次入口时序CLI 入口 src/index.ts 的执行顺序是理解激活的关键先桥接旧调试开关第 7–13 行若EXPO_DEBUG1或DEBUG环境变量中含有expo/expo:*命名空间正则/(^|[,\s])expo(:|\*|$)/匹配则设置EXPO_DEBUG1并将LOG_DEBUG ?? *。源码注释强调这一步必须发生在installEventLogger()之前这样会话激活时才能识别调试标记// Bridge the legacy EXPO_DEBUG/DEBUGexpo:* switches onto 2gs LOG_DEBUG if (boolish(EXPO_DEBUG, false) || /(^|[,\s])expo(:|\*|$)/.test(process.env.DEBUG ?? )) { process.env.EXPO_DEBUG 1; process.env.LOG_DEBUG ?? *; }这使得既有用户的DEBUGexpo:metro:*等习惯用法继续生效——2g是旧DEBUGexpo:area:*命名空间的结构化继任者。解析 argv 识别子命令通过arg库解析出npx expo subcommand中的命令名默认命令为start未识别子命令回落到start。在任何输出之前安装日志器第 70–79 行// Setup event logger output before any console output. This single install handles // explicit LOG_EVENTS targets, parent IPC, and bounded command sessions in 2gs precedence order. installEventLogger({ command: args[--version] ? expo --version : args[--help] !isSubcommand ? expo --help : expo ${command}, version: process.env.__EXPO_VERSION, });command字段携带了完整的命令标识如expo start、expo --versionversion取自构建期注入的__EXPO_VERSION。单次调用即按2g自身的优先级顺序处理三种激活途径显式LOG_EVENTS目标、父进程 IPC、以及系统临时目录下有界bounded的按命令会话。LOG_EVENTS覆盖输出目标LOG_EVENTS环境变量可显式指定事件写入位置——一个文件路径或1/2表示 stdout/stderrLOG_EVENTSevents.jsonl npx expo start # 写入文件 LOG_EVENTS1 npx expo start # 写到 stdoutCLI 控制台输出走 stderr互不干扰把事件导到 stdout 是让 Agent 消费 CLI 输出的实用姿势结构化事件流与控制台人类可读输出分离在不同的流上管道中不会互相污染。三、定义事件declaration merging 而非中央注册表事件通过在2g的EventRegistry接口上做 TypeScript **声明合并declaration merging**来声明。事件键是完全限定的category:event_name字符串没有中央注册表文件——每个特性在自己的模块里就地声明自己的事件。import { events } from 2g; declare module 2g { interface EventRegistry { my_module:something_started: { platform: string }; my_module:something_finished: { platform: string }; } } export const event events(my_module);仓库中的真实案例可对照 prebuild/events.tsdeclare module 2g { interface EventRegistry { prebuild:done: { platforms: string[]; clean: boolean; template?: string; }; prebuild:template:resolved: { source: local | npm | git; name: string; version?: string; }; prebuild:pods:installed: { ms: number; skipped: boolean }; // ... } } export const event events(prebuild); export const debugEvent events.debug(prebuild);另一个更嘈杂的例子是 metro/resolveEvents.ts它声明了resolve:autolinking_registered、resolve:module、resolve:resolver_threw等十余个模块解析事件payload 字段精细到携带originModulePath、type、error等排障所需的上下文。命名约束payload 字段不得使用保留的线上wire键_e、_t、_d、_l、_w——这些是2g协议层用于元信息事件类型、时间、时长、日志级别等的字段。四、发射事件类型检查、no-op 与 span 计时发射时事件名和 payload 会被对照合并后的注册表做类型检查当日志器处于非激活状态时event()是廉价空操作cheap no-op对热路径几乎无开销。event(something_started, { platform: ios }); event(something_started, { wrong: true }); // TS errorevent.span()成对的开始/结束事件与耗时测量event.span()返回一个完成回调调用时发射结束事件并记录实测时长毫秒写入_d字段。仓库中该模式贯穿关键耗时路径例如export/exportApp.tsconst doneExport event.span();覆盖整个导出流程prebuild/prebuildAsync.tsprebuild:done结束事件携带platforms、clean、template等汇总字段install/installAsync.ts、run/android/runAndroidAsync.ts 等。const done event.span(); // ...work... done(something_finished, { platform: ios });失败分支同样被记录如 runIosAsync.ts 中event(build:failed, { platform: ios, error: event.error(error) })——构建失败事件与耗时测量配合让哪一步慢/在哪一步挂直接从事件流读出。五、eventsvsevents.debug两级事件的分层events(category)值得保留在有界历史bounded history里的事件会话正常记录。events.debug(category)高频、调试级事件线上标记_l: 1。会话只有在进程以LOG_DEBUG运行时才记录它们2g读取时也需加--debug才回放。官方文档建议的分工是重要里程碑用events()高频诊断用events.debug()。例如 resolveEvents.ts 最后一行export const event events.debug(resolve);——模块解析是 Metro 打包期的高频路径全部归入 debug 级别。按子功能划分的事件类别CLI 的诊断日志按一个子功能一个类别组织2g中的类别包括devserver、tunnel、metro、resolve、hmr、inspector、manifest、middleware、ssr、rsc、router、atlas、typegen、devtools、interface、platform、run、prebuild、export、install、doctor、api、utils、telemetry等。因此LOG_DEBUGmetro:* npx expo start # 只输出 metro 子系统的事件 LOG_DEBUG* npx expo prebuild # 全部子系统可以精确针对单一子系统开调试而不是全局刷屏。旧世界DEBUGexpo:area:*的用法通过入口的桥接代码见第二节整体迁移为LOG_DEBUG*保持肌肉记忆可用。六、惰性 payload 辅助event.path()与event.error()event.path(absolutePath)和event.error(error)返回SerializedT包装器即{ toJSON(): T }只在事件真正被写入时才执行实际工作因此日志器非激活时完全跳过开销。payload 中凡是声明类型为T的位置都接受SerializedT。event.path(p)记录相对日志目标的路径例如event(config, { serverRoot: event.path(serverRoot) })。真实用例见 ESlintPrerequisite.tsevent(eslint_config_found, { path: event.path(configPath) })以及 getStaticRenderFunctions.ts 中对静态渲染文件路径的event.path(filename)。event.error(err)将错误序列化为{ name, message, code, stack, cause }且cause链递归展开。这是仓库中最普遍的用法之一隧道失败AsyncNgrok.ts、Apple ID 解析AppleAppIdResolver.ts、simctlURL scheme 解析simctl.ts等均以此记录保证事件流中的错误信息可直接用于自动化根因分析。七、读取日志2g --help是唯一事实来源官方文档明确不要找菜谱式教程直接运行2g --help。它是自描述的覆盖选择器selectors、过滤器filters、输出格式以及各场景该用哪个命令跟踪一个实时会话把会话保存为 Chrome/OTLP 格式 trace对一次性命令直接运行并追踪。文档特意强调在这里重复它只会过期因此本文同样不复制其细节——需要具体读法时以该 CLI 输出为准。八、测试captureEvents捕获子进程事件流编写针对子进程事件的测试时使用2g/api导出的captureEvents它给子进程一条**管道pipe**作为LOG_EVENTS目标——与2g record命令相同的机制。import { spawn } from node:child_process; import { captureEvents } from 2g/api; const capture captureEvents({ filter: metro:* }); const child spawn(expo, [export], capture.spawnOptions({ env: process.env })); const events await capture.attach(child).collect();要点filter: metro:*表示只捕获metro类别事件测试断言可以聚焦单一子系统capture.spawnOptions(...)负责组装带LOG_EVENTS管道目标的环境变量与 stdio 配置attach(child).collect()在进程退出后收集事件数组事件在进程自然退出时刷写flush如果子进程代码调用process.exit()硬退出应先await flushEventLogger()否则尾部事件可能丢失。九、降低终端噪音shouldReduceLogs()src/utils/interactive.ts 暴露两个判定函数源码注释说明EXPO_UNSTABLE_HEADLESS用于标示处于自动化工具中例如 E2E 测试当事件日志器激活且处于 headless 环境时应削减交互/噪音日志改由事件日志承载信息export const shouldReduceLogs () !!getEventLoggerInfo() env.EXPO_UNSTABLE_HEADLESS; export function isInteractive(): boolean { return !shouldReduceLogs() !env.CI process.stdout.isTTY; }即shouldReduceLogs()为true需要同时满足2g日志器已激活getEventLoggerInfo()非空且设置了EXPO_UNSTABLE_HEADLESSenv.ts 中该值在 WebContainer 环境下默认为真。isInteractive()在此基础上再排除 CI 环境并要求stdout.isTTY。实际消费方包括 MetroTerminalReporter.tsheadless 时抑制 Metro 的进度噪音、instantiateMetro.ts 与 startAsync.ts。E2E 工具链e2e/utils/server.ts正是通过设置EXPO_UNSTABLE_HEADLESS: 1让 CLI 安静运行、改由事件流做断言依据。十、实践要点汇总场景做法把事件写入文件供事后分析LOG_EVENTSevents.jsonl npx expo start让 Agent/脚本从管道读事件LOG_EVENTS1 npx expo export事件走 stdout人读输出走 stderr只开某子系统的调试事件LOG_DEBUGmetro:* npx expo start沿用旧的调试习惯EXPO_DEBUG1或DEBUGexpo:*入口自动桥接为LOG_DEBUG*新增事件在所属模块declare module 2g合并EventRegistry键为category:event_namepayload 避开_e/_t/_d/_l/_w测量耗时event.span()成对事件 自动_d时长记录路径/错误event.path(p)/event.error(err)惰性求值headless 自动化设置EXPO_UNSTABLE_HEADLESS1终端日志自动降级测试子进程事件captureEvents({ filter })spawnOptionsattach().collect()硬退出前await flushEventLogger()整体来看Expo CLI 的事件体系设计取向清晰写入端零中央配置每模块自注册、非激活时零成本、类型全链路检查读取端完全委托给自描述的2g工具。对需要把expo命令纳入自动化流水线或 Agent 工作流的读者LOG_EVENTScaptureEventsshouldReduceLogs()三者组合即提供了从捕获到断言的完整闭环。【免费下载链接】expoAn open-source framework for making universal native apps with React. Expo runs on Android, iOS, and the web.项目地址: https://gitcode.com/GitHub_Trending/ex/expo创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考