启动 Agent
编辑启动 Agent
编辑启动 Node.js APM Agent 有几种方法。选择最适合您的方法。选择方法时最重要的考虑因素是
- 确保 APM Agent 尽早启动,以及
- 有一种方便的方法来配置 Agent。
为了使 Node.js APM Agent 能够完全发挥作用,它必须在其他模块的require(...)
语句之前启动。APM Agent 通过将自身插入导入过程中来自动检测模块。如果在 APM Agent 启动之前导入给定模块,则它将无法检测该模块。
启动方法
编辑require('elastic-apm-node').start(...)
编辑启动 APM Agent 最常见的方法是需要elastic-apm-node
模块并在主模块的顶部调用.start()
方法。这允许您使用任何方法来配置 Agent。
const apm = require('elastic-apm-node').start({ // Add configuration options here. }); // Application main code goes here.
require('elastic-apm-node/start')
编辑另一种启动 Agent 的方法是使用elastic-apm-node/start
模块,该模块导入并启动Agent。
const apm = require('elastic-apm-node/start'); // Application main code goes here.
这种启动方法适用于那些使用 Babel 或 esbuild 等工具将使用 ES 模块(如下例所示)的代码转换为使用 CommonJS 的代码的工具。它确保在同一文件中的其他导入之前启动 APM Agent。有关详细信息,请参见下面的提升的 ES 模块导入。
import 'elastic-apm-node/start.js'; // Application main code goes here.
这种方法的一个限制是您不能使用选项对象配置 Agent,而必须依赖其他配置方法之一,例如设置ELASTIC_APM_...
环境变量。
注意:从 elastic-apm-node 3.47.0 版本开始,“elastic-apm-node/start.js”将不会在 Node.js Worker 线程中启动 Agent。
node -r elastic-apm-node/start.js ...
编辑另一种启动 Agent 的方法是使用-r elastic-apm-node/start.js
对node
的命令行选项。这将在应用程序代码启动之前加载并启动 APM Agent。此方法允许您无需接触任何代码即可启用 Agent。这是监控 AWS Lambda 函数和跟踪Next.js 服务器的推荐启动方法。
node -r elastic-apm-node/start.js app.js
-r, --require
选项也可以通过NODE_OPTIONS
环境变量指定。
# export ELASTIC_APM_... # Configure the agent with envvars. export NODE_OPTIONS='-r elastic-apm-node/start.js' node app.js
注意:从 elastic-apm-node 3.47.0 版本开始,“elastic-apm-node/start.js”将不会在Node.js Worker 线程中启动 Agent。新的 Worker 线程继承process.execArgv
和环境,因此“elastic-apm-node/start.js”将再次执行。“start.js”导致在每个 Worker 线程中启动新的 APM Agent 被认为是意外情况,因此目前已禁用。
单独的 APM 初始化模块
编辑如果您想避免提升的 ES 模块的陷阱,但仍然想要灵活地将配置对象传递给Agent 启动方法,那么一个不错的选择是编写一个单独的 JavaScript 或 TypeScript 模块来启动 Agent,并在主文件的顶部导入该初始化模块。例如
// initapm.ts import apm from 'elastic-apm-node'; apm.start({ serverUrl: 'https://...', secretToken: '...', // ... })
// main.ts import 'initapm' // Application code starts here.
启动陷阱
编辑本节显示使用某些技术启动 APM Agent 时有时会出现的细微的意外情况。使用任何生成已编译 JavaScript 的构建工具/系统的 Agent 的一般故障排除技巧是查看已编译的 JavaScript 以了解node
实际执行的内容。
提升的 ES 模块导入
编辑当使用 Babel 或 esbuild 等工具将使用 ES 模块(即import ...
语句)的代码转换为使用 CommonJS(即require(...)
)的代码时,所有导入都将“提升”到模块的顶部,正确遵循 ECMAScript 模块 (ESM) 语义。这意味着apm.start()
方法调用得太晚了——在http
模块导入之后。
例如,对以下代码运行 Babel 不会尽早启动 APM
import apm from 'elastic-apm-node'; apm.start() // This does not work. import http from 'http'; // ...
Babel 将其转换为等效于
var apm = require('elastic-apm-node'); var http = require('http'); apm.start() // This is started too late. // ...
elastic-apm-node/start
模块解决了这个问题。以下内容将有效
import 'elastic-apm-node/start'; // This works. import http from 'http'; // ...
使用 Babel 的更完整的示例在此处。
esbuild(如此处 esbuild 文档中所述)转换的 ES 模块用法也是如此。值得注意的是,TypeScript 在这方面不遵循 ECMAScript 模块语义。
另一个不错的选择是使用单独的 APM 初始化模块并首先导入它。
TypeScript 陷阱
编辑TypeScript 是一种编译为 JavaScript 的语言,通过tsc
TypeScript 编译器编译,然后通过node
(或其他 JavaScript 解释器)执行。有时,生成的 JavaScript 在使用此 APM Agent 时会出现陷阱。TypeScript 假设模块导入没有副作用,因此如果apm
变量未使用,它将省略以下导入
import apm from 'elastic-apm-node/start'; // Be careful
可以使用以下方法避免该省略
import 'elastic-apm-node/start';
或者使用类似这样的内容
import apm from 'elastic-apm-node/start'; apm; // Ensure import is kept for its side-effect.
TypeScript 5.0引入了--verbatimModuleSyntax
选项,可以避免这种省略。
捆绑器和 APM
编辑JavaScript 捆绑器是将许多 JavaScript 文件捆绑成一个或几个要执行的 JavaScript 文件的工具。它们通常还包括其他功能,例如编译(从较新的 JavaScript 语法到较旧的 JavaScript 语法,从 TypeScript),tree-shaking(删除未使用的代码部分),缩小,CSS/图像捆绑等。有很多捆绑器工具,包括:Webpack、esbuild、Rollup、Parcel。
捆绑器的主要用例是提高浏览器应用程序的性能,减少单独文件的数量和大小有助于减少网络和 CPU 开销。对于使用node
执行的服务器端 JavaScript 代码,用例通常较弱。但是,某些工具会将捆绑器用于服务器端 JavaScript,不一定是为了捆绑,而是为了其他一些功能。
不幸的是,使用捆绑器通常会破坏 APM Agent。将多个模块捆绑到单个文件中必然意味着用处理返回模块对象的自定义捆绑器代码替换require(...)
调用。但是 APM Agent 依赖于这些require(...)
调用来检测模块。对此没有自动修复。解决方法是
- 从捆绑包中排除
elastic-apm-node
APM Agent 模块;以及 - 可选地从捆绑包中排除您希望 APM Agent 检测的其他模块。
从捆绑包中“排除”模块foo(Webpack 将这些称为“外部”)意味着require('foo')
期望“node_modules/foo/…”在运行时存在。这意味着您需要部署捆绑包文件以及排除的模块。这可能会或可能不会破坏您使用捆绑器的理由。
本节的其余部分显示了如何使用各种捆绑器配置外部文件。如果您知道我们尚未记录的捆绑器机制,请告知我们。
Webpack
编辑Webpack 支持“外部”配置选项,以将其捆绑包中排除特定模块。至少必须将elastic-apm-agent模块设置为外部。此外,您希望 APM Agent 检测的任何模块(例如数据库客户端)也必须设置为外部。最简单的方法是使用webpack-node-externals模块将所有“node_modules/…”设置为外部。
对于 webpack@5,请确保您的“webpack.config.js”具有以下内容
const nodeExternals = require('webpack-node-externals'); module.exports = { // ... // Set these so Webpack emits code using Node's CommonJS // require functions and knows to use Node's core modules. target: 'node', externalsPresets: { node: true }, // This tells Webpack to make everything under // "node_modules/" external. externals: [nodeExternals()], };
对于 webpack@4,externalsPresets
配置变量不存在,因此请使用
const nodeExternals = require('webpack-node-externals'); module.exports = { // ... target: 'node', externals: [nodeExternals()], };
esbuild
编辑Esbuild 支持将模块/文件标记为“外部”到捆绑包。为了使 APM Agent 工作,至少必须将elastic-apm-agent模块设置为外部。此外,您希望 APM Agent 检测的任何模块(例如数据库客户端)也必须设置为外部。
这是一个“package.json”的示例构建脚本,用于捆绑 Node.js 应用程序(以“src/index.js”作为入口点,以 node v14.x 为目标,并确保检测pg
PostgreSQL 模块)
{ "scripts": { "build": "esbuild src/index.js --outdir=dist --bundle --sourcemap --minify --platform=node --target=node14 --external:elastic-apm-node --external:pg" } }
这可以通过以下方式调用:
npm run build
或者 esbuild 配置可以放入构建脚本中,并通过node esbuild.build.js
调用。
// esbuild.build.js require('esbuild').build({ entryPoints: ['./src/index.js'], outdir: 'dist', bundle: true, platform: 'node', target: 'node14', sourcemap: true, minify: true, external: ['elastic-apm-node', 'pg'] }).catch(() => process.exit(1))
手动将特定依赖项列为“外部”的替代方法是使用以下 esbuild 选项来排除所有依赖项
esbuild ... --external:'./node_modules/*'
使用 esbuild 和 APM Agent 的更完整示例在此处。