iii 全链路可观测性实战:用控制台与 engine 级日志/追踪观测 Linkly 短链服务

发布时间:2026/9/14 13:36:00
iii 全链路可观测性实战:用控制台与 engine 级日志/追踪观测 Linkly 短链服务
iii 全链路可观测性实战用控制台与 engine 级日志/追踪观测 Linkly 短链服务【免费下载链接】iiiEffortlessly compose, extend, and observe every service in real-time for the first time ever.项目地址: https://gitcode.com/GitHub_Trending/mo/iii本教程基于 Linkly 短链服务iii 项目的官方实战教程第二章讲解 iii 系统的端到端可观测性无需为每个服务单独接入 tracing 库只要跨 worker 调用流经引擎引擎就能自动为整个系统生成分布式追踪与结构化日志。读完本章你将掌握三件事用iii console实时查看 worker、trace 与日志为函数补充结构化日志并通过engine::logs::list直接读取用engine::traces::list/engine::traces::tree还原一次跨 worker 请求的完整调用树与耗时分布从而定位哪个环节拖慢了请求。为什么 iii 的可观测性是系统属性而非服务插件在传统微服务架构里端到端追踪通常意味着在每个服务中引入 tracing 库、手动传递 request ID、统一上报格式才能把散落的日志串成一条链路。iii 的不同之处在于每一次跨 worker 调用worker.trigger都必然经过引擎因此引擎天然拥有整张调用图可以替整个系统完成 tracing 与日志采集而不需要每个 worker 各自接入 SDK 或透传上下文。这一点在 Linkly 教程里体现得非常直接iii project init创建项目时默认的config.yaml中已包含iii-observabilityworker引擎内置注入无需也不应在config.yaml中重复声明详见 engine/src/workers/observability/README.md。此后每个请求都会自动获得一条 trace每个Logger输出例如logger.info(...)都会被自动采集并关联到对应 trace所有 worker 的日志与 span 统一汇入同一个存储与查询面。也就是说可观测性不是装在每个服务上的附加件而是系统本身固有的属性。这也正是本章想让你停下来体会的核心观点。打开控制台观察 Linkly 实时运行前置条件是引擎仍在运行第一章启动的引擎不要关。另开一个终端启动控制台——一个用于检视引擎的浏览器 UIiii console在浏览器打开 http://127.0.0.1:3113。控制台会列出你添加的每一个 worker以及它们注册的函数functions与触发器triggers。左侧导航栏的 WORKERS、FUNCTIONS、TRIGGERS、STATES、QUEUES、TRACES、LOGS、CONFIG 等面板分别对应引擎的不同可观测面。现在制造一点流量让调用在界面上实时流进来先创建一个短链再连续访问它 5 次curl -s -X POST http://127.0.0.1:3111/links \ -H Content-Type: application/json -d {url:https://iii.dev,code:iii} for n in $(seq 1 5); do curl -s -o /dev/null http://127.0.0.1:3111/s/iii; done回到控制台的 TRACES 面板你会看到GET /s/:code的追踪条目持续刷新。点击任意一条 redirect 追踪右侧会展开完整的瀑布视图waterfall展示一次跳转如何从iii-http进入linkworker、再经引擎折返的完整 span 时间线注意为了得到这些追踪你没有添加任何 tracing 库也没有在服务之间手动传递 request ID。这正是前文系统属性的直观印证。可观测性的底座iii-observability 与 OpenTelemetry控制台只是iii-observabilityworker 提供的一个展示面。从源码与内置文档看engine/src/workers/observability/skills/SKILL.md该 worker 基于OpenTelemetryOTel构建分布式追踪、结构化日志、带 rollup 的指标、告警规则、采样配置、baggage 传播全部开箱即用且每个能力面都是一个可调用的engine::*函数。这意味着你不会被控制台锁死其 traces、metrics、logs 都以 OTel 格式发射把该 worker 指向任意 OTel 兼容后端如 Honeycomb、Grafana、Datadog 等iii 的追踪数据即可直接流入若想保留控制台的本地查询能力、同时外发到 collector可将 exporter 设为bothtrace 与 log 分别对应exporter与logs_exporter两个字段。关键的配置项可通过configuration::set以 id 为iii-observability写入运行时配置或直接使用对应的OTEL_*环境变量字段类型/默认值说明enabledboolean默认falseOTEL_ENABLED是否启用 OpenTelemetry 追踪导出service_namestring默认iiiOTEL_SERVICE_NAMEtrace/metrics 中的服务名即前文日志里的service_nameexportermemory/otlp/both默认otlpOTEL_EXPORTER_TYPEtrace 导出方式本地开发常用memory生产外发可用bothendpointstring默认http://localhost:4317OTEL_EXPORTER_OTLP_ENDPOINTOTLP collector 地址https://走 TLShttp://走明文sampling_ratio0.0–1.0默认1.0OTEL_TRACES_SAMPLER_ARG全局 trace 采样比例memory_max_spansnumber默认1000OTEL_MEMORY_MAX_SPANS内存中保留的最大 span 数logs_enabledboolean是否启用结构化日志存储logs_exportermemory/otlp/both默认memoryOTEL_LOGS_EXPORTER日志导出方式logs_max_count/logs_retention_secondsnumber默认1000/3600内存日志条数上限与保留时长leveltrace/debug/info/warn/error默认info存储的最低日志级别alertsAlertRule[]基于指标的告警规则如iii.invocations.error超过阈值触发OTLP 传输默认走 gRPC如需 OTLP/HTTP protobuf可在启动引擎前设置OTEL_EXPORTER_OTLP_PROTOCOLhttp/protobuf或用OTEL_EXPORTER_OTLP_TRACES_PROTOCOL/OTEL_EXPORTER_OTLP_METRICS_PROTOCOL做信号级覆盖iii 会自动为 traces/metrics 追加/v1/traces、/v1/metrics路径日志则发往/v1/logs。认证头可通过OTEL_EXPORTER_OTLP_HEADERS等环境变量注入凭证应放在环境变量或密钥管理器中不要写进配置文件。对大多数团队而言控制台或你自己的 OTel 后端已经覆盖日常需求。本章剩余部分是一个可选深挖直接从引擎读取同样的日志与追踪。这正是把可观测性接入脚本、CI 或 Agent 的方式。如果只想走常规路径可以直接跳去 Ch. 3: Persist everything 学习持久化。给link::resolve补上日志第一章里link::create已经写入了日志logger.info(link created, { code, url })。为了让每一次短码解析也有据可查给link::resolve加一行对应的结构化日志。编辑link/src/index.tsworker.registerFunction(link::resolve, async (payload: { code: string }) { const stored await worker.trigger{ scope: string; key: string }, { url: string } | null({ function_id: state::get, payload: { scope: links, key: payload.code }, }); logger.info(link resolved, { code: payload.code, found: !!stored?.url }); return { url: stored?.url ?? null }; });关键点在于logger.info(link resolved, { code: payload.code, found: !!stored?.url })第二个参数是一个结构化对象它会原样进入日志的log.data字段稍后你会从引擎读回它。由于 worker 以tsx watch方式运行见第一章的package.json保存文件后 worker 会自动重载新日志即刻生效。保存后制造一些混合流量——包含一次成功解析、5 次重复访问、以及一次必然 404 的未知短码curl -s -X POST http://127.0.0.1:3111/links \ -H Content-Type: application/json -d {url:https://iii.dev,code:iii} for n in $(seq 1 5); do curl -s -o /dev/null http://127.0.0.1:3111/s/iii; done curl -s -o /dev/null http://127.0.0.1:3111/s/missing从引擎直接读取日志控制台能看引擎也能查。iii trigger除了调用业务函数还可以调用引擎内置的遥测函数。读取最近 100 条日志并用jq只筛出link resolved条目、投影出我们关心的字段iii trigger engine::logs::list limit100 \ | jq .logs[] | select(.body link resolved) | { body, log.data: .attributes[log.data], trace_id, service_name }这里的jq管道把响应裁剪到link resolved条目只保留本教程关心的字段去掉管道可以看引擎能提供的全部信息。输出类似{ body: link resolved, log.data: { code: iii, found: true }, trace_id: 6b20e1fe001742c25bb7dc570b57fe42, service_name: iii-node }逐字段解读log.data与你传给logger.info的对象完全一致——这是结构化日志的价值机器可读、可过滤、可聚合trace_id把这条日志关联回它所属的 trace这正是下一步要追查的线索service_name是iii-node说明这条日志由 Node SDK 侧的 worker 记录。engine::logs::list支持丰富的过滤参数对应实现见 engine/src/trigger_formats.rs 中engine::traces::list/engine::traces::tree/engine::logs::list的函数注册日志查询面还支持start_time/end_time/trace_id/span_id/severity_min/severity_text/offset/limit等过滤器详见 engine/src/workers/observability/skills/SKILL.md 的函数表。此外iii-observability还提供engine::log::info等五个发射函数info/warn/error/debug/trace以及engine::logs::clear用于清空内存日志存储。追踪一次跨 worker 的跳转每个请求同时也是一条 trace。先取最近一条 redirect 追踪iii trigger engine::traces::list nameGET /s/:code limit1拿到结果中的trace_id后用engine::traces::tree把整条请求还原成一棵父子 span 树。下面的jq脚本会按深度缩进每个 span并打印其service_name与耗时毫秒iii trigger engine::traces::tree trace_idtrace_id | jq -r def walk(depth): ( * depth // ) .name ( .service_name ) (((.end_time_unix_nano - .start_time_unix_nano) / 1e6 * 1000 | round) / 1000 | tostring) ms, (.children[]? | walk(depth 1)); .roots[] | walk(0) jq管道递归遍历roots树按深度缩进并打印service_name与毫秒级耗时。一次跳转的完整路径横跨两个 worker输出形如GET /s/:code (iii) 2.32 ms call http::redirect (iii) 2.228 ms call http::redirect (iii-node) 1.335 ms handle_invocation link::resolve (iii) 0.624 ms call link::resolve (iii) 0.582 ms call link::resolve (iii-node) 0.174 ms读这棵树你能看清整条链路请求经iii-http到达产生根 spanGET /s/:code引擎侧服务名iii引擎调用linkworker 上的http::redirectspan 在引擎侧iii与 worker 侧iii-node各记录一次http::redirect内部再经引擎调用link::resolve同样两侧各有一个 span直至iii-state返回结果。每个 span 的耗时都单独计时——这就是请求慢下来时你该去看的视图通过逐级耗时对比能立刻判断瓶颈在引擎调度、worker 处理还是某个下游函数。注意这里 worker 侧的 span 通过 OTLP 摄取会有短暂的导出延迟因此刚发起的请求 trace 可能在最初一两秒看起来不完整稍等片刻或改读稍早的 trace 即可。engine::traces::list的查询参数从源码看engine/src/workers/observability/mod.rs 中的TracesListInputengine::traces::list支持以下过滤器与分页参数非常适合接入脚本或 Agenttrace_id按指定 trace ID 精确过滤trace_ids支持一次展开一组 traceservice_name/name按服务名或 span 名做大小写不敏感的子串匹配statuserror/pending/ok/unsetmin_duration_ms/max_duration_ms按 span 时长毫秒支持亚毫秒精度过滤这正是找慢请求的基础start_time/end_timeUnix 毫秒时间窗过滤sort_bystart_time/duration别名duration_ms/service_name/name默认start_time与sort_orderasc/desc默认ascsearch_all_spans为 true 时按 trace 内任意span 匹配 name 过滤而非仅匹配根 spanattribute_projection只返回指定任意属性缩小响应体offset/limit默认 100用于分页include_internal是否包含引擎内部engine.*span默认排除。配套的engine::traces::spans返回完整 span 记录含 attributes、events、links适合需要完整载荷的细节/时间线消费者engine::traces::tree则以trace_id为必填参数返回层级树另有engine::traces::group_by可按属性聚合 span 统计、engine::traces::clear清空内存 span 存储。找出最慢的短链单条 trace 只能看一次请求要横向对比大量请求就按耗时把 redirect span 排序最慢的排最前iii trigger engine::traces::list nameGET /s/:code sort_byduration_ms sort_orderdesc limit10 | jq -c [.spans[] | ((.end_time_unix_nano - .start_time_unix_nano) / 1e6 * 1000 | round / 1000)]最慢的 redirect 会浮到列表顶部拿到任意一条的trace_id后用engine::traces::tree展开就能定位是哪一个 hop引擎调度、worker 处理、下游函数在拖后腿。这正是把用户感觉慢转化为具体 span 慢的标准排查路径。注当前仓库文档中标注了一个验证遗留问题——sort_byduration_ms的排序在引擎某次修复落地前可能出现倒序/未排序的情况若排序结果与预期不符可先手动比对 span 时长待引擎修复后此行为将恢复正常。小结与下一步至此Linkly 已经可观测了控制台iii console实时展示每一个 worker、trace 与 log用iii trigger engine::logs::list可读取同样的结构化日志trace_id将日志与 trace 关联用engine::traces::tree可把一次跨两个 worker 的跳转还原成带逐级耗时的 span 树用engine::traces::list的排序与时长过滤能力可横向对比、找出最慢请求。你全程没有引入任何 tracing 库或手动透传 request ID——端到端可观测性是 iii 系统的固有属性由引擎内置的iii-observabilityworkerOTel 底座自动提供且同一份数据既可查于控制台、也可经engine::*函数接入脚本/CI/Agent还能以标准 OTel 协议外发到任意兼容后端。目前的链接仍只保存在内存中第一章把iii-state设成了in_memory重启引擎即被清空。下一步进入 Ch. 3: Persist everything把短链数据迁入持久化存储。【免费下载链接】iiiEffortlessly compose, extend, and observe every service in real-time for the first time ever.项目地址: https://gitcode.com/GitHub_Trending/mo/iii创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考