配置投递
发布 CLI 时,将其指向你的接收 URL,将其内置到二进制文件中,并允许按环境覆盖:
withTelemetry(command, {
name: TOOL,
version: VERSION,
endpoint: 'https://telemetry.my-tool.dev/api/telemetry/ingest',
})
| 覆盖项 | 作用 |
|---|---|
EVLOG_TELEMETRY_ENDPOINT | 替换内置的 URL(预发布、自托管) |
EVLOG_TELEMETRY=0 / DO_NOT_TRACK=1 | 不记录,不发送 |
EVLOG_TELEMETRY_DEBUG=1 | 在不发送的情况下将负载打印到 stderr |
内置的 HTTP drain 会使用 POST 请求发送 Content-Type: application/json,请求体为 { events: RunEvent[] },并设置 400ms 超时。成功时(res.ok),已投递的键会从 outbox 中移除。flush() 最长 500ms,且绝不会抛出异常。
使用 citty 进行设置
使用 withTelemetry() 包裹根命令,通常位于调用 runMain() 的 src/index.ts 文件中。子命令可以位于同一文件中,也可以位于 src/commands/*.ts 中;只有入口点需要使用此包装器。
import { defineCommand, runMain } from 'citty'
import { withTelemetry, defineTelemetryCommands } from '@evlog/telemetry'
import { doctorCommand } from './commands/doctor'
import { syncCommand } from './commands/sync'
const TOOL = 'my-tool'
const VERSION = '1.0.0'
export const main = withTelemetry(
defineCommand({
meta: { name: 'my-tool', description: '…', version: VERSION },
subCommands: {
doctor: doctorCommand,
sync: syncCommand,
telemetry: defineTelemetryCommands({ name: TOOL }),
},
}),
{
name: TOOL,
version: VERSION,
endpoint: 'https://telemetry.my-tool.dev/api/telemetry/ingest',
collect: {
flags: { format: ['json', 'csv'] },
fields: { framework: ['nuxt', 'next'] },
},
},
)
runMain(main)
会自动记录什么
withTelemetry 会遍历你的 citty 树。每个 run 处理器都会生成一个事件,无需为每个命令编写遥测样板代码。
| 调用方式 | event.command | flags 说明 |
|---|---|---|
my-tool doctor | doctor | {} — 未传入任何内容,默认值也不属于可选项 |
my-tool doctor --json | doctor | { json: true } |
my-tool sync --dry-run --output ./out | sync | { dryRun: true, output: "<set>" } — 路径本身永远不会发送 |
my-tool sync --format json | sync | 只有在 collect.flags 中列入允许项时才会记录 { format: "json" } |
my-tool telemetry status | telemetry status | 嵌套子命令之间用空格连接 |
当根命令只是委托给 subCommands 时,根 meta.name 不会作为前缀添加。
为一次运行补充信息
可以在命令处理器内部的任意位置调用 telemetry.set(),也可以在同一异步调用栈中运行的辅助函数中调用(运行上下文通过 AsyncLocalStorage 保留)。
import { telemetry } from '@evlog/telemetry'
import { existsSync } from 'node:fs'
import { resolveConfigPath } from '../lib/config'
async function runHealthChecks() {
let checksFailed = 0
let checksWarn = 0
const configPath = resolveConfigPath()
if (!existsSync(configPath)) {
checksFailed++
}
try {
await fetch('https://registry.npmjs.org/my-tool')
} catch {
checksWarn++
}
// 计数器会进入本次运行的宽事件中的 event.custom
telemetry.set({ checksFailed, checksWarn })
return checksFailed === 0
}
export const doctorCommand = {
meta: { name: 'doctor', description: '检查环境' },
args: {
json: { type: 'boolean', alias: 'j' },
},
async run({ args }: { args: { json?: boolean } }) {
const ok = await runHealthChecks()
if (!ok) {
throw Object.assign(new Error('Health checks failed'), { code: 'DOCTOR_FAILED' })
}
if (!args.json) process.stdout.write('ok\n')
},
}
抛出带有 code 属性的错误时,会自动记录 outcome: "error" 和 errorCode:
throw Object.assign(new Error('Config missing'), { code: 'CONFIG_NOT_FOUND' })
下面是一个用于对比的最简 sync 处理器。标志会自动捕获,你只需使用 set() 记录业务计数器:
import { telemetry } from '@evlog/telemetry'
export const syncCommand = {
meta: { name: 'sync', description: '拉取远程状态' },
args: {
dryRun: { type: 'boolean' },
output: { type: 'string', description: '输出路径' },
},
async run() {
const itemsSynced = await pullRemoteState()
telemetry.set({ itemsSynced })
},
}
无 citty 的设置
对于脚本、迁移器或自定义 CLI,请使用 createTelemetry(),并用 t.run() 包裹每次逻辑运行:
import { createTelemetry, telemetry } from '@evlog/telemetry'
const t = createTelemetry({ name: 'my-migrator', version: '2.0.0' })
await t.run('migrate', async () => {
const rows = await migrateBatch()
telemetry.set({ rowsMigrated: rows.length, batchSize: 500 })
})
await t.flush() // 可选 — 也会在每次 t.run() 结束时执行
GitHub Actions:在同一文件中将 createTelemetry 替换为 createGitHubActionsTelemetry(),它仅从 GITHUB_ACTION / GITHUB_EVENT_NAME 中读取并将 ghaAction 和 ghaEvent 添加到 custom,不会读取仓库内容。
import { createGitHubActionsTelemetry, telemetry } from '@evlog/telemetry'
const t = createGitHubActionsTelemetry({ name: 'my-action', version: '1.0.0' })
await t.run('report', async () => {
const artifacts = await collectArtifacts()
telemetry.set({ artifactCount: artifacts.length })
})
调试
EVLOG_TELEMETRY_DEBUG=1 my-tool doctor
EVLOG_TELEMETRY=0 my-tool sync
调试模式会将原本将要发送的载荷打印到 stderr。除非已配置端点且投递成功,否则不会发送任何内容。