RFC-0069:ELF Runner 中的标准 I/O | |
---|---|
状态 | 已接受 |
领域 |
|
说明 | ELF 组件将 stdout 和 stderr 流转发到 LogSink 服务的机制。 |
Gerrit 更改 | |
作者 | |
审核人 | |
提交日期(年-月-日) | 2021-02-02 |
审核日期(年-月-日) | 2021-02-17 |
摘要
引入了两个新标志:forward_stdout_to
和 forward_stderr_to
。
控制要接收标准输出和/或
stderr 流。启用后,视频流将由
LogSink 服务。
设计初衷
组件管理器启动后,其 stdout 和 stderr 流会 绑定到 debuglog,因为在启动的这一阶段不存在替代方案 过程。例如,Archivist(提供 LogSink 的组件) 服务本身由组件管理器启动。
直到最近,组件管理器还重定向了所有组件的 stdout 和 stderr 流式传输到内核的调试日志。这是我们通过 复制组件管理器自己的 stdout 和 stderr 句柄本身 绑定到调试日志(如上所述)。不过,这样做也带来了一些问题。 首先,用户模式组件不应写入调试日志,因为它已被预留 用于内核使用和具有高特权的用户模式组件 大多数用户模式组件都应写入 LogSink 服务。其次, 在未明确选择启用的情况下提供两个出站视频流违反了 最小权限原则。
目前,组件管理器还缺少这一功能,我们希望将其引入 同时解决了之前存在的两个缺点。而不是 隐式向所有组件授予 stdout 和/或 stderr,我们建议添加 向 ELF 运行程序添加了一个新标志,以支持显式选择启用。
组件框架团队正在开发 从 appmgr 进行长时间运行的迁移 (组件 v1)迁移到组件框架(组件 v2)。一个 迁移项目的所有主要项目 Netstack 团队所拥有的组件。stdout/stderr 支持 是迁移所有这些组件的前提条件原因在于 要求 Netstack 组件使用 Go 编写。Go 程序 与用 C++ 或 Rust 编写的代码不同,它们是在 在开发者编写的程序开始之前,在设置期间向 stderr 发出错误 。由于错误是在程序的入口点之前记录的,因此 Go 组件作者将 stdout 和 stderr 句柄绑定到 日志记录服务为解决此问题,我们可以复刻和修改 Go 的运行时, 添加必要的日志记录初始化,然后再开始执行 用户编写的 Go 程序。这无疑会给 维护一个 Go 叉子。或者,组件管理器也可以将 先将 stdout 和 stderr 句柄添加到日志记录服务(以及 Go 运行时),这样可让系统捕获错误消息, Fuchsia 的日志记录服务。
我们希望减轻 v1 的技术负担 ->v2 迁移工作目前,appmgr 提供 stdout 和 stderr 所有 v1 组件的句柄,并将它们发送到调试日志。因此, 有理由假设 Fuchsia 内的许多开发者都依赖于 功能。随着 2021 年开始迁移越来越多的组件,我们 应允许开发者维护其依赖的 stdout 和 stderr 支持。
要求
后备日志记录服务必须是 LogSink,而不是调试日志。这里有一些 为什么我们必须改用 LogSink。首先,调试日志针对的是内核 (如上所述)。其次,debuglog 使用一个小的 (128kb) 共享环 缓冲区,并在 FIFO 的基础上轮换消息。档案管理员 定期排空这些消息并将其转发给 LogSink。然而,它们很可能会导致“丢失” 调试日志缓冲区空间,然后 Archivist 将其读取到 LogSink 中。使用 LogSink 这不仅会消除 但也会降低应用被误删的可能性 使用 debuglog 的组件和进程的消息将会被丢弃。 此外,没有适当的机制来跟踪缓冲时丢失的数据量 旋转速度比排空速度快。第三,debuglog 不支持严重级别 级别(例如 DEBUG、INFO 等)这是一项至关重要的要求 来区分 stdout 消息和 stderr 消息。关于日志记录, 只能通过将每个输出流映射到特定严重级别来实现这一点。
设计
此方案是在 ELF 的 program
节中引入两个新的枚举值
组件,即 forward_stdout_to
和 forward_stderr_to
。枚举将
对于 ELF 组件是可选的,默认为 none
。如果不是,ELF
运行程序不会将 stdout 和 stderr 句柄绑定到 LogSink 服务
(当前行为),并且系统会继续忽略这些信息流。当这些
值设为 log
,ELF 运行程序将创建一个套接字,用于捕获
stdout 和 stderr 流的输出。然后转发字节
读取到 LogSink 服务。对于 stdout,它会发送 INFO 消息;
对于 stderr,它会发送警告消息。邮件将以换行符分隔,
每行都将作为发送给 LogSink 服务的原子消息进行分区。
LogSink 服务的消息大小上限为 32KB,因此我们将
字节流缓冲区为 30 KB(以便为消息元数据留出一些空间)。
超出该边界的字节将被丢弃,并且只有部分消息
LogSink 服务。这就引出了一种有趣的极端情况,
可以在 30KB 边界处进行拆分。在这种情况下,
将完成处理,无效的前半部分将是
解码格式。所有输入字节都将使用
String::from_utf8_lossy.根据此函数的 API
所有无效的 UTF-8 序列都将替换为 U+FFFD REPLACEMENT CHARACTER
。
{
program: {
"runner": "elf",
"forward_stdout_to": "log",
"forward_stderr_to": "log",
}
}
实现
由于该功能将仅限于 ELF 运行程序, 实施工作相对较小。根据我们的预测, 实施这一更改所需的资源。
性能
由于行只发送到 LogSink,因此性能成本微乎其微。最
这项功能带来的一个明显开销是解析字节流
以及拆分换行符。不过,这种开销相对较低
日志记录是一项不规则的操作,字节流本身往往
。更重要的是,这只会影响明确选择启用这项功能的组件,
行为。因此,如果确实出现了性能问题
只需将 forward_stdout_to
和 forward_stderr_to
设置为
none
,暂时恢复到 1 级,直到我们
解决潜在的性能问题。
安全注意事项
档案管理员已建立归因和流程机制 这样,运行异常的组件就无法通过发送其标准输出来拒绝服务。 因此您无需执行任何额外操作。不过,需要注意的是 提供了一种机制,可将任意字节放入另一个进程地址空间 (从组件到档案管理员)。如果缓冲区空间太小, ,不过我们在前面也提到过要采取缓解措施。此外,由于 这种实现方式对输入流的处理最少,因此可以降低风险。 权限升级的情况。如果这是一个复杂的解析器, 那么错误和漏洞会越来越多
隐私注意事项
LogSink 后端和所有 LogSink 客户端均已符合隐私保护要求, 所有日志都归因于其来源,并进行了充分的个人身份信息清理 机制。因此您无需执行任何额外操作。
测试
除了单元测试,我们还将添加集成测试 写了一封信给档案管理员。这些集成测试将使用所有受支持的语言编写, 包括 C/C++、Rust 和 Go。将不会测试 Dart,因为 Dart 组件 执行不由 ELF 运行程序处理。
文档
缺点、替代方案和未知问题
编号标识名
我们已经探索了如何将带编号的句柄从流程框架提升到组件 支持此功能的框架。尽管我们决定不这么做, 清单文件的大致设计如下所示:
{
program: {
"runner": "elf",
"handles": ["STDOUT", "STDERR"]
}
}
ELF 运行程序将读取常量字符串并将其映射到相应的
内部编号句柄最终,我们决定不采用这种做法
不知道另一个编号句柄的直接用例。此外,如果我们
确实会在 ELF 运行程序的
program
节,那么我们就可以轻松更新清单文件语法,
ELF 运行程序实现。
组件管理器
我们还探索了如何引入一种新的框架级功能,用于为已编号的 标识名。清单文件大致如下所示:
{
use: [
{
"handle": "stdout-to-log",
"from": "framework",
"number": "STDOUT",
},
{
"handle": "stderr-to-log",
"from": "framework",
"number": "STDERR",
}
]
}
鉴于上述原因,我们认为这种方法不可取, 编号标识名,而且因为是在组件管理器级别引入该模式的 从组件管理器中引发了有关 POSIX 兼容性的新问题。例如: 所有跑步者都应该实现这一点吗?跑步者是什么样的体验 不使用 stdout/stderr 的命令,如“web”跑步者?因此,我们决定 除了 此 RFC 范围。
推出新组件
我们还探索了如何实现翻译层, 解析 stdout/stderr 字节流并将其转发到 LogSink 服务, 位于由组件框架团队拥有和管理的新组件中不过, 经过多次讨论,最终决定将这种方法 不切实际,因为我们必须想出一种方法来保留日志记录归因 。档案管理员利用事件功能 组件来源信息(例如,名称),如果 我们使用中间层组件。
FDIO
我们还研究了如何使用 Fuchsia 的 POSIX 兼容性 fdio 库来实现此功能。也就是说,创建一个 stdout/stderr 文件描述符,并在内部(在 fdio 内)重定向 LogSink。不过,经过多次讨论,我们决定放弃 修改 fdio,因为这样做会增加实现难度。周三 发现存在 POSIX 兼容性的极端情况 使用 fdio 中的 LogSink 转发器实现的。另外,基于 fdio 的实现 将产生更多不确定性和重复性工作。或者,如果我们使用 如上文所述,其“开箱即用”将符合 POSIX 标准。