@earendil-works/pi-遥测
供应商中性遥测合同和Pi包的打字系统.
本套件提供:
- 一个明确的、基于召回的
TelemetryContext编号 :TelemetrySpan合同; - a 共用资源
NOOP_TELEMETRY_CONTEXT· ; - 参考文献
InMemoryTelemetryContext执行; - 带有推断出类型Script的序列式计划定义;
- 没有导出者、全局电流平流状态或依赖遥测后端.
应用程序可以使用内存参考,或者为OpenTeleometter,哨兵,日志或另一个后端提供适配器. Pi包明确通过遥测上下文,并分别定义了它们的域计划.
目录
安装
npm install @earendil-works/pi-telemetry
# Bun alternative
bun add @earendil-works/pi-telemetry
遥测概念
遥测描述一个程序在运行时所做的. 使用跨度、属性、事件、状态和明确上下文的软件包模型:
| 概念 | 普通语的含义 |
|---|---|
| 宽度 | 一个操作的定时记录,例如加载账户或提出AI请求. 它开始于工作前,结束于工作完成后. |
| 父母和子女的跨度 | 操作可以包含较小的操作. 请求周期可能包含缓存查询和数据库查询。 他们一起组成一棵树,显示时间的花在什么地方. |
| 属性 | 某个跨度上附有一个点名的事实,例如: provider: "openai", (中文(简体) ), cache.hit: true,或 item_count: 12属性描述操作及其结果. |
| 活动 | 在一个期间某个点的命名事件,例如 retry.scheduled 或者说 cache.lookup. 活动没有持续时间,可能具有自己的属性. |
| 状态 | 手术结果: ok 或者说 error。错误状态可能包括错误名称和消息. |
| A. 背景情况 | 指向横跨树上新工作所在的手柄。 从上下文开始的一段时期使它成为上下文的孩子. |
例如,加载一个账户可以产生这种遥测:
example.account.load span
├─ attributes: account.id=123, found=true facts about the span
├─ event: example.cache.lookup occurrence during the span
│ └─ attribute: cache.hit=false fact about the event
└─ status: ok final outcome
一个跨度是诊断数据,而不是商业状态. 记录它不得改变账户载荷的运行、成功、失败或持续。 一个适配器将这些通用概念翻译为OpenTeleometry,哨兵,logs或另一个后端使用的相应概念.
核心内容 API
A 类 TelemetryContext 开始一个跨度 围绕召回。 回调收到一个 TelemetrySpan,这也是孩子跨度的明确的父母背景.
import {
NOOP_TELEMETRY_CONTEXT,
type TelemetryContext,
} from '@earendil-works/pi-telemetry';
async function loadAccount(
accountId: string,
telemetryContext: TelemetryContext = NOOP_TELEMETRY_CONTEXT,
) {
return telemetryContext.startSpan(
{
name: 'example.account.load',
attributes: { 'example.account.id': accountId },
},
async (span) => {
const account = await readAccount(accountId);
span.setAttributes({ 'example.account.found': account !== undefined });
return account;
},
);
}
将回调跨度传递给较低级别的工作,以创建清晰的筑巢:
return telemetryContext.startSpan({ name: 'example.parent' }, async (parentSpan) => {
return parentSpan.startSpan({ name: 'example.child' }, async (childSpan) => {
childSpan.addEvent('example.cache.lookup', { 'example.cache.hit': true });
return performWork();
});
});
没有公众 end() 方法. startSpan() 拥有和解并保持跨度,直到召回值或承诺达成为止。 对于以正常返回值为代表的预期失败,明确规定状态:
return telemetryContext.startSpan({ name: 'example.save' }, async (span) => {
const result = await save();
if (!result.ok) {
span.setStatus({
status: 'error',
error: { name: 'SaveError', message: result.reason },
});
}
return result;
});
适配器合同
适配器执行器 TelemetryContext 将通用API连接到它的后端。 它必须:
- 创建一个孩子的跨度,并同步引用回调,准确一次;
- 保存回调的返回值和拒绝值,在同步抛出后返回一个以同样值拒绝的承诺;
- 保持土生土长,直到返回的诺言达成为止;
- 将正常补全作为
ok并投掷/拒绝为错误,除非设定了明确的状态; - 重复一个
setStatus()要求最后写成; - 合并
setAttributes(),后定义的值取代先前的值undefined忽略; - 使录制方法同步、被动和非抛出;
- 无视定居后发出的呼吁;
- 忽略一个失败的录制调用 解析 , 压制后端失败, 并且仍然执行商业回调一次 .
适配器可以在内部激活后端-内在环境上下文,用于自动仪表,但pi代码总是通过 TelemetryContext 参数。 导出器缓冲,冲出,采样,后端ID,后端特定上下文对象属于适配器. 使用 适配器套装 检查这些可见的语义.
无上下文
使用 NOOP_TELEMETRY_CONTEXT 当遥测为可选时:
import { NOOP_TELEMETRY_CONTEXT } from '@earendil-works/pi-telemetry';
const result = await NOOP_TELEMETRY_CONTEXT.startSpan(
{ name: 'example.operation' },
() => runOperation(),
);
无上下文 :
- 同步引用回调;
- 保存返回的值和同步拒绝,并将同步丢入以相同值拒绝的承诺;
- 使用一个共享的冷冻惰性跨度,包括嵌入式跨度;
- 不检查或保留姓名、属性、事件或状态.
记忆参考适应器
InMemoryTelemetryContext 是后端中性引用执行。 用于测试、本地诊断和应用,如果有意想要在没有出口者的情况下进行本地过程捕获:
import { InMemoryTelemetryContext } from '@earendil-works/pi-telemetry';
const telemetry = new InMemoryTelemetryContext();
await telemetry.startSpan(
{ name: 'example.operation', attributes: { input: 'demo' } },
async (span) => {
span.addEvent('example.started');
span.setAttributes({ output_count: 3 });
},
);
console.log(telemetry.getSpans());
getSpans() 返回跨度启动顺序的已解开快照。 每个 RecordedTelemetrySpan 包含一个决定性的数字 ID, 父ID, 合并属性, 命令事件, 最终状态, 和解状态, 以及决定性的结束序列 。 它没有记录时间戳.
适配器可以正常使用 TelemetryContext,但存储是无约束的,并进行本地处理。 创建一个新实例来隔离测试或记录范围,除非呼叫者的数据政策允许,否则不捕捉敏感属性.
适配器
@earendil-works/pi-telemetry/testing 输出一个按组装型号排列的、独立运行的套装。 固定提供了新的上下文,并将其后端的完成跨度转换为正常 RecordedTelemetrySpan 快照 :
import {
createTelemetryAdapterConformance,
type TelemetryAdapterFixture,
} from '@earendil-works/pi-telemetry/testing';
import { describe, it } from 'vitest';
const conformance = createTelemetryAdapterConformance(async () => {
const adapter = createMyTelemetryAdapter();
return {
context: adapter.context,
getSpans: async () => adapter.normalizedSpans(),
async [Symbol.asyncDispose]() {
await adapter.close();
},
} satisfies TelemetryAdapterFixture;
});
for (const group of new Set(conformance.map((testCase) => testCase.group))) {
describe(group, () => {
for (const testCase of conformance.filter((candidate) => candidate.group === group)) {
it(testCase.name, () => testCase.run());
}
});
}
套房检查同步的单一收录,结果和拒绝身份,自动和明确状态,属性合并,事件顺序,定居后惰性呼叫,筑巢并同时进行亲和,并抑制无法读取的遥测有效载荷故障. getSpans() 可能会在返回前冲出同步导出者。 测试子路径使用Node的断言 API;根遥测包保持运行时中立.
已键入Schemas
低等跨度 API 有意接受开放名称和属性包,因此适配器仍然通用. 域包可以定义封闭的,可序列化的策略并推断出它们中的确切类型TypeScript.
import {
createTypedSpanStarter,
defineTelemetrySchema,
} from '@earendil-works/pi-telemetry';
export const EXAMPLE_TELEMETRY_SCHEMA = defineTelemetrySchema({
version: 1,
spans: {
'example.read': {
description: 'Read one resource',
parents: { kind: 'any' },
startAttributes: {
'example.resource': {
type: 'string',
required: true,
values: ['account', 'project'],
description: 'Resource kind',
},
},
endAttributes: {
'example.item_count': {
type: 'number',
description: 'Number of returned items',
},
},
events: {
'example.cache': {
description: 'Cache lookup result',
attributes: {
'example.cache.hit': {
type: 'boolean',
required: true,
description: 'Whether the cache contained the resource',
},
},
},
},
status: {
default: 'ok',
errorWhen: 'The read throws or returns an error result',
},
},
},
} as const);
const startSpan = createTypedSpanStarter(
telemetryContext,
[EXAMPLE_TELEMETRY_SCHEMA],
);
起步器每个跨度暴露出一个超载,并在编译时检查名称和属性. 在呼叫前必须缩小联盟值名称,保留每个运行时名称与其属性计划之间的关系. 它的回调接收到一个孩子的启动器 通过相同的计划, 已经捆绑在回调范围:
await startSpan(
'example.read',
{ 'example.resource': 'account' },
async (span, startChildSpan) => {
span.addEvent('example.cache', { 'example.cache.hit': true });
const accounts = await readAccounts();
span.setAttributes({ 'example.item_count': accounts.length });
await startChildSpan(
'example.read',
{ 'example.resource': 'project' },
async (childSpan) => {
const projects = await readProjects();
childSpan.setAttributes({ 'example.item_count': projects.length });
},
);
return accounts;
},
);
开始和完成属性
startAttributes 财务报告和审定财务报表 endAttributes 描述一个属性通常已知时,而不是单独的运行时存储 :
| Schema 字段 | 如何记录数值 | 需求 |
|---|---|---|
startAttributes | 在输入的启动器中通过 attributes 创建时的参数 | 每个定义都明确规定 required: true 或者说 false |
endAttributes | 后通过"计划范围"填充 setAttributes() 方法 | 总是可选 |
两套都在同一后端间距上成为了普通属性. 没有单独的终端属性有效载荷或终端回调。 在上述例子中, example.resource 何时知道 example.read 启动时 example.item_count 只在 readAccounts() 返回 :
await startSpan(
'example.read',
{ 'example.resource': 'account' }, // required start attribute
async (span) => {
const accounts = await readAccounts();
span.setAttributes({
'example.item_count': accounts.length, // optional completion attribute
});
return accounts;
},
); // resolving the callback settles the span
“结束”是指完成浓缩:在召回有效时,可以在任何时间设定最终属性,在无法使用时可以省略。 通话 setAttributes() 0倍有效。 这关系到早期的失败,取消,以及可能不存在于每个路径的特定提供者的数据.
重复 setAttributes() 调用合并到同一属性包。 后来定义的值取代了同一密钥的较早值,同时 undefined 被忽略。 计程镜法只接受当前跨度所声明的结束属性.
属性不会结束长度 。 返回、解决、投掷或拒绝回调控制结算; startSpan() 进行实际的终端操作。 在和解后拨打的适配电话是无效的.
启动器可以编写多个独立版本的策略:
import { AGENT_TELEMETRY_SCHEMAS } from '@earendil-works/pi-agent-core';
const startAgentSpan = createTypedSpanStarter(
telemetryContext,
AGENT_TELEMETRY_SCHEMAS,
);
Inline schema 阵列自动保留其tuple类型. 单独宣布的数组应使用 as const。整个阵列的字面上重复名称在编译时被拒绝;计划不合并、检查,也不在运行时保留.
Schema-衍生类型拒绝缺少所需的属性,未知的密钥,无效的闭合集值,未宣布的事件,以及空子计划中的属性. 尾端属性总是可选的浓缩;类型系统不需要 setAttributes() 被调用.
defineTelemetrySchema() 是输入的身份函数。 它返回了普通的JSON-序列相上的数据,并且不执行运行时验证或执行父规则.
Schema 元数据
支持的属性类型有:
string, (中文(简体) ),number,以及boolean· ;string[], (中文(简体) ),number[],以及boolean[]。 。 。 .
属性定义支持 :
values: 已关闭的平面值集;elementValues: 阵列元素的封闭集;examples: 文献实例;sensitive需要特殊处理的数据标记;cardinality: 需要记录low或者说high至高无上.
开始和事件属性声明 required。结尾属性不;见 开始和完成属性。 。 。 .
父元数据是描述性计划数据 :
{ kind: 'any' }: 根或任何调用器;{ kind: 'root_or_external' }: 根或计程器以外的调用器;{ kind: 'spans', spans: [...] }:只有所列计划跨度.
适配器不需要理解计划对象. 仪器辅助和测试人员利用它们保持所排放的名称和属性的一致性.
Pi 软件包集成
包的所有权被故意分割:
@earendil-works/pi-telemetry拥有供应商中立合同、不公开和模拟参考背景、计划公用事业和适配器兼容套件;@earendil-works/pi-ai接受和宣传telemetryContext在提供者请求选项中,但没有遥测计划;@earendil-works/pi-agent-core拥有并输出 Pi AI 请求和驾驭 schemas, 它们的组合只读 schema tuple, 和打出跨度帮助器.
import {
AGENT_TELEMETRY_SCHEMAS,
AI_TELEMETRY_SCHEMA,
HARNESS_TELEMETRY_SCHEMA,
startAiSpan,
startHarnessSpan,
} from '@earendil-works/pi-agent-core';
皮子计划是用皮子的 pi.ai.*, (中文(简体) ), pi.harness.*,以及 pi.session.* 名称。 适配器可以将其翻译为后端常规而无需更改所排放的pi词汇.
安全和可移动性
遥测是过程局部诊断,不是持久应用状态. 不坚持 TelemetryContext, (中文(简体) ), TelemetrySpan,或记录、信件、快照或被推迟的手柄中的后端-内存跟踪对象.
属性值被故意限制在原始的分级和数组. 域仪器应避免提示、补全、工具参数或输出、文件内容、供应商有效载荷、信头、证书和自由形式的出错细节,除非其计划和数据政策明确允许.
软件包不使用 AsyncLocalStorage 或其它运行时间特定环境上下文 API。 它适合Node.js, Bun,浏览器,以及工人;后端适配器仍然负责自己的运行时间相容性.
API 参考
核心类型和价值
| 导出 | 目的 |
|---|---|
TelemetryContext | 开始召回管理的孩子跨度 |
TelemetrySpan | 记录属性、事件和地位;也作为儿童背景 |
SpanOptions | 扩展名和可选起始属性 |
SpanAttributes 编号 : AttributeValue | 打开适配器级属性包和支持的值 |
SpanStatus | 明确 ok 或者说 error 状态 |
NOOP_TELEMETRY_CONTEXT | 共有的对残疾遥测的被动语境 |
InMemoryTelemetryContext | 具有确定性过程的参考适配器-本地记录 |
RecordedTelemetrySpan | 正常捕获的跨度快照 |
RecordedTelemetryEvent | 正常捕获的事件快照 |
方案定义和推论
| 导出 | 目的 |
|---|---|
defineTelemetrySchema() | 可序列化计划数据的类型化身份帮助器 |
createTypedSpanStarter() | 将父上下文绑入一个或多个术语表 |
TypedSpanStarter | 精确的启动类型, 递归带子的回调 |
TelemetrySchemaDefinition | 顶级方案形状 |
TelemetrySpanDefinition | Span 元数据、 父数据、 属性、 事件和状态规则 |
TelemetryAttributeType | 支持的scalar 和数组类型名称 |
TelemetryAttributeMetadata | 描述、敏感性和主要元数据 |
TelemetryAttributeDefinition | 属性类型、允许的值、示例和元数据 |
TelemetryStartAttributeDefinition | 以要求开始属性定义 |
TelemetryEventAttributeDefinition | 事件属性定义与要求 |
TelemetryEventDefinition | 事件描述和属性定义 |
TelemetryParentDefinition | 开放的、外部的、或有限的 " 母性 " 规则 |
TelemetrySchemaSpanName | 宣布的跨度名称的结合 |
TelemetrySchemaSpanStartAttributes | 精确推断一个跨度的起始属性 |
TelemetrySchemaSpanEndAttributes | 一个跨度的可选推断端属性 |
TelemetrySchemaSpanEventName | 一分一秒宣布的活动合并 |
TelemetrySchemaSpanEventAttributes | 一个事件的精确推断属性 |
SchemaTelemetrySpan | 宽视限为 1 个方案间距 |
TelemetrySchemaSpanUnion | 在计划中歧视所有阶段的结合 |
InferStartAttributes | 从初始定义推断出的必要值和可选值 |
InferOptionalAttributes | 从终端定义推断出来的可选值 |
InferEventAttributes | 从事件定义推断出的必要值和可选值 |
InferRequiredAndOptionalAttributes | 必要定义的共同推论效用 |
ExactTelemetryAttributes | 在预期属性集之外拒绝密钥 |
测试子路径
| 导出 | 目的 |
|---|---|
createTelemetryAdapterConformance() | 创建独立运行的适配器 |
TelemetryAdapterFixture | 一个案件的新背景和普通快照阅读器 |
TelemetryAdapterFixtureFactory | 创建孤立的固定装置 |
TelemetryAdapterConformanceCase | 测试跑者执行的分组案件 |
发展
从此包目录 :
bun run test
bun run build
仓库全库类型检查、格式化、林地化和烟雾检查:
bun run check
许可证
麻省理工学院