iii 引擎可观测性实战:基于 iii-observability Worker 的 OTel 追踪、日志、指标与告警指南
【免费下载链接】iiiEffortlessly compose, extend, and observe every service in real-time for the first time ever.项目地址: https://gitcode.com/GitHub_Trending/mo/iii
iii-observability是 iii 引擎内置的 OpenTelemetry 可观测性 Worker,它以一组engine::*可调用函数加一个log响应式触发器的方式,为引擎提供分布式追踪、结构化日志、带 rollup 的指标、告警规则、采样配置与 baggage 传播能力。阅读本篇后,你将掌握该 Worker 的完整配置面(含全部OTEL_*环境变量覆盖)、OTLP 导出协议选择、九个函数子命名空间的具体用法,以及如何在函数中实时响应日志事件(如对error级别日志分页告警),并理解其与pubsub、queue等独立 Compose Worker 的职责边界。
一、Worker 概述与核心定位
从 iii-observability SKILL.md 可以看到,该 Worker 提供的可观测性能力全部面向「引擎内部可编程消费」设计:
- 发射:通过
engine::log::*发射结构化日志; - 查询:通过
engine::logs::list、engine::traces::*、engine::metrics::list、engine::rollups::list读取已存储的遥测数据; - 检查:
engine::sampling::rules、engine::health::check、engine::alerts::list用于运行态巡检; - 响应:
log触发器在每条日志进入管道时触发对应函数,实现零轮询的实时响应。
其 Worker 声明文件 iii.worker.yaml 表明这是type: engine的内置 Worker,由引擎自动注入,不需要也不应该在config.yaml的engine.workers或项目containers:中显式声明。Worker 默认开启(配置默认值见 config.rs 中的enabled: Some(true));当被显式禁用时,发射与读取类函数仍会注册但退化为 no-op,且log触发器永不触发。
二、核心配置面与OTEL_*环境变量覆盖
Worker 的完整配置在 README.md 中有明细表格,下表整理了核心字段、默认值与环境变量覆盖:
| 字段 | 类型 | 说明 | 默认值 | 环境变量 |
|---|---|---|---|---|
enabled | boolean | 是否启用 OTel 追踪导出 | true(Worker 级默认) | OTEL_ENABLED |
service_name/service_version/service_namespace | string | OTel resource 属性(service.name/service.version/service.namespace) | "iii"/ 引擎版本 / 无 | OTEL_SERVICE_NAME/SERVICE_VERSION/SERVICE_NAMESPACE |
exporter | string | 追踪导出器:memory|otlp|both | memory | OTEL_EXPORTER_TYPE |
endpoint | string | OTLP collector 基础端点 | http://localhost:4317 | OTEL_EXPORTER_OTLP_ENDPOINT |
sampling_ratio | number | 全局追踪采样率(0.0–1.0) | 1.0 | OTEL_TRACES_SAMPLER_ARG |
memory_max_spans | number | 内存保留的最大 span 数 | 1000 | OTEL_MEMORY_MAX_SPANS |
metrics_enabled/metrics_exporter | boolean / string | 指标采集开关与导出器(memory|otlp) | false/memory | OTEL_METRICS_ENABLED/OTEL_METRICS_EXPORTER |
metrics_retention_seconds/metrics_max_count | number | 指标内存保留时长与点数上限 | 3600/10000 | OTEL_METRICS_RETENTION_SECONDS/OTEL_METRICS_MAX_COUNT |
logs_enabled/logs_exporter | boolean / string | 结构化日志存储开关与导出器(memory|otlp|both) | true/memory | OTEL_LOGS_EXPORTER |
logs_max_count/logs_retention_seconds/logs_sampling_ratio | number | 日志内存上限、保留时长、保留比例 | 1000/3600/1.0 | — |
logs_console_output | boolean | 是否将摄入日志打印到控制台 | true | — |
level | string | 最小日志级别:trace|debug|info|warn|error | info | — |
format | string | 日志输出格式:default|json | default | — |
alerts | AlertRule[] | 针对指标的告警规则 | [] | — |
字段的实际序列化与校验逻辑定义在 config.rs 中:顶层配置结构体ObservabilityWorkerConfig标注了#[serde(deny_unknown_fields)],未知字段会在configuration::set时被 JSON Schema 拒绝;sampling_ratio、logs_sampling_ratio等比率字段被#[schemars(range(min = 0.0, max = 1.0))]约束在0..=1,memory_max_spans、metrics_max_count等计数字段下限为 1。
1.enabled默认值的两处口径
需要注意:README 配置表中enabled字段标注默认false(对应 serde 的Option::None语义),而 config.rs 的 Default 实现 在 Worker 自动注入且无config:块时使用enabled: Some(true)。因此实际行为是:Worker 默认开启;SKILL.md 中的「The worker is on by default (enabled: true)」即指此默认值。手动在config.yaml的对应配置块中显式设置enabled: false才会关闭。
2. 配置的持久化与${VAR:default}占位符
配置以iii-observability为 id 注册到内置configurationWorker(见 configuration.rs 的CONFIG_ID),存储的配置项是运行时的事实来源。首次启动时用config.yaml配置块(或默认值)做 seed,之后仅当无存储值时才写入initial_value,因此运行时编辑能跨引擎重启存活。使用默认的文件后端时,配置持久化在./config/iii-observability.yaml,每次引擎启动(在日志/追踪初始化之前)都会重新读取——这也意味着即使是「仅重启生效」的字段,编辑后下次启动即可生效。
字符串字段支持${VAR:default}占位符,读取时展开。一个典型例子是 config.rs 默认值 中的service_version: "${SERVICE_VERSION:__III_ENGINE_VERSION__}":seed 保留模板形态,configuration::get读取时再按进程环境展开为实际引擎版本(with_env_expanded方法会执行同样的展开,见 config.rs)。
3. 越界值的归一化
由于存储在磁盘上的条目可能早于 schema 收紧(或被人手编辑),每次读取都会经过normalized()(config.rs):比率被钳制到0..=1,零值计数回退为内置默认(None),避免创建零容量存储。若持久化值严重越界导致启动时 schema 刷新失败,会以SCHEMA_INVALID错误 warn-and-continue,读取仍可用。
三、OTLP 传输:gRPC 默认、HTTP/protobuf 可选、鉴权头
追踪与指标默认走OTLP/gRPC;https://端点启用 TLS(使用系统根证书),http://端点使用明文传输。要改用 OTLP/HTTP protobuf,在启动引擎前设置标准协议环境变量:
export OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf按信号分别覆盖(信号级优先级更高):
export OTEL_EXPORTER_OTLP_TRACES_PROTOCOL=grpc export OTEL_EXPORTER_OTLP_METRICS_PROTOCOL=http/protobuf当选择 HTTP/protobuf 时,endpoint被视作 collector 基础 URL,iii 会自动追加信号路径:
- traces →
/v1/traces - metrics →
/v1/metrics
日志导出器固定走 OTLP/HTTP,POST 到/v1/logs。
需要鉴权或路由头时使用标准 OTLP 头环境变量:
export OTEL_EXPORTER_OTLP_HEADERS="Authorization=Bearer $OTLP_TOKEN"信号级头变量:OTEL_EXPORTER_OTLP_TRACES_HEADERS、OTEL_EXPORTER_OTLP_METRICS_HEADERS、OTEL_EXPORTER_OTLP_LOGS_HEADERS。日志导出器优先读OTEL_EXPORTER_OTLP_LOGS_HEADERS,未设置时回退到OTEL_EXPORTER_OTLP_HEADERS。凭据务必放在环境变量或密钥管理器中,不要提交进配置文件。
生产环境如果既要导出到外部 collector、又要保持 iii Console 可查询,应使用exporter: both(追踪)与logs_exporter: both(日志)——这也正是 README 的明确建议。
四、配置热更新:按字段分层的生效策略
配置通过configuration:updated事件热应用,README.md 给出了按字段分层的生效策略,这是运维排障时的关键参考:
| 分层 | 字段 | 生效方式 |
|---|---|---|
| Live(实时) | logs_console_output、logs_sampling_ratio、logs_enabled(摄入闸门)、enabled(摄入闸门) | 立即生效,按使用点读取 |
| Limits(限额) | memory_max_spans、logs_max_count、metrics_max_count、metrics_retention_seconds | 立即生效,在下一次插入或 60s 清扫时执行 |
| Swap(交换) | sampling_ratio、sampling.*、alerts、collapse_spans、level | 立即生效,编译产物重建并交换(存活的告警规则保持冷却/触发连续性) |
| Task rebuild(任务重建) | logs_exporter、logs_batch_size、logs_flush_interval_ms、logs_retention_seconds,以及logs_enabled的 false→true 切换 | 后台任务以新设置重启;logs_enabled从 false 切到 true 会复活日志存储、重建log触发器订阅者、OTLP 日志导出器与保留任务,无需重启引擎 |
| Restart-only(仅重启) | exporter、endpoint(trace 与 logs 导出器)、service_name/service_version/service_namespace(trace resource 与 logs 导出器身份)、format、metrics_enabled、metrics_exporter、enabled(管道构建) | 记录 warning,下次引擎启动通过持久化条目生效 |
其中endpoint/service_name/service_version对所有信号都是仅重启层级,目的是让 logs 与 traces 始终一起迁移到新 collector/身份,避免编辑过程中出现分裂。
底层实现上,configuration.rs 的on_config_change处理函数会忽略触发器载荷、在 apply 锁下重新拉取权威配置值——这样任何调用方都不能仅凭发送 payload 就重定向遥测;超时失败会延迟 5 秒重试一次,其余失败保持旧配置。已知局限:引擎配置文件重载若销毁并重建本 Worker,会关闭 OTLP trace/metric provider 而不重建(它们是进程级 set-once 状态),此时 OTLP 导出需要引擎重启,内存后端不受影响。
五、何时使用与边界约束
SKILL.md 明确列出了适用场景与边界,避免误用:
适用场景:在函数内部发射结构化日志、或读回已存储的日志/span/指标(而不是 shell 出去调 collector);对日志实时响应(错误分页、全量归档)而无需轮询;运维巡检引擎健康、活跃采样规则或告警状态;跨调用传播 OpenTelemetry baggage。
边界约束(来自 SKILL.md):
engine::baggage::set不会向调用方回传——baggage 传播发生在 SDK/调用层(通过 header);- 内存查询函数(
logs、traces)只有在配置了memory(或both)导出器时才有数据;otlp-only 时数据在 collector 里; logs_enabled关闭时日志管道休眠,log触发器永不触发;摄入时的level决定存储的最小严重级别;- 本 Worker 只负责观察遥测,不是通用事件总线(那是
pubsub)也不是持久队列(那是queue)——后两者是独立的 Compose Worker。
六、engine::*函数全览
所有可调用函数在 mod.rs 中通过#[function(...)]宏注册,九个子命名空间完整清单如下(对应 SKILL.md 的 Functions 章节):
日志发射(Logging)
| 函数 | 说明 |
|---|---|
engine::log::info/warn/error/debug/trace | 按命名级别发射日志,输入形状相同,仅级别不同 |
以 mod.rs 的注册代码 为例,每个级别映射到固定的 OTel severity:TRACE=1、DEBUG=5、INFO=9、WARN=13、ERROR=17。输入统一为OtelLogInput:message(string,必填)、data(object)、trace_id(string)、span_id(string)、service_name(string)。当logs_enabled为 false 或命中logs_sampling_ratio丢弃时,函数是 no-op。
日志查询(Logs API)
| 函数 | 说明 |
|---|---|
engine::logs::list | 读取已存储的 OTel 日志,可按时间、trace 关联或严重级别过滤 |
engine::logs::clear | 清空内存日志存储 |
logs::list支持过滤器:start_time、end_time、trace_id、span_id、severity_min、severity_text、offset、limit。
追踪查询(Traces API)
| 函数 | 说明 |
|---|---|
engine::traces::list | 每条 trace 返回一条紧凑摘要,子 span 贡献聚合状态/计数;支持search_all_spans全 span 搜索与attribute_projection属性投影 |
engine::traces::spans | 返回完整 span 记录(含 attributes、events、links),供详情/时间线消费 |
engine::traces::tree | 将单条 trace 还原为父子层级树(trace_id必填) |
engine::traces::group_by | 按属性值聚合已存储 span(各组计数、时长、错误数) |
engine::traces::clear | 清空已存储 span |
traces::list的输入结构(mod.rs)还包含status(error/pending/ok/unset)、min_duration_ms/max_duration_ms、start_time/end_time、sort_by(start_time/duration/service_name/name)、sort_order、attributes/exclude_attributes过滤、include_internal(是否包含engine.*内部 trace)等。traces::group_by额外支持since_ms、label_attribute(将某属性的值作为分组的可读label,重命名会自动反映为最新值)。
指标与 rollup(Metrics API)
| 函数 | 说明 |
|---|---|
engine::metrics::list | 列出带聚合统计的指标,含引擎计数器(invocations、workers、performance)、SDK 指标,以及可选的时间分桶聚合 |
engine::rollups::list | 列出指标 rollup 聚合(1 分钟、5 分钟、1 小时窗口) |
其他 API(baggage / sampling / health / alerts)
| 函数 | 说明 |
|---|---|
engine::baggage::get | 从当前 trace 上下文读取单个 baggage 值 |
engine::baggage::get_all | 读取全部 baggage 键值对 |
engine::baggage::set | 在当前 trace 上下文设置 baggage 值(不回传调用方,见上文边界) |
engine::sampling::rules | 列出当前生效的采样规则 |
engine::health::check | 返回引擎健康状态:status、components、timestamp、version |
engine::alerts::list | 列出已配置的告警规则与当前状态 |
engine::alerts::evaluate | 手动触发一轮告警评估 |
从源码实现看(mod.rs),baggage 三个函数是「诊断用途」:baggage::get/get_all读取的是当前进程上下文的 baggage(而非逐次调用的 baggage),baggage::set由于 OTel baggage 不可变,只在新 Context 上设置且不会传播回调用方,其返回的note字段明确提示「For propagation, use SDK-level baggage headers.」——真正的跨服务传播要靠 SDK 层的 header。
七、log响应式触发器:零轮询实时响应日志
log触发器在每一条日志进入引擎 OTel 日志管道时触发绑定函数——无论日志来自engine::log::*、OTLP 摄入,还是任何使用结构化日志的 Worker。每个订阅者收到的是同一份 OTel 形状的记录,因此处理器可以按严重级别、属性或 trace 关联路由。
适用场景:特定严重级别(典型是error)需要分页人工、发 Slack 或开 ticket;把日志条目实时 fan-out 到下游 sink(归档、分析、转换)而不轮询engine::logs::list。若只是按需查询已存储条目,则用engine::logs::list。
绑定步骤与代码示例
- 注册处理器:
iii.registerFunction('monitoring::on-error', handler); - 注册触发器(SKILL.md 中的 TypeScript 示例):
iii.registerTrigger({ type: "log", function_id: "monitoring::on-error", config: { level: "error", // optional. trace|debug|info|warn|error. Omit to fire on every level. }, });level是可选的,省略则所有级别都触发;指定后按该最小严重级别过滤。触发器仅在日志管道启用时触发;处理器返回值被忽略,每次条目存储后异步触发调用。
README 提供了更完整的实战示例(README.md):
const fn = iii.registerFunction("monitoring::onError", async (logEntry) => { await sendAlert({ message: logEntry.body, severity: logEntry.severity_text, traceId: logEntry.trace_id, }); return {}; }); iii.registerTrigger({ type: "log", function_id: fn.id, config: { level: "error" }, });日志条目负载字段(README.md):timestamp_unix_nano、observed_timestamp_unix_nano、severity_number、severity_text、body、attributes、trace_id、span_id、resource、service_name、instrumentation_scope_name、instrumentation_scope_version。要查看触发类型或处理函数的完整 OTel 记录形状,可运行iii get function info。
实现上,触发器类型常量LOG_TRIGGER_TYPE = "log"与订阅者管理器OtelLogTriggers定义在 mod.rs,订阅者在logs_enabled的 false→true 切换时会被重建(配合上文的热更新分层)。
八、告警规则:AlertRule 字段与动作类型
告警规则结构体AlertRule定义在 config.rs,字段如下:
| 字段 | 类型 | 说明 |
|---|---|---|
name | string | 必填,唯一规则名 |
metric | string | 必填,要监控的指标名(如iii.invocations.error) |
threshold | number | 必填,阈值 |
operator | string | 比较算子,默认greaterthan |
window_seconds | number | 评估时间窗口(秒),默认60 |
cooldown_seconds | number | 两次触发的最小间隔(秒),默认60 |
enabled | boolean | 规则是否激活,默认true |
action | AlertAction | { "type": "log" }、{ "type": "webhook", "url": "..." }或{ "type": "function", "path": "..." } |
算子命名有讲究:JSON Schema 只对外公布规范小写名greaterthan、greaterthanorequal、lessthan、lessthanorequal、equal、notequal(configuration::set按 schema 校验,远程编辑必须用这些规范名);而config.yaml中作为 serde 便利还接受符号别名>、>=、<、<=、==、!=(见 config.rs 的注释与别名定义)。config.rs 的单测 专门断言 schema 的 enum 只含规范名、不含>。AlertOperator::evaluate(config.rs)对equal/notequal使用f64::EPSILON容差比较。
README 给出一个完整配置示例(可通过configuration::set提交):
{ "function_id": "configuration::set", "payload": { "id": "iii-observability", "value": { "enabled": true, "service_name": "my-service", "service_version": "1.0.0", "exporter": "memory", "metrics_enabled": true, "logs_enabled": true, "memory_max_spans": 1000, "sampling_ratio": 1.0, "alerts": [ { "name": "high-error-rate", "metric": "iii.invocations.error", "threshold": 10, "operator": "greaterthan", "window_seconds": 60, "action": { "type": "log" } } ] } } }九、高级采样:规则、父级采样与限流
除了全局sampling_ratio,还可以用sampling块做精细控制(README 示例):
sampling: default: 1.0 parent_based: true rules: - operation: "api.*" rate: 0.1 rate_limit: max_traces_per_second: 100对应结构体为 config.rs 中的 SamplingConfig / SamplingRule / RateLimitConfig:
sampling.default:未命中任何规则时的默认采样率;sampling.parent_based:是否启用父级采样(继承父 span 的采样决策);sampling.rules[]:按operation(支持api.*通配符)或service模式匹配的规则,rate取值0.0–1.0,按声明顺序求值;sampling.rate_limit.max_traces_per_second:全局每秒最大 trace 数限流。
运行时由 sampler.rs 中的 AdvancedSampler 实现:规则先被编译为CompiledRule,命中规则则按should_sample_by_ratio采样;之后若有rate_limiter(令牌桶TokenBucket),还要再经过每秒限流闸门。可以用engine::sampling::rules检查当前生效的规则。
另外,trace 树视图还支持collapse_spans(SpanCollapseRule,见 config.rs):按 span 名模式(如trigger *)隐藏冗余的透传包装 span,并把其子 span 重挂到最近的幸存祖先,保持树连通——不修改 Worker 代码即可让 trace 视图更清爽。
十、Live(pending)spans:实时追踪视图
memory导出器(本地开发默认)下,span 在开始的瞬间就会被镜像为内存存储中的 pending 快照(live_spans字段,memory默认开启、both需显式live_spans: true或OTEL_LIVE_SPANS=true在生产环境开启、otlp-only 永不镜像)。pending 快照以pending: true与end_time_unix_nano: 0标记,只携带创建时已知的属性(span 宏字段 + baggage 印记的iii.*属性),所以status读作"unset";span 关闭时,最终 span原地替换快照(同一存储位置,每个 span id 一条记录)。
这一机制让 live 追踪视图能展示进行中的工作:引擎侧父 span(trigger <fn>、enqueue、builtincall <fn>)在子工作仍在运行时即可见,trace 一启动就出现在列表视图中。OTEL 合规性:pending 快照只存在于内存存储(及其查询视图与 trace 触发器 tick),OTLP 导出路径永不出现未完成 span——end_time_unix_nano在 OTLP 线上是语义必需字段。消费方应将pending: true(或end_time_unix_nano == 0)视为「仍在运行」:时长过滤与排序按已运行时长度量,engine::metrics::list会把它们排除在延迟统计之外。
十一、源码地图与延伸阅读
- Worker 技能文档(本文主体):engine/src/workers/observability/skills/SKILL.md
- 详细配置表、热更新分层、函数与触发器全量文档:engine/src/workers/observability/README.md
- 配置结构体、默认值、告警/采样/span 折叠规则定义:engine/src/workers/observability/config.rs
- configuration Worker 集成(注册、读取、热应用):engine/src/workers/observability/configuration.rs
- 全部
engine::*函数注册与log触发器实现:engine/src/workers/observability/mod.rs - 高级采样器(规则编译 + 令牌桶限流):engine/src/workers/observability/sampler.rs
- OTel 初始化与全局配置管理:engine/src/workers/observability/otel.rs
- Worker 声明:engine/src/workers/observability/iii.worker.yaml
综上,iii-observability把 OpenTelemetry 的发射、存储、查询与实时响应全部收敛为引擎内可编程的engine::*函数与log触发器,配合OTEL_*环境变量覆盖、按字段分层的热更新与高级采样/告警能力,开发者可以在不额外部署 collector 基础设施的前提下(memory/both导出器)获得完整且可查询的可观测性闭环,并在需要时平滑接入标准 OTLP 生态。
【免费下载链接】iiiEffortlessly compose, extend, and observe every service in real-time for the first time ever.项目地址: https://gitcode.com/GitHub_Trending/mo/iii
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考