关于 traceway-setup
traceway-setup 是用于在代码改动前分析应用架构并规划 Traceway 可观测性配置的技能。它要求先确认项目边界,再按组件集成和验证,并强调凭据安全。
主要功能
- 01分析组件、部署与现有监控
- 02提出并确认 Traceway 项目映射
- 03按后端、浏览器和移动端规划集成
- 04遵循令牌保护规则
- 05集成后执行验证
适用场景
- 01为新应用规划 Traceway 项目结构
- 02梳理多服务的监控边界
- 03在保留凭据安全的前提下接入监控
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ tracewayapp/ traceway --skill traceway-setup安装后在 Claude Code 中输入/traceway-setup确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
android.mdkubernetes.mdios.mdflutter.mddashboard-project-setup.mdfrontend-js.mdai-agent.mddata-model.md详细介绍
设置 Traceway
先分析应用,与用户商定其 Traceway 项目结构,协助他们在仪表板中创建该结构,然后再进行集成和验证。
操作约定
- 先检查,再索取凭据或修改代码。
- 说明检测到的内容、已有的追踪情况,以及每个生产组件应如何向 Traceway 上报。
- 提出一个项目映射方案,并等待用户确认后再开始实施。
- 凭据分为两类,处理方式不同:
- 设置令牌(前缀为
tws_)限定于组织范围,有效期为 6 小时,且只能用于提出设置方案;用户在 Traceway 网站上批准方案之前,不会创建任何内容。它们专为在聊天中传递而设计,因此可以要求用户粘贴设置令牌。 - 项目上报令牌和上传令牌长期有效。切勿打印其值、要求用户将其粘贴到聊天中,或将其提交到代码库。当第 3 步的应用脚本(或
tracewayCLI)将已批准方案中的上报令牌写入环境文件时,只读取其输出中的变量名称和状态,绝不读取令牌值。
- 设置令牌(前缀为
- 切勿打印在文件或环境中发现的令牌值。仅报告某个值是否存在,以及承载该值的变量名称。
- 切勿提交令牌或已填入内容的连接字符串。
调用方式
| 调用中包含 | 路径 |
|---|---|
| 项目上报令牌 + url | 对于单个组件,使用快速路径;否则执行步骤 1–2,然后基于现有项目进行集成 |
设置令牌(tws_)+ url | 执行步骤 1–2,然后从“编写方案文件”(3b)开始执行步骤 3 |
| 不含令牌 | 执行步骤 1–2,然后从步骤 3 开始 |
所有后端都通过 OpenTelemetry 集成。 后端路径只有一条,不因语言或 Web 框架而异:Go、Node、Python、PHP、Java、.NET、Ruby 及其他所有技术栈,都会将 OTLP/HTTP 导出到 <instance>/api/otel/*。Traceway 项目应使用框架 OpenTelemetry 创建,这是仪表板框架选择器中唯一的后端选项。原生 Traceway Go SDK 是特例,仅在用户明确要求时使用。
快速路径
满足以下所有条件时,跳过步骤 2 和 3,直接进入集成步骤:
- 代码库中恰好有一个可部署组件;并且
- 用户提供了(或环境中已有)实例 URL 和项目上报令牌(而非用于创建项目的
tws_设置令牌);并且 - 该令牌所属项目已与该组件匹配。
仍须执行步骤 1,因为它决定了如何进行插桩。将“提出并确认项目映射”简化为一行确认(“这是一个 Go API;它通过 OpenTelemetry 上报到你现有的 <name> 项目”),并完全跳过项目创建步骤。完整的项目映射和创建流程仅适用于包含多个可部署组件的代码库,或尚无项目的情况。对于已经提供令牌的用户,若只有一个服务,就不要让他们再经历项目映射审核。
步骤 1:分析架构
在做任何更改之前,先了解部署了哪些内容,以及已有的插桩情况:
- 框架和语言:从
package.json、go.mod、composer.json、requirements.txt/pyproject.toml、pubspec.yaml、build.gradle(.kts)、Package.swift、*.xcodeproj/*.xcworkspace、Podfile和源文件扩展名中识别。对于 iOS/Apple 目标,注明源代码使用 Swift 还是 Objective-C。此选择决定“前端和移动端”中的路径(Swift 使用原生 SDK;仅有 Objective-C 时没有原生 SDK,因此回退到 OTel)。 - 可部署组件和入口点:梳理 API、SSR 服务、浏览器应用、工作进程、调度器、CLI 和移动应用。将代码组织视为证据,而非某项内容已部署的证明。同时记录部署目标:Kubernetes 清单、Helm chart、Kustomize overlays、
Dockerfile加 compose 文件,或 cloud-init / Ansible / Terraform。它决定第 8 步中的主机指标路径。 - 现有可观测性:查找 OpenTelemetry 配置、Traceway SDK、Sentry、Datadog、New Relic、Honeycomb、日志导出器、追踪中间件、源映射或符号上传步骤,以及可观测性环境变量名称。说明 Traceway 会替换、共存于每项集成,还是对其进行扩展。绝不显示发现的凭据值。
- JS 元框架的生产环境角色(Next.js、SvelteKit、Remix、Nuxt):绝不要假定它们是全栈。
- 仅前端的信号:
next.config.*中有output: 'export'(静态导出,因此生产环境中没有服务端);没有 API 路由(app/api/**/route.*、pages/api/*)或只有简单路由;没有'use server'服务端操作;rewrites/代理配置,或NEXT_PUBLIC_API_URL风格的环境变量指向外部 API;此仓库、其他仓库或另一种语言中存在独立的后端服务;部署配置使用静态托管(S3/CloudFront、GitHub Pages、由 nginx 提供out/)。 - 服务端信号:包含实际逻辑的 API 路由、
'use server'服务端操作、在服务端代码中导入数据库客户端、SSR 读取自身数据层、部署配置中使用next start或独立输出。 - 混合或不明确:询问应用如何部署,以及框架服务端是否承担值得追踪的生产工作。 仅前端集成意味着只通过前端 SDK 集成浏览器端;不要在框架的服务端添加 OTel,也不要以其他方式对其进行检测。独立后端是单独的组件,拥有自己的 Traceway 项目。
- 仅前端的信号:
- 后台工作:查找 cron 作业、队列消费者、调度器、CLI 命令和长时间运行的工作进程。记录现有库是否已发出
CONSUMERspan。 - AI/LLM 使用情况:检查依赖项中是否包含
openai、@anthropic-ai/sdk、anthropic、langchain/@langchain/*、ai(Vercel AI SDK)、litellm、google-generativeai、cohere、openrouter,以及 Agent 框架(langgraph、crewai、autogen/ag2、pydantic-ai、@openai/agents、@mastra/*、llamaindex/llama-index、semantic-kernel)。然后判断这是否属于对话式产品(聊天机器人、助手、Agent),而不是一次性的大模型调用:查找聊天或流式聊天路由(/chat、/completions、向大模型提供数据的 SSE 或 websocket 处理程序)、消息历史持久化(messages/conversations表或 thread id)、传给模型的工具/函数调用定义,或上述任一 Agent 框架。如果属于对话式产品或涉及工具调用,请先阅读此技能目录中的ai-agent.md,再提出 AI 集成方案。 - 浏览器构建与发布流程:确定打包工具、输出目录、source map 设置、部署流水线和现有制品上传情况。
- 移动端构建与发布流程:确定 Flutter 是否经过混淆、Android 是否使用 R8/代码缩减、iOS 是否生成 dSYM,以及多个平台目录属于同一个跨平台产品还是独立发布的应用。
- 部署与主机指标:检查 Dockerfile、Compose、Kubernetes、Helm、Terraform、Ansible、cloud-init、PaaS 配置和 CI/CD。确定后端运行的主机是否适用 Traceway OTel Agent。
提出问题之前,先展示调查结果:
| 组件 | 仓库证据 | 生产环境角色 | 当前追踪情况 | 建议的 Traceway 项目 | 集成方式 | 所需凭据 |
|---|---|---|---|---|---|---|
apps/api | Go 模块 + Gin 路由 | 后端 API | OTel / 无 / 供应商 | Product Backend | OTel | 运行时令牌 |
apps/web | Svelte + Vite | 浏览器应用 | 已有 SDK / 无 | Product Web | Traceway Svelte SDK | 运行时令牌 + 上传令牌 |
使用实际路径和调查结果。明确列出尚未确认的部署假设。
步骤 2:提出并确认项目映射
说明以下默认边界:
| 应用边界 | Traceway 项目 | 应包含的内容 |
|---|---|---|
| 后端系统 | 一个使用框架 OpenTelemetry 的项目 | API 端点、子 Span、问题、后台任务、AI 跟踪、日志、应用/运行时指标和主机指标。API、工作进程、调度程序和主机 Agent 共用项目令牌;使用稳定的 service.name 值加以区分。 |
| 浏览器前端 | 每个已部署的浏览器应用各建一个独立项目,使用 React、Svelte、Vue.js 或 jQuery | 浏览器错误、Web Vitals、会话回放、分布式跟踪关联、源映射和浏览器发布元数据。 |
| 移动应用 | 每个独立发布的应用各建一个独立项目,使用 Flutter、React Native、Android 或 iOS | 移动端错误/崩溃、支持时的回放,以及构建符号或映射。一个同时面向 Android 和 iOS 的 Flutter 产品通常使用一个项目;独立的原生应用则分别使用不同项目。 |
| 全栈 JS 应用 | 若其服务器在生产环境中运行,则使用两个项目 | 服务端工作发送到 OpenTelemetry 后端项目;浏览器端工作发送到浏览器项目。 |
仪表板的框架选择器恰好提供以下九个选项:适用于所有后端的 OpenTelemetry、适用于浏览器的 React / Svelte / Vue.js / jQuery,以及适用于移动端的 Flutter / React Native / Android / iOS。列表中没有 Gin、Django、Laravel、Next.js 或 Remix,也不需要这些选项。Next.js 或 Remix 浏览器项目选择 React,其服务端则像其他后端一样选择 OpenTelemetry。后端的框架专属设置位于项目的 Connection 页面;项目创建后,该页面会询问语言和 Web 框架。
默认情况下,不要为每个后端进程创建一个项目。将 API、工作进程、AI 调用和服务器指标放在一起,可以保留其运行上下文和分布式跟踪。只有在确实存在产品、所有权、访问控制、合规性或数据隔离边界时,才拆分后端项目。
只需询问用户那些无法从代码仓库中安全确定的信息:
- 检测到的哪些组件已部署,应该纳入跟踪?
- 使用 Traceway Cloud 还是可自托管版本,哪些组织应拥有这些项目?
- 提议的名称和项目边界是否正确?
- 后端运行于 VM/主机、Kubernetes 还是无服务器/PaaS 环境,是否应采集主机指标?
- 移动端目录代表一个跨平台产品,还是多个独立发布的应用?
每个已确认的项目还必须确定步骤 3 的计划文件所需的信息:
- 框架值,须为以下九个之一:
opentelemetry、react、svelte、vuejs、jquery、flutter、react-native、android、ios。 - 环境变量配置:哪个未跟踪的环境文件保存凭据(
envFile),以及使用哪个变量名(envVar);遵循上述凭据命名规则(浏览器值使用VITE_/PUBLIC_/NEXT_PUBLIC_等公开前缀)。当代码自行拼接连接字符串,或该值用于 OTLP Authorization 标头时,envFormat为token;当 SDK 初始化直接从变量中读取完整的<token>@<instance>/api/report字符串时,envFormat为connectionString。 - 部署位置,依据步骤 1 的部署分析:如果由于组件的部署方式(Vercel/Fly/K8s/CI 注入的环境变量、密钥管理器)而无法将凭据硬编码到代码仓库中,则添加一个
deployment区块,填写平台名称和准确的命令或 UI 路径,并使用字面占位符<token>表示凭据值。网站会在获批后替换为真实值;切勿在计划中放入真实令牌。凭据通常存放在构建配置中的移动项目,一般应添加deployment区块且不设置envFile。
在用户确认此映射之前,不要修改代码。
第 3 步:创建项目
默认流程:将确认后的映射作为设置计划提交,用户在 Traceway 网站上直观审核并批准,然后通过一个简短的 curl 脚本将生成的写入令牌写入环境文件。除了 curl 和 jq,无需安装任何内容。可以复用现有且映射正确的 Traceway 项目(计划按名称匹配项目,因此列出已有名称会复用项目,而不是创建重复项目)。
3a. 获取设置令牌(如果调用时已提供令牌,则跳过)
根据账户状态选择分支;只有当仓库和对话无法确定状态时才询问:
- 已有 Traceway 账户:“打开
<instance>/setup(Traceway Cloud:https://cloud.tracewayapp.com/setup)。如果页面提示,请登录;页面会显示一个以tws_开头的设置令牌。请将其粘贴到这里,并保持页面打开。”令牌 6 小时后过期;页面上有一个 Generate New Token 按钮。 - 尚无账户:“打开
<instance>/register(Traceway Cloud:https://cloud.tracewayapp.com/register)。第 1 步会创建账户和组织。在第 2 步中,保持选中 ‘AI’;页面会显示一个以tws_开头的设置令牌。请将其粘贴到这里,并保持页面打开:我的提案会显示在那里供你审核,批准后项目会立即显示。”
在此流程中,不要让用户在网页界面中创建项目;没有令牌时不要继续。
3b. 写入计划文件
根据确认后的映射写入 traceway-setup-plan.json,每个项目对应一个条目:
{
"projects": [
{"name": "Product Backend", "framework": "opentelemetry",
"envFile": "backend/.env", "envVar": "TRACEWAY_BACKEND_TOKEN"},
{"name": "Product Web", "framework": "svelte",
"envFile": "frontend/.env", "envVar": "PUBLIC_TRACEWAY_WEB_CONNECTION_STRING",
"envFormat": "connectionString",
"deployment": {"platform": "Vercel",
"instructions": "vercel env add PUBLIC_TRACEWAY_WEB_CONNECTION_STRING production\n# paste: <token>"}}
]
}
规则:framework 必须是第 2 步中九个值之一;每个 envFile 都必须未被跟踪(创建前先确认其已加入 gitignore);envVar 必须遵循凭据命名规则;deployment.instructions 必须使用字面量 <token> 占位符,绝不能使用真实值。
3c. 提交计划并等待批准(curl)
此步骤直接使用 curl 和 jq。不要为此安装 traceway CLI;此流程不会安装任何内容。(只有在已安装的 CLI 版本足够新、包含 setup apply 时,printf '%s' "$TRACEWAY_SETUP_TOKEN" | traceway setup apply --url "$TRACEWAY_URL" --plan traceway-setup-plan.json --token-stdin 才是等效的一步式替代方案。)如果没有 jq 且无法安装,请使用手动备用方案(3e)。
先导出凭据;设置令牌可能会经过聊天,因此这样做没有问题:
export TRACEWAY_URL="<instance>" # no trailing slash
export TRACEWAY_SETUP_TOKEN="tws_..."
验证令牌并显示组织信息(此响应仅包含项目名称,可以安全显示):
curl -s -w '\nHTTP %{http_code}\n' -H "Authorization: Bearer $TRACEWAY_SETUP_TOKEN" \
"$TRACEWAY_URL/api/setup/session"
将计划作为草稿提交:
curl -s -w '\nHTTP %{http_code}\n' -X PUT \
-H "Authorization: Bearer $TRACEWAY_SETUP_TOKEN" -H 'Content-Type: application/json' \
--data-binary @traceway-setup-plan.json "$TRACEWAY_URL/api/setup/plan"
200 {"status":"pending"} 表示草稿已提交;告诉用户:“请在已打开的 Traceway 页面上审核提案,然后点击 Approve Setup。”422 表示验证错误消息:修正计划并重新提交。401 表示令牌无效或已过期:请用户从 <instance>/setup 获取新令牌。
然后使用下面的脚本等待决定。编写此脚本是因为决定响应中包含写入令牌:这些值必须从 API 直接写入环境文件,不能出现在标准输出、你的上下文或聊天中。请严格按原样将其写入 traceway-setup-wait.sh,然后运行 sh traceway-setup-wait.sh。绝不要直接获取 GET /api/setup/plan,也绝不要读取脚本写入的环境文件。
#!/bin/sh
# Waits for the setup decision, then writes each ingest token into the env
# file named by the plan. Prints statuses and variable names only.
set -eu
PLAN=${1:-traceway-setup-plan.json}
AUTH="Authorization: Bearer ${TRACEWAY_SETUP_TOKEN:?}"
URL=${TRACEWAY_URL:?}
deadline=$(($(date +%s) + 1800))
while :; do
resp=$(curl -s -H "$AUTH" "$URL/api/setup/plan")
status=$(printf '%s' "$resp" | jq -r '.status // "error"')
case "$status" in
approved) break ;;
rejected)
echo "rejected: $(printf '%s' "$resp" | jq -r '.reason // "(no reason given)"')"
exit 2 ;;
pending|none) ;;
*) echo "unexpected response, the setup token may have expired"; exit 1 ;;
esac
if [ "$(date +%s)" -ge "$deadline" ]; then
echo "timed out waiting for approval; rerun when the user is ready"
exit 3
fi
sleep 3
done
i=0
n=$(jq '.projects | length' "$PLAN")
while [ "$i" -lt "$n" ]; do
name=$(jq -r ".projects[$i].name" "$PLAN")
envfile=$(jq -r ".projects[$i].envFile // empty" "$PLAN")
envvar=$(jq -r ".projects[$i].envVar // empty" "$PLAN")
format=$(jq -r ".projects[$i].envFormat // \"token\"" "$PLAN")
pstatus=$(printf '%s' "$resp" | jq -r --arg n "$name" \
'[.projects[] | select(.name == $n)][0].status // "missing"')
if [ "$pstatus" = missing ]; then
echo "$name: missing from the approved plan"
i=$((i + 1)); continue
fi
if [ -n "$envfile" ] && [ -n "$envvar" ]; then
value=$(printf '%s' "$resp" | jq -r --arg n "$name" --arg f "$format" \
'[.projects[] | select(.name == $n)][0]
| if $f == "connectionString"
then .token + "@" + (.backendUrl | sub("/+$"; "")) + "/api/report"
else .token end')
dir=$(dirname "$envfile"); mkdir -p "$dir"
tmp=$(mktemp "$dir/.tmp-env-XXXXXX")
{ [ -f "$envfile" ] && grep -v "^${envvar}=" "$envfile" > "$tmp"; } || true
printf '%s=%s\n' "$envvar" "$value" >> "$tmp"
mv "$tmp" "$envfile"
echo "$name: $pstatus, wrote $envvar to $envfile"
else
echo "$name: $pstatus"
fi
i=$((i + 1))
done
echo "done: deployment credentials, if any, are shown on the Traceway page the user has open"
mktemp 会将环境文件的权限设为 0600。故障处理:
- 已拒绝(退出码 2):脚本会打印用户拒绝的原因。与用户一起修改映射,更新计划文件,再次提交(新的 PUT 会替换待处理草稿),然后重新运行等待脚本。已批准的项目会按名称匹配,绝不会重复创建。
- 令牌已过期或无效(401 / 退出码 1):请用户从
<instance>/setup获取新令牌,重新导出令牌、提交计划并再次运行脚本。 - 超时(退出码 3):等用户准备好后重新提交并再次运行;这样做是安全的。
成功应用后,删除计划文件和 traceway-setup-wait.sh;它们不包含机密信息,但属于设置过程中留下的临时文件。
3d. 确认并处理部署凭据
获批后,Traceway 页面会显示庆祝信息,并将用户带到新项目的仪表板,这也能确认项目已创建。对于包含 deployment 块的每个项目,页面会先显示 Next Steps 面板,其中包含准确的命令和实际凭据值;请在用户继续之前引导他们在此处完成操作,因为账号中有项目后将无法再次访问设置页面。切勿通过聊天传递这些值;如果之后需要某个值,各项目仪表板中的 Connection 页面都提供相应令牌。
3e. 手动备用方案
如果用户更倾向于点击操作、无法使用 curl 或 jq,或者 apply 持续失败,请按照 dashboard-project-setup.md 操作:由用户在仪表板 UI 中创建每个项目,并将令牌直接填入环境文件或密钥存储中。此路径完全适用旧规则:接入令牌绝不会通过聊天传递。
上传令牌在所有路径中都必须手动处理:上传工具会读取固定的变量名。 traceway-sourcemaps 读取 TRACEWAY_SOURCEMAP_TOKEN,而 dart run traceway:upload_symbols 和 iOS dSYM 脚本读取 TRACEWAY_UPLOAD_TOKEN(三者也都会读取 TRACEWAY_URL)。请将 CI 密钥命名为这些名称之一,或保留组件专用密钥并显式传入:
traceway-sourcemaps --url "$TRACEWAY_URL" --token "$TRACEWAY_WEB_UPLOAD_TOKEN" --directory ./dist
如果自创一个名称,例如 TRACEWAY_WEB_UPLOAD_TOKEN,却调用上传工具时不带 --token,那么会在发布时而非设置时失败,因此请在 CI 步骤中明确选择。上传令牌按项目生成,可在仪表板的 Connection -> Source Maps 或 Symbol Upload 中获取。
集成路径
根据项目类型选择路径,并依据“分析架构”中的发现确定项目类型,切勿仅凭框架名称判断(Next.js 仓库可能是全栈应用,也可能只是独立后端的前端)。每条路径都不能因框架不同而协商变更;这是 Traceway 接收数据的设计方式:
| 项目类型 | 路径 |
|---|---|
| 后端(任何语言) | 使用 OpenTelemetry,通过 OTLP/HTTP 导出到 <instance>/api/otel/v1/*。始终如此,包括 Go。 |
| 前端(浏览器 SPA,或在生产环境中仅运行前端的 JS 元框架) | 使用 Traceway @tracewayapp/<framework> SDK + 打包器插件 + 源码映射上传(见下文“前端和移动端”)。 |
| 全栈 JS(Next.js、SvelteKit、Remix,依据“分析架构”的结论,确实在生产环境中提供其 API/SSR) | 两侧都接入,各自使用独立的 Traceway 项目:服务端通过 OpenTelemetry,浏览器端通过前端 SDK。 |
| 移动端(Flutter、React Native、Android、原生 Swift iOS) | 使用 Traceway 平台 SDK。绝不使用 OTel。唯一例外:非 Swift 的 iOS/Apple 应用没有原生 SDK,因此会使用 OTel 库(例如 Honeycomb),像后端一样导出到 Traceway(见“前端和移动端”)。 |
所有后端集成都必须遵守的规则,以及每个 span 对应的内容表格,都在第 4 步中说明并应用。
第 4 步:后端 OTel 设置
所有语言的基本流程相同:
- 安装该语言的 OpenTelemetry SDK,以及 Web 框架和数据库客户端的自动插桩。
- 将 OTLP/HTTP 导出器指向 Traceway,并使用项目令牌作为 Bearer 标头。
- 在资源上设置
service.name(会成为 Traceway 中的 Server Name)和service.version(启用版本比较)。 - 首先验证端点分组。
三条不可协商的规则
- 端点必须使用参数化路由。
http.route必须是路由模式(/api/users/:id),不能是具体 URL。Traceway 会原样使用该值,但只有以/开头时才会使用:像app_user_show这样的路由名称会被丢弃,效果与缺少该值完全相同。没有可用路由时,它会回退到url.path,Endpoints 页面就会膨胀为每个唯一 URL 对应一行。 - 后台工作必须使用
SpanKind.CONSUMER。 使用默认INTERNAL类型且没有 HTTP 属性的根 Span 会被静默丢弃(记录在该 Span 上的异常仍会显示在 Issues 中,但任务运行本身会丢失)。 - 导出器必须使用 OpenTelemetry 项目的令牌。 如果 OTLP 携带的是浏览器项目的令牌(框架为 React、Svelte、Vue.js、jQuery 或 React Native),Traceway 只会保留 Issues,并丢弃所有端点、任务、子 Span 和 AI Trace。其表现是“错误会显示,但 Endpoints 页面为空”,原因几乎总是两个项目的令牌被交换了。
Traceway 如何分类 Span
规则按以下顺序评估,命中第一条后即停止。
| # | Span | 条件 | 分类结果 |
|---|---|---|---|
| 1 | 任意 Span | SpanKind = INTERNAL 且携带 exception.* 属性 | Issue。 即使存在 HTTP 或 gen_ai.* 属性,也绝不会升级为其他类型。子 Span 会保留其普通 Span 行,根 Span 则只会生成 Issue |
| 2 | 根 Span,或其父 Span 不在同一导出批次中的 Span | SpanKind = SERVER,或类型为 INTERNAL 且带有 HTTP 属性 | Endpoint |
| 3 | 任意 Span | SpanKind = CONSUMER | Task |
| 4 | 根 Span | 类型为 INTERNAL,且带有 console.command 属性 | Task(CLI 命令) |
| 5 | 任意 Span | 带有任意 gen_ai.* 属性 | AI Trace |
| 6 | 非根 Span | 带有父 Span ID | Span(子级) |
| 7 | 根 Span | 以上规则均未命中 | 丢弃(记录在该 Span 上的异常仍会成为未关联的 Issue) |
由于顺序是固定的,携带 gen_ai.* 的 CONSUMER Span 会被分类为 Task,携带 gen_ai.* 的 SERVER 请求 Span 会被分类为 Endpoint。请将模型调用放在单独的子 Span 上(步骤 6)。
另外两条规则与 Span 类型无关:
- 任意 Span 上的
exception事件或exception.*属性都会生成 Issue,无论该 Span 本身被分类为何种类型。 - 如果端点返回 404,且没有匹配到真实路由(缺少
http.route,或路由为/、/*这样的兜底模式),端点名称会改为字面值UNMATCHED,将机器人扫描和输错的 URL 合并为一行。具体匹配到的路由返回 404 时,则保留其自身名称。
有关精确规则、端点命名、指标转换及其他注意事项,请阅读此技能目录中的 data-model.md。这是权威参考文档。
导出器配置
如果 SDK 支持标准环境变量,请优先使用它们;这些变量在不同语言中表现一致:
OTEL_SERVICE_NAME=my-service
OTEL_RESOURCE_ATTRIBUTES=service.version=1.2.3 # there is no OTEL_SERVICE_VERSION variable
OTEL_EXPORTER_OTLP_ENDPOINT=https://<instance>/api/otel
OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf # required, several SDKs default to gRPC
OTEL_EXPORTER_OTLP_HEADERS="Authorization=Bearer <project-token>"
OTEL_TRACES_EXPORTER=otlp
OTEL_METRICS_EXPORTER=otlp
OTEL_LOGS_EXPORTER=otlp
其中两行至关重要,配置错误时却会静默失败:
OTEL_EXPORTER_OTLP_PROTOCOL不能省略。 如果未设置,Python SDK 和 Java Agent 会将otlp解析为 gRPC,而 Traceway 没有 gRPC 监听器。应用会启动、处理流量、以 0 退出,不会打印任何警告,所有导出数据都会丢失。(PHP 是唯一需要使用http/json的例外。)- 没有
OTEL_SERVICE_VERSION。service.version只能通过OTEL_RESOURCE_ATTRIBUTES传到 Traceway。缺少它时,每个端点行的 App Version 都为空,版本对比也不会生效。
SDK 会自动在端点后追加 /v1/traces、/v1/metrics、/v1/logs,因此只需设置基础 URL。若在 OTEL_EXPORTER_OTLP_ENDPOINT 中填写完整的信号路径,就会产生 /v1/traces/v1/traces;而信号专用变量(OTEL_EXPORTER_OTLP_TRACES_ENDPOINT)会原样使用,不会追加任何内容。若改为在代码中配置,完整 URL 为 https://<instance>/api/otel/v1/traces(以及 /v1/metrics、/v1/logs),请求头为 Authorization: Bearer <project-token>。
执行第 3 步的应用脚本写入环境文件后,计划中的变量在本地已经有了实际值,因此请将导出器准确连接到这些变量名(例如 Authorization=Bearer ${TRACEWAY_BACKEND_TOKEN}),并立即运行实时验证。脚本只会写入每个项目的一个计划变量:集成还会读取的配套变量,例如实例 URL 对应的 TRACEWAY_URL,需要你现在一并添加到同一个环境文件中。若使用手动备用方案,则由用户先填入这些变量。
限制:仅支持 OTLP/HTTP(protobuf 或 JSON)。不支持 OTLP/gRPC,端口 4317 上没有监听器。Content-Encoding: gzip 可用。解压后的请求体上限为 10 MB,超出上限的批次会直接以 413 和 {"error":"request body exceeds the 10MB limit"} 拒绝。不会部分摄取数据。令牌错误或缺失时返回 401,其他路径均返回 404。这些情况在应用中都无法察觉:大多数 SDK 只会在 debug 级别记录导出失败,因此如果没有数据到达,请先开启 SDK 自身的诊断日志。
Node.js 示例
npm install @opentelemetry/sdk-node @opentelemetry/auto-instrumentations-node \
@opentelemetry/instrumentation @opentelemetry/api @opentelemetry/sdk-metrics \
@opentelemetry/exporter-trace-otlp-http @opentelemetry/exporter-metrics-otlp-http
即使 @opentelemetry/api 也会作为传递依赖安装,也请自行声明它。依赖提升在 pnpm 的严格 node_modules 模式和 Yarn PnP 下会导致问题。
在项目根目录创建 instrumentation.mjs,并在应用前加载它:node --import ./instrumentation.mjs server.js。请使用 .mjs 扩展名。在 CommonJS 项目中,包含 import 语法的 .js 文件会加载失败,而大多数项目仍采用 CommonJS。
import { register } from "node:module";
register("@opentelemetry/instrumentation/hook.mjs", import.meta.url);
import { NodeSDK } from "@opentelemetry/sdk-node";
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";
import { OTLPMetricExporter } from "@opentelemetry/exporter-metrics-otlp-http";
import { PeriodicExportingMetricReader } from "@opentelemetry/sdk-metrics";
import { getNodeAutoInstrumentations } from "@opentelemetry/auto-instrumentations-node";
const url = process.env.TRACEWAY_URL;
const headers = { Authorization: `Bearer ${process.env.TRACEWAY_BACKEND_TOKEN}` };
const sdk = new NodeSDK({
serviceName: process.env.OTEL_SERVICE_NAME ?? "my-service",
traceExporter: new OTLPTraceExporter({ url: `${url}/api/otel/v1/traces`, headers }),
metricReaders: [
new PeriodicExportingMetricReader({
exporter: new OTLPMetricExporter({ url: `${url}/api/otel/v1/metrics`, headers }),
exportIntervalMillis: 30_000,
}),
],
instrumentations: [getNodeAutoInstrumentations()],
});
sdk.start();
这段代码中有三处至关重要:
- 两行
register会安装 ESM 加载器钩子。 缺少它们时,ESM 应用("type": "module"、import express from "express")仍会生成 HTTP Span,但没有http.route,因此每个 URL 都会成为单独的端点行。CommonJS 应用(require("express"))即使没有此钩子也能完成插桩,这两行在那里没有影响,所以无论哪种情况都请保留。 serviceName会成为 Traceway 中的 Server Name。若不设置,每个 Span 都会报告unknown_service:node。要进行版本对比,还需设置OTEL_RESOURCE_ATTRIBUTES=service.version=1.2.3。metricReaders是一个列表。 旧的单数选项metricReader已弃用。
自动插桩涵盖 Express 路由(设置 http.route)、状态码、错误和数据库客户端(pg、mysql2、mongodb、ioredis)。SQLite 和自定义业务逻辑需要手动创建 tracer.startActiveSpan() 子 Span。
Next.js 服务端例外
Next.js 不得使用上面的通用 instrumentation.mjs 预加载方式,也不得使用 node --import 或 NODE_OPTIONS。Next.js 会为每个传入请求创建自己的根 span,并设置匹配的 http.route;预加载通用 Node HTTP 插桩会创建一个以 URL 字面值命名的竞争根 span,因此 /api/users/1 和 /api/users/2 会成为不同的端点。
对于 Next.js 服务端,请创建一个精简的 instrumentation.ts 钩子,并将 SDK 放在单独的 instrumentation.node.ts 中。Next.js 会为 Node 和 Edge 编译该钩子;即使在运行时判断之后才通过动态 import() 调用,直接在钩子中导入 OTel 包也可能导致开发环境下的 Edge 编译解析 Node 内置模块并失败。包装器必须仅在条件满足时导入 Node 模块:
export async function register() {
if (process.env.NEXT_RUNTIME === "nodejs") {
await import("./instrumentation.node");
}
}
将所有仅限 Node 的导入和 SDK 启动代码放在同级模块中:
import { getNodeAutoInstrumentations } from "@opentelemetry/auto-instrumentations-node";
import { OTLPLogExporter } from "@opentelemetry/exporter-logs-otlp-http";
import { OTLPMetricExporter } from "@opentelemetry/exporter-metrics-otlp-http";
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";
import { resourceFromAttributes } from "@opentelemetry/resources";
import { BatchLogRecordProcessor } from "@opentelemetry/sdk-logs";
import { PeriodicExportingMetricReader } from "@opentelemetry/sdk-metrics";
import { NodeSDK } from "@opentelemetry/sdk-node";
(Error as unknown as { prepareStackTrace?: unknown }).prepareStackTrace = undefined;
const instance = process.env.TRACEWAY_URL!.replace(/\/+$/, "");
const base = `${instance}/api/otel`;
const headers = { Authorization: `Bearer ${process.env.TRACEWAY_BACKEND_TOKEN}` };
new NodeSDK({
resource: resourceFromAttributes({
"service.name": process.env.OTEL_SERVICE_NAME ?? "nextjs-server",
"service.version": process.env.APP_VERSION ?? "development",
}),
traceExporter: new OTLPTraceExporter({ url: `${base}/v1/traces`, headers }),
metricReaders: [
new PeriodicExportingMetricReader({
exporter: new OTLPMetricExporter({ url: `${base}/v1/metrics`, headers }),
exportIntervalMillis: 30_000,
}),
],
logRecordProcessors: [
new BatchLogRecordProcessor({
exporter: new OTLPLogExporter({ url: `${base}/v1/logs`, headers }),
}),
],
instrumentations: [getNodeAutoInstrumentations()],
}).start();
除通用 Node 依赖外,还要安装日志导出器和 SDK 包:@opentelemetry/exporter-logs-otlp-http、@opentelemetry/sdk-logs、@opentelemetry/api-logs 和 @opentelemetry/resources。Next.js 15 之前的版本还需要设置 experimental.instrumentationHook: true。完整设置与验证指南:https://docs.tracewayapp.com/client/otel/nextjs
在 instrumentation.node.ts 中重置 Error.prepareStackTrace,可让 Node 应用源映射,避免服务端 Issues 指向经过压缩的 Next 代码块。生产环境还必须使用 NODE_OPTIONS=--enable-source-maps 启动,并且构建必须生成 .next/server/**/*.js.map;请验证这两项,不要想当然地认为服务端堆栈可读。如果 webpack 构建未生成这些文件,请在 next.config 中添加 experimental: { serverSourceMaps: true }。还要在 serverExternalPackages 中列出 @opentelemetry/auto-instrumentations-node,以免 Next 打包插桩注册表,或针对其可选传输方式发出警告。
各语言说明
- Go:使用框架的 OTel 中间件。
go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin、github.com/riandyrn/otelchi(这是一个社区包,并且要传入otelchi.WithChiRoutes(r),否则它会报告原始 URL)、github.com/gofiber/contrib/otelfiber/v2。这三个包都会根据匹配的路由模式设置http.route。导出器:otlptracehttp.WithEndpointURL(...)+WithHeaders。Go 独有的两个陷阱:-
设置传播器。 Go 的全局传播器默认是空操作,因此不会发送或读取
traceparent,跨服务追踪也永远无法关联。紧接着otel.SetTracerProvider(tp)添加这一行:otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator( propagation.TraceContext{}, propagation.Baggage{}, )) -
标准库
net/http不会报告路由。otelhttp会从r.Pattern读取路由,而ServeMux只有在完成匹配后才会填充该字段,因此常见的顶层otelhttp.NewHandler(mux, "server")不会记录任何http.route,每个 URL 都会成为单独的端点行。可以包装每个已注册的处理器,让 span 在 mux 内部开始:mux.Handle("GET /api/users/{id}", otelhttp.NewHandler(usersHandler, "users"))也可以保留单个顶层包装,并自行设置路由:
// imports: strings, go.opentelemetry.io/otel/trace, // semconv "go.opentelemetry.io/otel/semconv/v1.26.0" func withRoute(mux *http.ServeMux) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if _, pattern := mux.Handler(r); pattern != "" { if i := strings.IndexByte(pattern, '/'); i >= 0 { route := pattern[i:] // 去掉 "GET " 前缀,http.route 必须以 "/" 开头 trace.SpanFromContext(r.Context()).SetAttributes(semconv.HTTPRoute(route)) } } mux.ServeHTTP(w, r) }) } handler := otelhttp.NewHandler(withRoute(mux), "server")
-
- Python:运行
pip install opentelemetry-distro opentelemetry-exporter-otlp,然后运行opentelemetry-bootstrap -a install,再设置上述环境变量,并通过opentelemetry-instrument启动应用(opentelemetry-instrument uvicorn app:app、opentelemetry-instrument gunicorn wsgi:app、opentelemetry-instrument python worker.py)。不加此前缀启动服务不会发送任何数据。FastAPI 和 Flask 插桩无需编写任何应用代码,就能以框架自身的语法正确设置http.route(GET /users/{user_id}、GET /orders/<order_id>);这两个框架在遇到未处理的异常时也都会返回500,因此端点状态和 Issue 会自动对应起来,无需额外处理。对于 FastAPI 0.142.0 或更高版本,下一条提到的内置遥测功能默认已启用;如果仍采用此方案,必须将应用构造为FastAPI(telemetry={"auto_configure": False}),否则 FastAPI 会添加第二个导出器,每个 span 都会发送两次到 Traceway。仅适用于 Python 的三个注意事项:-
日志还需要另外两个变量。
OTEL_LOGS_EXPORTER=otlp会将 OTel 处理器附加到根 logger,其默认级别为WARNING,因此在桥接器处理日志之前,所有logger.info(...)都会被过滤掉,只有 WARN 及以上级别的日志会发送到 Traceway。附加处理器还会替换根处理器列表,因此应用自身的记录将不再显示在 stdout 中。请同时添加以下两个变量:OTEL_PYTHON_LOG_CORRELATION=true # 恢复控制台输出,并写入 otelTraceID/otelSpanID OTEL_PYTHON_LOG_LEVEL=info # 仅与上面一行同时设置时才会读取单独设置
OTEL_PYTHON_LOG_LEVEL不会产生任何作用。请勿设置OTEL_PYTHON_LOGGING_AUTO_INSTRUMENTATION_ENABLED=true;在当前版本中,桥接器已启用,该变量会将其切换为 SDK 已弃用的处理器。 -
指标需要等待一分钟。 SDK 中
OTEL_METRIC_EXPORT_INTERVAL的默认值为 60000 ms,因此你在检查时,配置正确的流水线看起来也会是空的。验证期间请设置OTEL_METRIC_EXPORT_INTERVAL=10000。 -
Django 还需要两项额外配置:导出
DJANGO_SETTINGS_MODULE,否则opentelemetry-instrument会在启动 Django 时直接失败;另外,还要添加 Django 指南中的路由中间件,因为opentelemetry-instrumentation-django会将http.route报告为 Django 自身的模式(api/users/<int:user_id>/),该模式没有前导/,因此会被丢弃。 指南:https://docs.tracewayapp.com/client/otel/python 和 https://docs.tracewayapp.com/client/otel/django
-
- FastAPI 0.142.0 及更高版本:默认使用 FastAPI 内置遥测功能(https://fastapi.tiangolo.com/advanced/opentelemetry/)。从锁文件或
pip show fastapi中读取已安装的版本。低于 0.142.0 时没有内置遥测功能,因此请使用上文的通用 Python 路径;是否仅为此升级 FastAPI,由用户自行决定。内置路径无需 Agent,也无需opentelemetry-instrument前缀:-
安装
fastapi[standard](如果应用不使用 standard extra,则安装fastapi[opentelemetry])。纯fastapi仅包含opentelemetry-api,因此 FastAPI 会在启动时记录警告FastAPI automatic telemetry configuration failed: Automatic OpenTelemetry export requires fastapi[opentelemetry] or fastapi[standard]...,且不会导出任何内容。 -
在进程环境中设置“Exporter configuration”中的相同环境变量块。FastAPI 会在 lifespan 启动时读取这些变量,并为 traces、metrics 和 logs 构建 OTLP/HTTP exporters;exporters 会读取
OTEL_EXPORTER_OTLP_HEADERS,resource 会读取OTEL_SERVICE_NAME和OTEL_RESOURCE_ATTRIBUTES。它只支持http/protobuf。任何其他协议都会记录FastAPI automatic telemetry configuration failed,应用也不会导出任何内容。 -
按常规方式启动服务器(
fastapi run、uvicorn app:app、gunicorn -k uvicorn.workers.UvicornWorker app:app)。 -
添加异常处理器,否则不会显示 Issues。 内置遥测会将请求 span 标记为状态
500和error.type的ERROR,但只会将异常作为日志记录(http.server.request.exception)发送,不会在 span 上添加exception事件。Traceway 根据 span 异常事件创建 Issues,因此你会看到失败的端点行,但 Issues 页面为空。请在请求 span 上记录异常:from fastapi import Request from fastapi.responses import JSONResponse from opentelemetry import trace from opentelemetry.trace import Status, StatusCode @app.exception_handler(Exception) async def record_unhandled_exception(request: Request, exc: Exception): span = trace.get_current_span() span.record_exception(exc) span.set_status(Status(StatusCode.ERROR, str(exc))) return JSONResponse({"detail": "Internal Server Error"}, status_code=500)HTTPException和验证错误会保留各自的处理器,因此 404 或 422 不会被记录为 Issue。 覆盖内容:SERVER spans 中包含 FastAPI 语法的http.route(GET /users/{user_id})、状态码、fastapi.dependencies/fastapi.endpoint/fastapi.serialization子 span、traceparent传播、HTTP 服务端指标,以及 FastAPI 自身的异常和验证日志。它不覆盖数据库查询、发出的 HTTP 调用或应用自身的logging调用。如果用户需要这些功能(大多数带数据库的应用都会需要),请使用通用 Python 路径(opentelemetry-instrument),并关闭 FastAPI 的导出功能:FastAPI(telemetry={"auto_configure": False})。在opentelemetry-instrument下保留auto_configure的默认开启状态,FastAPI 会向 Agent 的 provider 添加第二个导出器,导致每个 span 都被发送两次。(FastAPI 确实会检测到 contrib 中间件并停止创建自己的 span,因此不会出现两个请求 span,只会重复导出。)请在设置计划中说明这一权衡,不要默默替用户做选择。
-
- PHP:Laravel 使用
composer require keepsuit/laravel-opentelemetry open-telemetry/exporter-otlp php-http/guzzle7-adapter;Symfony 使用composer require traceway/opentelemetry-symfony open-telemetry/exporter-otlp php-http/guzzle7-adapter(Symfony 自带的自动插桩会将http.route设为路由 NAME,而 Traceway 会丢弃该值;请参阅data-model.md)。PHP 不会像上面的配置块那样,将OTEL_PHP_AUTOLOAD_ENABLED作为普通环境变量读取。Laravel 无需额外配置,keepsuit 服务提供程序会启动 SDK。Symfony 可通过config/packages/open_telemetry.yaml中的open_telemetry.sdk.autoload_enabled: true启动 SDK,也可通过在 php-fpm、Dockerfile 或 Apache 中设置真正的进程环境变量来启动。切勿将OTEL_PHP_AUTOLOAD_ENABLED放入.env:Dotenv 会在 Composer 自动加载之后读取它,bundle 随后会跳过 SDK 启动,所有信号都会悄然变成空操作。PHP 也更适合使用OTEL_EXPORTER_OTLP_PROTOCOL=http/json;http/protobuf同样可用,但如果没有ext-protobuf,速度会更慢。- Symfony workers:切勿对
messenger:consume进行插桩。 bundle 已将其排除。traces.console.excluded_commands默认值为['messenger:consume', 'messenger:consume-messages'],并且该键是一个原型数组节点,因此在config/packages/open_telemetry.yaml中设置它会替换默认值,而不是追加。如果因任何原因覆盖它,必须重新列出这两项。消费者会在无限循环中运行,因此它的命令 span 永远不会结束,也不会导出,并会在 worker 的整个运行期间吞掉每个子 span。排除该命令并不会静默其中执行的工作:Messenger 中间件仍会为每条消息发出一个 CONSUMER span,而这些 span 会成为 Traceway 中的 Task。使用bin/console debug:config open_telemetry traces.console检查实际生效的配置。
- Symfony workers:切勿对
- Java / .NET / 其他任何语言:标准 OTel Agent 或 SDK 配合上述环境变量即可直接使用。
- 各框架的完整文档:https://docs.tracewayapp.com/client/otel
验证端点分组(首先执行)
使用不同 ID 多次请求一个参数化路由,并检查 Traceway Endpoints 页面:你必须只看到一行(GET /api/users/:id),而不是每个 ID 对应一行。如果看到的是原始 ID,说明插桩没有设置 http.route;继续操作前请先修复。最后的办法是在能识别已匹配路由模式的中间件中手动设置该值(该值必须以 / 开头,否则会被丢弃):
import { trace } from "@opentelemetry/api";
trace.getActiveSpan()?.setAttribute("http.route", matchedRoutePattern);
错误
抛出的错误必须记录为异常事件,才能显示在 Issues 中。自动插桩会处理未捕获的错误;对于已捕获并处理的错误:
import { trace, SpanStatusCode } from "@opentelemetry/api";
const span = trace.getActiveSpan();
span?.recordException(error);
span?.setStatus({ code: SpanStatusCode.ERROR, message: error.message });
(Go:span.RecordError(err, trace.WithStackTrace(true));生成 exception.stacktrace 属性的正是堆栈跟踪选项。)
发生异常时返回 HTTP 500。 抛出异常或发生 panic 的请求必须返回 500,并将 span 状态设为 ERROR。绝不能让它最终返回 200/2xx。常见的陷阱是 recover/panic 中间件报告了异常,却让响应写入器保留默认的 200:Traceway 随后会将端点事务记录为成功,因此 Endpoints 页面看起来一切正常,Issues 却不断增加;异常也会成为一个未关联的孤立事件,无法关联到失败请求(使用分布式追踪时,也无法关联到触发它的前端调用)。始终将“已记录异常”与“返回 500 并将 span 状态设为 ERROR”配套处理。真正的客户端错误(不是程序缺陷),例如验证失败 422、身份验证失败 401、未找到 404,应保留实际的 4xx 状态,并且一开始就不应记录为异常。
Go 中同样的陷阱更明显。otelgin 只有在 c.Next() 返回后才会记录状态,但 panic 展开期间永远不会返回,因此未恢复的 panic 会让端点行的状态显示为 0,尽管 Gin 自带的恢复机制已经向客户端返回了 500;同时,Issue 虽然有标题,却没有堆栈跟踪。在 otelgin.Middleware 之后注册恢复中间件,让它在服务 span 内运行:
// imports: fmt, net/http, go.opentelemetry.io/otel/codes, go.opentelemetry.io/otel/trace
func otelRecovery() gin.HandlerFunc {
return func(c *gin.Context) {
defer func() {
if r := recover(); r != nil {
err, ok := r.(error)
if !ok {
err = fmt.Errorf("%v", r)
}
span := trace.SpanFromContext(c.Request.Context())
span.RecordError(err, trace.WithStackTrace(true))
span.SetStatus(codes.Error, err.Error())
c.AbortWithStatus(http.StatusInternalServerError)
}
}()
c.Next()
}
}
router.Use(otelgin.Middleware(serviceName))
router.Use(otelRecovery())
日志
OTEL_LOGS_EXPORTER=otlp 只会接入导出器。应用自身的日志调用仍需要桥接,否则即使追踪和指标看起来完全正常,Logs 页面也会是空的。
- Node:添加
@opentelemetry/winston-transport,或 Pino、Bunyan 插桩,让每条记录都带上trace_id和span_id。 - Python、PHP、Java、.NET:
opentelemetry-instrument和语言 Agent 会自动桥接标准日志记录器。FastAPI 内置的遥测功能不会这样做:它只导出 FastAPI 自身的记录,因此,如果应用的logging输出需要发送到 Traceway,就应使用opentelemetry-instrument路径。 - Go:使用
otlploghttp.WithEndpointURL(...)构建sdklog.LoggerProvider,通过global.SetLoggerProvider(lp)注册,并调用defer lp.Shutdown(ctx),否则退出时会丢失最后一批日志。
从请求上下文发出日志(Gin 中的 c.Request.Context()、net/http 中的 r.Context()、Node 中的活动上下文)。从后台上下文发出的日志行不带 trace id,也无法从其所属的端点打开。Traceway 会读取 severity_text(或根据 severity_number 推导)、正文、service.name 以及 trace id 和 span id。
Go 使用 OpenTelemetry
Go 没有单独的 Traceway SDK。它已停止维护;Go 与其他所有后端一样,使用 opentelemetry-go 进行插桩:运行 go get go.opentelemetry.io/otel/sdk go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp,然后使用上方代码块中的相同环境变量。主机 CPU 和内存数据来自 Traceway OTel Agent(参见“部署和服务器指标”),而非应用本身。
步骤 5:后台任务(边界与命名)
如果“分析架构”发现了后台工作,请将其作为任务进行埋点。规则如下:
- 边界:一个任务 = 执行一次后台工作单元。 一次完整的 cron 作业运行就是一个任务。一条队列消息或一个作业就是一个任务。一次 CLI 命令调用就是一个任务。运行内部针对单个项目的工作(批次中的每封电子邮件、导入中的每一行)是任务跨度的子跨度,绝不能是单独的
CONSUMER跨度。 - 不要重复包装。 如果某个库的自动埋点已经生成了
CONSUMER跨度(Kafka、RabbitMQ、Symfony Messenger 消费者),再次包装会创建重复的任务条目。 - 命名:跨度名称就是任务名称和分组键。 使用稳定的标识符,例如
cleanup-expired-sessions或process-email-queue。绝不要在名称中包含作业 ID、时间戳或用户 ID;每个唯一名称都会成为单独的任务组。动态上下文(作业 ID、批次大小)应放在跨度属性中,这些属性会显示在任务详情页上,且不会影响分组。 - 类型必须是
CONSUMER。 使用默认SpanKind.INTERNAL的根跨度会被静默丢弃;这是“我的 cron 作业没有显示”最常见的原因。CLI 命令也可以使用带有console.command属性的根INTERNAL跨度(Laravel/Symfony 控制台埋点就是这样处理的)。
import { trace, SpanKind, SpanStatusCode } from "@opentelemetry/api";
const tracer = trace.getTracer("my-app");
async function runScheduledJob() {
await tracer.startActiveSpan(
"cleanup-expired-sessions",
{ kind: SpanKind.CONSUMER },
async (span) => {
try {
await doWork();
span.setStatus({ code: SpanStatusCode.OK });
} catch (error) {
const err = error instanceof Error ? error : new Error(String(error));
span.recordException(err);
span.setStatus({ code: SpanStatusCode.ERROR, message: err.message });
throw error;
} finally {
span.end();
}
}
);
}
(Go:tracer.Start(ctx, "cleanup-expired-sessions", trace.WithSpanKind(trace.SpanKindConsumer))。)
第 6 步:AI 追踪
如果“分析架构”发现了 AI/大模型依赖,请为模型调用添加监测。一个 Span 在包含至少一个 gen_ai.* 属性,且未匹配任何更早的分类规则时,才会成为 AI 追踪。实际操作中,这意味着:不要将 gen_ai.* 放在 HTTP 路由 Span 上(它会成为 Endpoint),也不要放在 CONSUMER Span 上(它们会成为任务);应为模型调用单独创建一个子 Span,并将其 SpanKind 设为 CLIENT 而非 INTERNAL,这样即使失败调用记录了 exception.* 属性,也仍会生成 AI 追踪。由于调用发生在请求或任务内部,该子 Span 会通过 trace ID 与其 Endpoint 或任务保持关联。
边界:每次模型调用对应一个 Span(一次供应商 API 请求 = 一行 AI 追踪)。多步骤 Agent 运行会产生多个共享稳定 trace.name 的 Span(命名规则与任务名称相同:不包含 ID 或时间戳)。对于流式响应,请在流结束时结束 Span。
对话式产品? 如果“分析架构”标记出聊天机器人、助手或工具调用 Agent,请改为遵循此技能目录中的 ai-agent.md,不要只查看下表。该文件介绍了 gen_ai.conversation.id(多轮对话分组)、user.id 应填写的值(稳定的最终客户 ID,绝不能是会话 ID)、工具调用负载格式以及子 Agent。如果缺少这些信息,“用户”选项卡会一直为空,而“对话”选项卡则会为每个请求生成一条一次性单轮对话,因为 Traceway 会回退使用 OTel trace ID。这比空白更糟糕:它看起来像是功能正常。
Traceway 会读取的属性(均为可选,按可用情况设置):
| 属性 | 含义 |
|---|---|
gen_ai.request.model / gen_ai.response.model | 请求的模型 / 实际提供服务的模型 |
gen_ai.system 或 gen_ai.provider.name | 供应商(openai、anthropic 等) |
gen_ai.operation.name | 操作(chat、embeddings 等) |
gen_ai.usage.input_tokens / .output_tokens / .total_tokens | Token 数量 |
gen_ai.usage.input_tokens.cached / gen_ai.usage.output_tokens.reasoning | 缓存 / 推理 Token 数量 |
gen_ai.usage.input_cost / .output_cost / .total_cost | 成本,需自行计算价格 |
trace.name | Agent/工作流分组名称 |
gen_ai.conversation.id | 多轮对话分组(依次回退至 session.id,再到 OTel trace ID) |
user.id | 最终用户归属:稳定的客户 ID(账户 ID、租户 ID 或电子邮件),同一用户的所有对话中保持一致 |
gen_ai.response.finish_reason | 生成停止的原因 |
gen_ai.prompt / gen_ai.completion | 对话内容,显示在追踪详情页中(如果内容不得离开应用,则跳过) |
如果不存在 gen_ai.prompt / gen_ai.completion,也会从 trace.input/trace.output(或 span.input/span.output)读取对话内容;如果缺少 total_tokens/total_cost,则会根据输入值和输出值计算。Token 数量必须是整数属性,成本必须是双精度或整数属性。以字符串形式发送的数字会被静默丢弃并存储为 0,导出时不会报错。
import { SpanKind } from "@opentelemetry/api";
return tracer.startActiveSpan("chat-completion", { kind: SpanKind.CLIENT }, async (span) => {
const response = await openai.chat.completions.create({ model: "gpt-4o", messages });
span.setAttributes({
"gen_ai.system": "openai",
"gen_ai.request.model": "gpt-4o",
"gen_ai.usage.input_tokens": response.usage?.prompt_tokens ?? 0,
"gen_ai.usage.output_tokens": response.usage?.completion_tokens ?? 0,
"trace.name": "support-agent",
});
span.end();
return response;
});
OpenRouter 用户的零代码方案:在 OpenRouter Settings -> Observability 中,添加一个指向 https://<instance>/api/otel/v1/traces 的 OpenTelemetry Collector 目标,并设置请求头 {"Authorization": "Bearer <project-token>"}。文档:https://docs.tracewayapp.com/client/openrouter
第 7 步:前端和移动端
前端和移动端项目不使用 OTel;它们使用 Traceway SDK,通过连接字符串 <project-token>@https://<instance>/api/report 向 /api/report 上报数据。
浏览器(React / Vue / Svelte / jQuery / 纯 JS),需要包含以下三个部分:
- SDK:执行
npm install @tracewayapp/react(纯 JS 可使用vue、svelte、jquery或frontend),并使用连接字符串进行初始化(React:将应用封装在<TracewayProvider connectionString="...">中)。它会捕获错误、Web Vitals 和会话回放。 - 打包器插件:执行
npm install -D @tracewayapp/bundler-plugin,然后在打包器配置中添加来自@tracewayapp/bundler-plugin/vite的tracewayDebugIds()(或来自/rollup的版本,或来自/webpack的TracewayDebugIdsWebpackPlugin),并启用源映射(build.sourcemap: true/devtool: "source-map")。 - 源映射上传:执行
npm install -D @tracewayapp/sourcemap-upload,然后在构建后步骤或 CI 步骤中运行traceway-sourcemaps --url <instance> --token <source-map-upload-token> --directory ./dist(环境变量:TRACEWAY_URL、TRACEWAY_SOURCEMAP_TOKEN)。上传令牌来自浏览器项目,是 CI 机密,绝不能提交。
关于各框架的初始化代码(纯 JS、React、Vue、Svelte/SvelteKit、jQuery)、共享 SDK 选项、错误过滤、自定义属性、分布式追踪,以及完整的调试 ID + 源映射流水线,请阅读此技能目录中的 frontend-js.md。在线文档:https://docs.tracewayapp.com/client/react(或 vue、svelte、jquery、js-sdk)。
全栈 JS(Next.js、SvelteKit、Remix):仅当“分析架构”确认该框架的服务端在生产环境中实际提供应用时才适用。仅前端部署只需使用上述浏览器端组件;单独的后端则遵循“后端 OTel 设置”。如果确实是全栈应用,则在已确认的两个项目下集成前后端:服务端按照“后端 OTel 设置”配置,并使用后端项目的令牌;浏览器端按照上述三个步骤配置,并使用前端项目的令牌。对于 Next.js,具体应使用上述“Next.js 服务端异常”中的 instrumentation.ts 框架钩子;绝不能对其应用通用 Node 预加载。
对于 Next.js 浏览器端构建,@tracewayapp/bundler-plugin 目前提供 webpack 入口点,而非 Turbopack 入口点。设置 productionBrowserSourceMaps: true,并且仅在 !isServer && !dev 时添加 TracewayDebugIdsWebpackPlugin(该插件仅用于生产环境,不能替代 Next.js 的开发源映射模式);同时让生产构建运行 next build --webpack(Next.js 16 默认使用 Turbopack,否则会忽略此插件)。构建后,确认客户端 .js 文件包含 //# debugId=,其对应的 .js.map 文件包含 debugId,然后使用 React 项目的源映射上传令牌上传 .next/static/chunks。
移动端始终使用平台 SDK,绝不使用 OTel:
- Flutter:执行
flutter pub add traceway,然后调用Traceway.run(connectionString: '<token>@https://<instance>/api/report', child: MyApp())。接着检查发布构建是否经过混淆(--obfuscate --split-debug-info):如果经过混淆,生产环境中的崩溃堆栈跟踪会以混淆形式上传,在上传构建的.symbols文件之前将无法读取,因此请使用移动项目的上传令牌并配置符号上传。有关选项、平台权限、导航器观察器、屏幕录制、隐私遮罩、混淆检查和符号上传,以及 Flutter Web 的注意事项,请阅读此技能目录中的flutter.md。文档:https://docs.tracewayapp.com/client/flutter - React Native:执行
npm install @tracewayapp/react-native,并用TracewayProvider包装应用。文档:https://docs.tracewayapp.com/client/react-native - Android(原生 Kotlin/Java):从 Maven Central 添加
implementation("com.tracewayapp:traceway:1.0.1"),并在Application.onCreate()中调用Traceway.init(application = this, connectionString = "<token>@https://<instance>/api/report", options = TracewayOptions(version = "1.0.0"))(在清单中注册Application类;无需添加权限条目,AAR 自带的清单会声明INTERNET和ACCESS_NETWORK_STATE,清单合并器会将它们合并进去)。它会捕获所有线程上未捕获的 Java/Kotlin 异常,以及手动调用的Traceway.captureException(...);仅报告错误和崩溃,不提供会话回放。发布构建会运行 R8,因此生产环境中的崩溃会使用重命名后的类名和重写后的行号上传,在上传构建的mapping.txt之前将无法读取:应用com.tracewayapp.symbolsGradle 插件(version "1.0.1",从mavenCentral()解析),该插件会注入BuildConfig.TRACEWAY_PROGUARD_UUID,并使用移动项目的上传令牌上传映射文件;将该 UUID 传入TracewayOptions(proguardUuid = ...),以便每次崩溃都能匹配相应的映射文件。有关初始化代码、选项、清单和完整的 Gradle 插件设置,请阅读此技能目录中的android.md。文档:https://docs.tracewayapp.com/client/android - iOS / Swift(原生 SwiftUI 或 UIKit):通过 Swift Package Manager(
https://github.com/tracewayapp/traceway-ios.git)添加 Traceway iOS SDK,并尽早调用Traceway.start(connectionString: "<token>@https://<instance>/api/report", options: TracewayOptions(version: "1.0.0"))。它会捕获未捕获的NSException和致命信号(严重崩溃会在下次启动时上传),以及手动调用的Traceway.capture(...);仅报告错误和崩溃,不提供会话回放。发布崩溃会以纯地址形式上传,直到构建的 dSYM 文件上传后才能读取,因此请使用移动项目的上传令牌配置 dSYM 上传。有关初始化代码、选项、调试器注意事项和 dSYM 上传,请阅读此技能目录中的ios.md。如果应用不是 Swift 应用(仅使用 Objective-C、使用不含 Traceway 移动 SDK 的跨平台技术栈,或团队统一采用 OpenTelemetry),则没有原生 SDK:请使用 Honeycomb 等 OTel 发行版,将其导出器指向<instance>/api/otel,并设置Authorization: Bearer <project-token>标头,方式与后端完全相同(参见“后端 OTel 设置”)。非 Swift 路径也见ios.md。
第 8 步:部署和服务端指标
使用在“提出并确认项目地图”中确认的部署方式和主机指标选项。如果尚未确定,请先询问,再更改部署配置:
- 此项目如何部署? 在 VM 上使用 Docker/直接部署到 VM 或裸机/Kubernetes/无服务器或 PaaS。
- 是否要在 Traceway 中跟踪服务端(主机)指标? 运行应用的机器的 CPU、内存、磁盘、文件系统和网络指标。
| 部署方式 | 是否需要主机指标 | 操作 |
|---|---|---|
| 在 VM 上使用 Docker,或直接部署到 VM/主机 | 是 | 在主机上安装 Traceway OTel Agent(如下)。Docker 部署时默认采用此方式;Agent 安装在主机上,而不是容器内。 |
| Kubernetes | 任意 | 不使用 Agent,因为它是主机服务。部署此 skill 目录中 kubernetes.md 里的两个采集器工作负载:用于节点指标、Pod 指标和容器日志的 DaemonSet,以及用于集群状态和事件的单副本 Deployment。进程内应用指标仍通过“后端 OTel 设置”中的 OTLP 指标导出器传输。 |
| 无服务器/PaaS | 任意 | 无主机可供安装;跳过。 |
| 任何方式 | 否 | 跳过。 |
在 Kubernetes 上部署时,请停在此处并阅读此 skill 目录中的 kubernetes.md。决定配置能否生效的四个要点(每个节点的 service.name、默认禁用的三个 *.utilization 指标、k8s.cluster.name 和 root_path: /hostfs)都很容易被忽略,任何一点配置错误都会导致配置看似合理,却无法报告有用数据。
此 Agent 是一个小型预配置 OTel Collector,每 60 秒采集一次主机指标。它必须使用与 API、worker、task 和 AI trace 相同的后端项目令牌。在主机上安装(Linux 使用 systemd/macOS 使用 launchd;Windows 也提供 PowerShell 安装程序):
curl -fsSL https://install.tracewayapp.com/install.sh | \
TRACEWAY_TOKEN=<project-token> \
TRACEWAY_ENDPOINT=https://<instance>/api/otel \
TRACEWAY_SERVICE_NAME=<host-label, e.g. api-prod-eu-1> \
bash
TRACEWAY_ENDPOINT必须以/api/otel结尾,且自托管实例必须设置;仅 Traceway Cloud 可省略。- 可选:
TRACEWAY_LOG_PATHS(以逗号分隔的 glob 模式,用于跟踪日志)、TRACEWAY_PROCESS_NAMES(进程级指标)。 - 重新运行安装程序会就地升级,因此可以放心将此命令保留在配置脚本中。
- 如果代码库中有主机配置或部署脚本(cloud-init、Ansible、Terraform
user_data、deploy.sh),请将命令添加到其中,并通过密钥引用令牌。否则,将填好参数的单行命令交给运维人员;不要修改代码库。
指标会在约 60 秒内以其 hostmetrics 名称到达(system.cpu.utilization、system.memory.usage 等)。主机通过 server_name 标签标识,该标签来自 TRACEWAY_SERVICE_NAME;host.name、host.id、host.arch、os.type 和云区域等资源元数据也会保留,但不能替代此规范标识。每台主机都应使用不同的 TRACEWAY_SERVICE_NAME,否则无法区分主机。组织概览会直接识别这些 hostmetrics,但旧版 Go SDK 的 CPU/内存小组件仍会读取其确切的 SDK 指标名称。安装 OTelemetry Server Agent 仪表板模板即可查看完整指标集:按 Cmd K 打开命令面板,搜索该模板,系统会安装一个已配置好 system.cpu.utilization、system.memory.*、system.filesystem.*、system.disk.* 和 system.network.* 的仪表板。模板未涵盖的内容可通过自定义小组件补充。Agent 仓库:https://github.com/tracewayapp/traceway-otel-agent
第 9 步:验证
下方列出的每个仪表板页面都位于 <instance>/<page>:/endpoints、/issues、/tasks、/ai-traces、/logs、/dashboards。查看任何页面前,请将时间选择器设为最近 15 分钟。分别验证每个项目,并以项目到组件的汇总结束。
CLI 应用计划后,本地环境文件中会包含真实值,因此现在就运行本地验证,不要推迟。只有在用户完成网站的「后续步骤」面板后,才能验证部署环境;请再次说明该面板中必须存在的变量,验证才能通过。
- 后端项目
- 运行
curl <app>/api/users/1,然后运行/2,再运行/3,并打开/endpoints。应当只有一行GET /api/users/:id,且状态码非零。如果有三行,说明未设置http.route。在检查其他内容之前先修复这一问题。 - 有两种结果看起来像是异常,实际上并非如此:请求的 URL 不匹配任何路由时,会显示为
UNMATCHED,而不是你输入的 URL;如果项目启用了“丢弃正常健康检查”功能,成功的/health请求会在摄取时被丢弃,因此绝不要用健康检查路由进行冒烟测试。 - 触发一个已处理或测试用异常,然后打开
/issues。异常会连同其端点或任务上下文一起列出,对应请求的端点行会显示状态码 500。 - 打开一个端点行:数据库查询和对外 HTTP 调用会显示为子 Span。
- 每个后台作业都运行一次,然后打开
/tasks。每个作业应有一行,名称稳定且不包含 ID 或时间戳。 - 如果已接入日志,在请求内分别输出一条 INFO 和一条 ERROR 日志,然后打开
/logs。两条日志的 trace id 都应与该端点相同。 - 如果已应用“AI Traces”,发起一次模型调用,然后打开
/ai-traces。调用会显示其模型、令牌数,以及可用时的费用。 - 打开
/dashboards,确认应用和运行时指标已到达。如果安装了 OTel Agent,主机指标应在约 60 秒内到达同一个后端项目,每台主机都带有各自的server_name标签。 - 如果已设置主机或 Kubernetes 指标,打开
/organization。每台主机和每个节点都应作为单独的实例行显示,并包含 CPU、内存和磁盘指标。Kubernetes 节点会按集群分组。如果原本预期有多台机器,却只显示一行,说明每台机器的service.name不唯一;如果环形图为空,说明未启用*.utilization指标。
- 运行
- 浏览器项目
- 触发一个浏览器测试错误,并确认它显示在此项目中,而不是后端项目中。
- 上传匹配的源映射和构建包后,确认堆栈能够解析到原始源文件和行号。
- 如果已配置分布式追踪,确认浏览器问题或请求会关联到其后端 trace。
- 每个移动项目
- 触发平台的安全测试错误或崩溃流程,并确认它只显示在匹配的移动项目中。
- 对于混淆过的 Flutter、经过压缩的 Android 或 Release iOS 构建,确认匹配的符号文件、R8 映射文件或 dSYM 能够解析生产环境堆栈。
- 凭证与拓扑审计
- 确认浏览器和移动端集成未使用后端令牌,且 OTLP 导出器未使用浏览器项目的令牌(步骤 4 中的规则 3)。
- 确认 API、工作进程、调度程序、AI 调用和主机指标均使用后端项目,除非用户批准了隔离边界。
- 报告因本地、部署或 CI 变量尚未设置而无法执行的任何验证。
- 如果已安装
tracewayCLI,运行traceway login,使用traceway projects use <project-id>选择后端项目(或在每次调用时传入--project <project-id>),然后:
Tasks 和 AI traces 没有traceway endpoints list --since 15m traceway exceptions list --since 15m traceway logs query --since 15m traceway metrics query --name system.cpu.utilization --since 15mlist子命令,只有show <id>,因此请在仪表板的/tasks和/ai-traces上检查这两项。 - 让用户亲自试用。 完成自己的验证后,最后准确告知用户如何亲自查看连接是否正常:针对他们的应用执行两到三个可复制粘贴的操作,并为每个操作说明它会显示在哪个 Traceway 页面。例如:多次请求一个实际的参数化端点(
curl <their-app>/api/users/1、/2、/3会在几秒内合并为/endpoints上的一行);触发应用的错误路径或主动抛出一个测试异常(会显示在/issues上,并关联到失败的请求);再运行或等待一个后台作业(/tasks)。使用他们实际的路由和命令,提醒他们将仪表板的时间选择器设为过去 15 分钟,并说明审批已将他们带到正确项目的仪表板。