@columenlabs/ lumen- ops
Lumen的"Cloudflare Workers"的操作部分:每个请求的应用程序回答,每个事件和每个数据查询都成为一个分析引擎点,这样你就可以发现出错,错配以及慢或重的查询并修正. 计数器将你的装订包了一次,所以新的代码是不选择加入的。 它只写每个查询的形状,从来不是值. 它也是仪表工具调用( I)/tools)并发送签名的webhoks(/webhooks) (中文(简体) ).
- 文档 : https://lumen.columenlabs.com/docs/ (中文(简体) )
- 对于代理人: https://lumen.columenlabs.com/llms.txt (中文(简体) )
- 资料来源: https://github.com/columenlabs/lumen (中文(简体) ) (单位:千美元)
packages/ops麻省理工学院.
安装
bun add @columenlabs/lumen-ops
把它固定在与你其他的鲁门软件包相同的准确版本上. 用这个 AsyncLocalStorage,所以工人们需要 nodejs_compat 旗帜.
设置
将分析引擎数据集装入 wrangler.jsonc数字 :
"analytics_engine_datasets": [{ "binding": "QUERIES", "dataset": "myapp_queries" }]
将包好的装订件 交给您的应用程序:
import { foldRoute, meteredClickHouse, meteredD1, setQueryCallerOf, setQuerySink } from '@columenlabs/lumen-ops';
setQuerySink(env.QUERIES);
// Name the request in scope: the app (or part of it), the route, and the tool when an MCP call runs.
setQueryCallerOf(() => {
const req = currentRequest(); // your own request scope
return req && { app: 'myapp', kind: 'fetch', route: foldRoute(new URL(req.url).pathname) };
});
const db = meteredD1(env.DB);
const clickhouse = meteredClickHouse(env.CLICKHOUSE); // any ClickHouse HTTP service binding
在永久对象中,测量其SQLite:
import { meteredSql } from '@columenlabs/lumen-ops';
const sql = meteredSql(state.storage.sql, { app: 'myapp', kind: 'do', route: 'Person' });
运行、排队或提醒名本身
import { withQueryCaller } from '@columenlabs/lumen-ops';
await withQueryCaller({ app: 'myapp', kind: 'cron', route: 'nightly-rollup' }, () => rollup(env));
另一个引擎(R2 SQL,一个HTTP数据库)调用 meterQuery({ engine, sql, method, ok, ms, ... }) 从它的一个客户端.
说得有道理
每回合发言一次1分:同一语句多次运行一次(插入圈)是总数的一分 statements 说多少跑。 以平分为正. 列单是读取数据集的合约:
| 栏 | 持有 |
|---|---|
index1 | 引擎 : d1, (中文(简体) ), clickhouse, (中文(简体) ), do_sqlite, (中文(简体) ), r2_sql 或属于自己的 |
blob1 . blob5 | 发动机、应用程序、种类(fetch, (中文(简体) ), cron, (中文(简体) ), queue, (中文(简体) ), alarm, (中文(简体) ), do页:1 :id),工具 |
blob6 . blob8 | 操作( O)select, (中文(简体) ), insert, (中文(简体) ), update, (中文(简体) ), delete, (中文(简体) ), ddl, (中文(简体) ), pragma, (中文(简体) ), other),指纹,查询的形状(最多 1 KB) |
blob9, (中文(简体) ), blob10 | ok 或者说 error方法(all, (中文(简体) ), first, (中文(简体) ), run, (中文(简体) ), raw, (中文(简体) ), batch, (中文(简体) ), query, (中文(简体) ), insert, (中文(简体) ), exec(中文(简体) ) |
double1 . double10 | 壁 ms,引擎本身的 ms,行读取,字节读取,行回取,行写取,回取字节(每个共取),HTTP状态,语句,最慢 ms |
形状有每个字符串和数字字面 ?, (中文(简体) ), IN 已折叠到 (?...),评论和点击之家 FORMAT 掉下去,插入的一行被切断。 指纹是形状的散列, 一种查询.
以分析引擎SQL API为例
SELECT blob7 AS fingerprint, any(blob8) AS shape,
sum(IF(double9 > 0, double9, 1) * _sample_interval) AS calls,
sum(double1 * _sample_interval) / sum(IF(double9 > 0, double9, 1) * _sample_interval) AS mean_ms,
max(double10) AS slowest_ms
FROM myapp_queries
WHERE timestamp > NOW() - INTERVAL '1' DAY
GROUP BY fingerprint ORDER BY mean_ms DESC LIMIT 20
出口
| 导出 | 它会做什么 |
|---|---|
setQuerySink(binding) | 数据集每个点; 在设定之前什么都不是写入的 |
setQueryCallerOf(fn) | 在没有任务时在范围中列出请求 |
withQueryCaller(caller, fn) | 充电内部的每个查询 fn 改为 caller |
meteredD1(db) | D1, 计量吸入器: prepare 财务报告和审定财务报表 batch,其中D1自己的行读取并写出并持续时间 |
meteredClickHouse(binding) | 一个 ClickHouse HTTP 服务绑定, 当答案被读取后调制: 墙上时间到最后一个字节, ClickHouse 自己的统计 |
meteredSql(sql, caller?) | 持久对象 storage.sql;其光标保持 toArray, (中文(简体) ), one, (中文(简体) ), raw, (中文(简体) ), next,重复和 columnNames |
meterQuery(point) | 一分一分,对任何其他引擎 |
queryShape(sql) | 查询的形状、指纹和操作 |
foldRoute(path) | 有类似 id 的片段的路径 :id |
setQueryRowCap(n) | 最多一行的语句应返回; 更多返回的语句应标记 capped 内 blob11 |
请求
@columenlabs/lumen-ops/hono meth 每一个请求一个Hono应用程序的答案:先挂载一次,然后该应用程序的每一个路由在后面写出一个有其路由模板,状态,结果代码和时间的点. 它将请求命名为范围,所以里面的事件和询问都带有它的ID.
import { requestId } from 'hono/request-id';
import { requestMeter } from '@columenlabs/lumen-ops/hono';
import { setRequestSink } from '@columenlabs/lumen-ops/requests';
app.use('*', async (c, next) => {
setRequestSink(c.env.REQUESTS, { log: true }); // also one JSON line per point in Workers Logs
await next();
});
app.use('*', requestId());
app.use('*', requestMeter({
app: 'myapp',
env: (c) => c.env.DEPLOY_ENV,
describe: (c) => ({ surface: 'api', subject: c.get('accountId'), callerKind: 'key' }),
}));
| 栏 | 持有 |
|---|---|
index1 | 主体:应用程序给谁打电话的不透明标识,或 anon |
blob1 . blob7 | App,表面,方法,路由模板,结果(ok,一个 error jSON的密码, exception, (中文(简体) ), http_<status>),呼叫者种类,呼叫者编号 |
blob8 . blob13 | 区域, env, 版本, colo, 国家, 请求 id |
double1 . double6 | 状态, ms, 字节在, 字节在外, 上游 ms, 子请求 |
路径没有匹配的路由被写入为 *所以扫描仪的垃圾不会落地 没有贺野 就把处理器包起来 withRequest(scope, route, fn) 打电话 meterRequest(point) 你自己来.
约束和外出电话
在条目和补丁时包起嵌入器 fetch 一次,每个持久对象RPC方法,工人AI,矢量化,KV,R2,服务或VPC呼叫和每个出站 fetch 被定时并收费于请求的范围:
import { meterFetch, meteredBindings } from '@columenlabs/lumen-ops';
import { requestMeter } from '@columenlabs/lumen-ops/hono';
meterFetch();
app.use('*', requestMeter({ app: 'myapp', serverTiming: true }));
export default { fetch: (req, env, ctx) => app.fetch(req, meteredBindings(env, { kinds: { AI: 'ai', INDEX: 'vectorize' }, skip: ['METRICS'] }), ctx) };
呼叫按其约束性和方法命名(DIRECTORY.seen, (中文(简体) ), AI.run, (中文(简体) ), fetch.api.example.com),从不争论. 请求点 upstream ms 财务报告和审定财务报表 subrequests 添加; serverTiming: true 将拆分寄回 Server-Timing (单位:千美元)do, (中文(简体) ), ai, (中文(简体) ), fetch. . . . . . . , app, (中文(简体) ), total),当请求结束时,每个调用代码是一个 call 活动(code 电话, ref 同类的, ms 总计, 计数: 呼叫, 失败 。 在请求(警报)之外,每个电话都是自己的事件. app 可能是一种功能,而且 route 当一个工人通过路径向其他应用程序请求时,请指定路径.
活动
@columenlabs/lumen-ops/events 写一个应用程序所做的不是它回答的请求: 呼叫另一个服务, 网约车进出,同步的开始和结束, 登录。 以这些要求的要点,他们表明,是否已经采取行动.
import { meterEvent, setEventSink } from '@columenlabs/lumen-ops/events';
setEventSink(env.EVENTS);
meterEvent({ event: 'bank.call', code: '/accounts/get', ok: res.ok, ms, status: res.status, upstream: true });
meterEvent({ event: 'sync.done', ok: true, subject: accountId, ref: connectionId, counts: [added, modified, removed] });
| 栏 | 持有 |
|---|---|
index1 | 主题,或 anon |
blob1 . blob10 | App,事件,代码,结果, ref,触发,请求id,上游id,env,版本 |
double1 . double6 | ms,状态,数一,数二,数三,尝试 |
upstream: true 在请求中增加通话时间 upstream ms在永久对象中,没有请求范围: setEventSink(binding, { app, env, version }) 默认,然后通过一个 requestId 如果你有电话就派来.
浏览器遥测
useLumenTelemetry 内 @columenlabs/lumen-ui 将打开的屏幕、 未发现的错误( 类和堆栈的散列, 永远不发送信件) 、 失败的请求和Web Vitals 分批发送到 /api/t工人检查并写道:
import { meterClientBatch, setClientSink } from '@columenlabs/lumen-ops/client';
setClientSink(env.CLIENT);
app.post('/api/t', async (c) => {
meterClientBatch(await c.req.json().catch(() => null), { app: 'console', subject: c.get('accountId') });
return c.body(null, 204);
});
| 栏 | 持有 |
|---|---|
index1 | 主题,或 anon |
blob1 . blob9 | 类App(view, (中文(简体) ), error, (中文(简体) ), fetch_fail, (中文(简体) ), vital),路由模板,名称,堆栈指纹,请求代号,版本,设备,env |
double1, (中文(简体) ), double2 | 数值(毫分或分数),状态 |
一批失败的 schema 被全部删除; 只有代码,模板和数字通过 .
工具调用
@columenlabs/lumen-ops/tools mm每个调用助手或代理制作:每调用1分,随时间,结果和所运行代码的散列,如此缓慢或失败的工具出现,相同的脚本被分组到各跑.
import { meteredCall, meterToolCall, setToolSink } from '@columenlabs/lumen-ops/tools';
setToolSink(env.TOOL_CALLS); // an Analytics Engine dataset binding
// Wrap a call: latency and outcome are taken for you.
const rows = await meteredCall({ app: 'trade', tool: 'portfolio_list', kind: 'call' }, () => run(args));
// Or write one by hand, with the code a script ran.
meterToolCall({ app: 'trade', tool: 'script', kind: 'script', ok: true, ms: 840, code: source, rows: 120 });
| 栏 | 持有 |
|---|---|
index1 | 应用 |
blob1.blob9 | 应用、工具、种类、结果(ok 或者说 error),错误类型、代码散列、源、级、运行 id |
double1.double6 | 毫秒、行、字节、字节、符号、符号 |
争论和结果从不写;只写出其大小.
| 导出 | 它会做什么 |
|---|---|
setToolSink(binding) | 数据集工具点进入; 在设定之前没有写入 |
meterToolCall(point) | 一分一分的手头 |
meteredCall(point, fn, errorKind?) | 运行 fn,定出时间并撰写结果 |
codeHash(code) | 一个简短的散列脚本, 白空间折叠 |
网络呼出
@columenlabs/lumen-ops/webhooks 发送一份快递,按 标准网络呼号 给定一个秘密时的 spec, 这样接收器就可以用任何标准的 Webhooks 库来验证它. 它从不扔出:结果说明它是如何走的,为的是应用自己的送货日志和回放.
import { newWebhookSecret, sendWebhook } from '@columenlabs/lumen-ops/webhooks';
const secret = newWebhookSecret(); // whsec_…, shown to its owner once, kept sealed
const r = await sendWebhook({
url: endpoint.url,
secret,
id: event.id, // the same id on a retry
body: JSON.stringify(event.payload),
headers: { authorization: `Bearer ${endpoint.token}` },
userAgent: 'MyApp-Webhooks/1',
});
if (!r.ok) scheduleRetry(event, r.status, r.error);
| 导出 | 它会做什么 |
|---|---|
sendWebhook(send) | 一个POST: { ok, status, ms, id, error? }; 当结束点从未回复时, 状态 0 |
newWebhookSecret() | 一个新的 whsec_ 秘密, 32 个随机字节 |
端点的自有头先行,所以永远无法替换签名头. 重定向算作失败, 除非 redirect 否则, 超时为 10 s .
版本数
0.x:任何未成年人可以断裂到1.0. 每次和鲁门合并 main 发布每个 Lumen 软件包的下一个补丁;参见 释放。 。 。 .