管弦乐-JS/ @apm-js-collab/code-transformer
这是一个用于帮助在构建或加载时对 Node.js 库进行设备化的图书馆 时间.
它使用一个AST行走器来注入代码,该代码称为Node.js
TracingChannel。 。 。 .
您可能不想直接使用此库, 而是考虑使用 :
@apm-js-collab/tracing-hooks/- 无害环境管理
require装入时对仪器模块的钩子.
- 无害环境管理
apm-js-collab/code-transformer-bundler-plugins- 用于 Webpack、Vite、Rollup 和安装仪器模块的 Bundler 插件 在建设时间.
Java 脚本
@apm-js-collab/code-transformer 揭发图书馆.
使用量
import * as codeTransformer from "@apm-js-collab/code-transformer";
// The full instrumentation config
const instrumentation = {
// The name of the diagnostics channel
channelName: "my-channel",
// Define the module you'd like to inject tracing channels into
module: {
name: "my-module",
versionRange: ">=1.0.0",
filePath: "./dist/index.js",
},
// Define the function you'd like to instrument
// (e.g., match a method named 'foo' that returns a Promise)
functionQuery: {
methodName: "fetch",
kind: "Async",
},
};
// Create an InstrumentationMatcher with an array of instrumentation configs
const matcher = codeTransformer.create([instrumentation]);
// Get a transformer for a specific module
const transformer = matcher.getTransformer(
"my-module",
"1.2.3",
"./dist/index.js",
);
if (transformer === undefined) {
throw new Error("No transformer found for module");
}
// Transform code
const inputCode = "async function fetch() { return 42; }";
const result = transformer.transform(inputCode, "unknown");
console.log(result.code);
导出异名
当模块使用不同的名称再导出函数或类时
export { local as exported },你可以瞄准 导出 名称中
FunctionQuery 设置 isExportAlias: true。变压器将解决
在匹配前将别名移至本地声明.
例如:
function f(url) { return fetch(url); }
export { f as fetchAliased };
你可以瞄准目标 fetchAliased 在配置中 :
const instrumentation = {
channelName: "my-channel",
module: { name: "my-module", versionRange: ">=1.0.0", filePath: "./index.mjs" },
functionQuery: { functionName: "fetchAliased", kind: "Async", isExportAlias: true },
};
这也有利于阶级出口(例如:, export { MyClass as PublicClass }) (中文(简体) ).
更改返回值
用户既可以观察函数的返回值,也可以变异函数的返回值
通过 message.result重新调配 message.result 当
函数返回您需要的另一个函数(或对象)
包装,像工厂 返回每个要求处理器.
对于 同步 函数,重新指定 message.result 输入
end 处理器 :
const instrumentation = {
channelName: "create-handler",
module: { name: "my-framework", versionRange: ">=1.0.0", filePath: "lib/router.js" },
functionQuery: { methodName: "create", kind: "Sync" },
};
const { tracingChannel } = require("node:diagnostics_channel");
tracingChannel("orchestrion:my-framework:create-handler").subscribe({
end(message) {
const original = message.result;
// Replace the returned handler with a wrapped version.
message.result = function wrapped(...args) {
// ...start a span, etc.
return original.apply(this, args);
};
},
});
对于 一个同步 函数,重新指定 message.result 输入
asyncEnd 操作者替换返回的许诺值
决心——例如,将承诺决定的功能包起来
改为:
const instrumentation = {
channelName: "load-handler",
module: { name: "my-framework", versionRange: ">=1.0.0", filePath: "lib/router.js" },
functionQuery: { methodName: "load", kind: "Async" },
};
tracingChannel("orchestrion:my-framework:load-handler").subscribe({
asyncEnd(message) {
const original = message.result;
message.result = function wrapped(...args) {
// ...start a span, etc.
return original.apply(this, args);
};
},
});
[!INFO]
更改一个合成函数的解析值, 仅工作 当它返回本地时
Promise(单位:千美元)value instanceof Promise) (中文(简体) ). 承诺子类和其他可使用土地是 侧链并返回到呼叫者 不变,以便他们 子类特定方法(例如:.APIPromise.withResponse()(中文(简体) ) 仍可使用;因此,其确定的价值 无法 为: 变异,和再分配message.result他们没有 效应.
在抛出路径上,原始错误仍然传播
替换返回值仅适用于函数返回时(或
通常情况下。 如果没有订阅者重新指定 message.result,则
原始返回值保持不变.
AST 查询
名称 FunctionQuery 涵盖常见情况的变式
(命名函数,类/对象方法,表达式). 当你
需要瞄准一个无法表达的节点,例如 匿名
由工厂返回的函数 (装饰厂,a)
每个请求处理器,您可以设置 astQuery 改为生
风情 选择器代替.
现在, astQuery 选择乐器的节点
优先于 functionQuery匹配字段;
functionQuery 然后只提供行为(kind, (中文(简体) ), index, (中文(简体) ),
callbackIndex)并可能被省略(默认为 kind: "Sync") (中文(简体) ).
例如,工厂归还的装饰器的仪器:
function Injectable(options) {
return (target) => { /* applied to the decorated class */ };
}
const instrumentation = {
channelName: "injectable-apply",
module: { name: "@nestjs/common", versionRange: ">=8.0.0", filePath: "decorators/core/injectable.decorator.js" },
// Match the arrow returned from `Injectable`. There is no name to target!
astQuery: 'FunctionDeclaration[id.name="Injectable"] ReturnStatement > ArrowFunctionExpression',
functionQuery: { kind: "Sync" },
};
每次安装时,通道会起火
可用的装饰目标为 message.arguments[0],哪个a
订阅者可以变异,例如用来包装原型方法.
一个 astQuery 用于逐字记录,因此可以匹配任何节点,
包括那些基于名称的变体不暴露的,例如
匿名或深度嵌入功能。 (两个名称均基于
astQuery 匹配同步和同步函数的工作
类似。 )
如果一个 astQuery 匹配节点, “找不到注入”
点"出错包括选择器,因此可以调试.
自定义变形
内置运算符( E)traceSync, (中文(简体) ), tracePromise, (中文(简体) ),
traceCallback, (中文(简体) ), traceAuto选择 functionQuery.kind(中文(简体) )
包括诊断渠道追踪。 当你需要重写一个
匹配不同的节点,将自定义操作员注册到
matcher.addTransform(name, fn) 并参考从一个配置
transform 字段。 何时 transform 设定为优先
结束 functionQuery.kind。 。 。 .
自定义变换收到相同的参数 横向产量,加上合并的仪器状态,以及 变异匹配的节点 :
type CustomTransform = (state, node, parent, ancestry) => void;
state- 配置与运行时域合并 (单位:千美元)moduleVersion, (中文(简体) ),moduleType, (中文(简体) ),operator,解决functionQuery(中文(简体) ).node- 匹配的 AST 节点(选定的函数/表达式) 以functionQuery或者说astQuery) (中文(简体) ).parent, (中文(简体) ),ancestry- 父节点和完整的祖先链 (最后根数);ancestry[ancestry.length - 1]这是Program。 。 。 .
一个自定义的变换是完全自主的 node 重写。 没有
需要插入一个追踪通道.
const matcher = codeTransformer.create([
{
channelName: "fetch",
module: { name: "my-module", versionRange: ">=1.0.0", filePath: "index.js" },
functionQuery: { functionName: "fetch", kind: "Sync" },
transform: "myCustomTransform",
},
]);
matcher.addTransform("myCustomTransform", (state, node) => {
// Mutate the matched node's AST however you need.
node.body.body.unshift(/* an ESTree statement */);
});
注册匹配内置变换的名称( N)traceSync, (中文(简体) ),
tracePromise, (中文(简体) ), traceCallback, (中文(简体) ), traceAuto, (中文(简体) ),
tracingChannelImport, (中文(简体) ), tracingChannelDeclaration() 超过它
在它派遣的任何地方—— 包括当有人内在援引
另一个内部(例如,追踪操作员呼叫
tracingChannelDeclaration,这反过来又要求
tracingChannelImport) (中文(简体) ). state.transforms 持有合并的地图
用于此发送的(已安装内置外加已注册的覆盖); 至
代表给原内置的,从一个覆盖,称它通过
页:1 @apm-js-collab/code-transformer/lib/transforms 模块
直接说.
(CLI通过 customTransforms
区域。 见: CLI 工具. ) (中文(简体) )
API 参考
type ModuleType = "esm" | "cjs" | "unknown";
type FunctionKind = "Sync" | "Async" | "Callback" | "Auto";
FunctionQuery 备选案文
type FunctionQuery =
| // Match class constructor
{ className: string; index?: number | null; isExportAlias?: boolean }
| // Match class method
{
className: string;
methodName: string;
kind: FunctionKind;
index?: number | null;
callbackIndex?: number;
isExportAlias?: boolean;
}
| // Match method on objects
{ methodName: string; kind: FunctionKind; index?: number | null; callbackIndex?: number }
| // Match standalone function
{ functionName: string; kind: FunctionKind; index?: number | null; callbackIndex?: number; isExportAlias?: boolean }
| // Match arrow function or function expression
{ expressionName: string; kind: FunctionKind; index?: number | null; callbackIndex?: number; isExportAlias?: boolean };
| // Match private class methods
{ className: string; privateMethodName: string; kind: FunctionKind; index?: number | null; callbackIndex?: number };
ModuleMatcher
type ModuleMatcher = {
/** Module name */
name: string;
/** Matching semver range */
versionRange: string;
/**
* Relative Unix-style path to the file from the module root (e.g. "lib/index.js")
* Or a regular expression to test against the Unix-style path.
*/
filePath: string | RegExp;
};
InstrumentationConfig
// Behaviour-only fields, used when `astQuery` does the matching.
type FunctionBehavior = {
kind?: FunctionKind;
index?: number | null;
callbackIndex?: number;
};
type InstrumentationConfig =
| {
channelName: string; // Name of the diagnostics channel
module: ModuleMatcher;
functionQuery: FunctionQuery; // Name-based matching
astQuery?: string; // Raw esquery selector; takes precedence over functionQuery matching
transform?: string; // Name of a custom transform registered via addTransform
}
| {
channelName: string;
module: ModuleMatcher;
astQuery: string; // Raw esquery selector chooses the node(s)
functionQuery?: FunctionBehavior; // Behaviour only; matching fields ignored
transform?: string;
};
职能
create(configs: InstrumentationConfig[], dcModule?: string | null): InstrumentationMatcher;
为一个或多个仪器配置创建匹配器.
configs- 仪器配置阵列.dcModule- 要导入的可选模块diagnostics_channelAPI从.
InstrumentationMatcher
getTransformer(moduleName: string, version: string, filePath: string): Transformer | undefined;
获取特定模块和文件的变压器.
回返a Transformer 用于给定模块,或 undefined 如果没有
匹配仪器配置.
moduleName- 舱名.version- 模块的版本.filePath- 从模块根(如:."lib/index.js") (中文(简体) ). Windows风格的回斜道也被接受,并将自动正常化.
addTransform(name: string, fn: CustomTransform): void;
注册自定义变换操作员 name编号
从配置 transform 字段。 见 自定义
变换。 。 。 .
name- 运算符名称(也用作配置)transform数值).fn- 说吧(state, node, parent, ancestry) => void; 变异 匹配节点.
Transformer
transform(code: string | Buffer, moduleType: ModuleType, sourcemap?: string | undefined): TransformOutput;
转换代码,按配置进行注射跟踪.
回返 { code, map }。 。 。 . map 如果没有提供源图,则将取消定义.
code- JavaScript代码要转换.moduleType- 正在改造的模块类型.sourcemap- 代码的可选现有源地图.
CLI 工具
软件包包括一个用于应用转换到源文件的 CLI 工具 :
npx @apm-js-collab/code-transformer transformer.js source-file.js
CLI 使用量
CLI 工具需要两个参数:
transformer.js- 一个导出仪器配置的文件source-file.js- 要转换的源文件(可以是任何路径,包括节点 模块)
已转换的代码被写入 stdout, 您可以将它重定向到文件或管道到其他命令中 .
CLI 示例
创建变压器配置文件 :
// my-transformer.js
module.exports = [{
channelName: 'my-fetch-channel',
module: {
name: 'my-module',
versionRange: '>=1.0.0',
filePath: 'index.js'
},
functionQuery: {
functionName: 'fetch',
kind: 'Async'
}
}]
应用转换 :
npx @apm-js-collab/code-transformer my-transformer.js lib/index.js > instrumented.js
变压器配置文件还可以导出一个带有额外选项的对象:
module.exports = {
configs: [/* array of configs */],
dcModule: './custom-diagnostics-channel.js', // optional custom dc module
customTransforms: { // optional custom transform functions
myCustomTransform: (state, node) => {
// custom AST transformation logic
}
}
}
许可证
见LICENSE