链路追踪
Midway 采用社区最新的 open-telemetry 方案,其前身是知名的 OpenTracing 和 OpenCensus 规范,现阶段也是 CNCF 的孵化项目,社区许多知名的大公司如 Amazon,Dynatrace,Microsoft,Google,Datadog,Splunk 等都有使用。
open-telemetry 提供了通用的 Node.js 接入方案,以供应商无关的方式将数据接收,处理,导出,支持向一个或多个开源或者商业化的采集端发送可观测的数据(比如阿里云 SLS,Jaeger,Prometheus,Fluent Bit 等)。
Midway 提供了接入 open-telemetry 的 Node.js 方案,并提供了一些简单的使用 API。
open-telemetry 的 Tracing 部分当前 Node.js SDK 已经 Release 1.0.0,可以在生产使用,Metrics 部分未正式发布,我们依旧在跟进(编码)中。
使用须知
open-telemetry 基于 Node.js 的 Async_Hooks 的稳定 API 实现,经过我们的测试,在最新的 Node.js v14/v16 性能影响已经很小,可以在生产使用,在 v12 情况下虽然可以使用,但是性能依旧有不小的损失,请尽可能在 Node.js >= v14 的版本下使用。
安装基础依赖
# Node.js 的 api 抽象
$ npm install --save @opentelemetry/api
# Node.js 的 api 实现
$ npm install --save @opentelemetry/sdk-node
# 常用 Node.js 模块的埋点实现
$ npm install --save @opentelemetry/auto-instrumentations-node
# jaeger 输出器
$ npm install --save @opentelemetry/exporter-jaeger
以上的包均为 open-telemetry 的官方包。
启用 open-telemetry
open-telemetry 的模块请尽可能加在代码的最开始(比框架还要早),所以在不同场景中,我们有不同的添加方式。
使用 bootstrap 部署
如果使用 bootstrap.js
部署,你可以加在 bootstrap.js
的最顶部,示例代码如下。
const process = require('process');
const { NodeSDK, node, resources } = require('@opentelemetry/sdk-node');
const { getNodeAutoInstrumentations } = require('@opentelemetry/auto-instrumentations-node');
const { SemanticResourceAttributes } = require('@opentelemetry/semantic-conventions')
const { JaegerExporter } = require('@opentelemetry/exporter-jaeger')
// Midway 启动文件
const { Bootstrap } = require('@midwayjs/bootstrap');
// https://www.npmjs.com/package/@opentelemetry/exporter-jaeger
const tracerAgentHost = process.env['TRACER_AGENT_HOST'] || '127.0.0.1'
const jaegerExporter = new JaegerExporter({
host: tracerAgentHost,
});
// 初始化一个 open-telemetry 的 SDK
const sdk = new NodeSDK({
// 设置追踪服务名
resource: new resources.Resource({
[SemanticResourceAttributes.SERVICE_NAME]: 'my-app',
}),
// 配置当前的导出方式,比如这里配置了一个输出到控制台的,也可以配置其他的 Exporter,比如 Jaeger
traceExporter: new node.ConsoleSpanExporter(),
// 配置当前导出为 jaeger
// traceExporter: jaegerExporter,
// 这里配置了默认自带的一些监控模块,比如 http 模块等
// 若初始化时间很长,可注销此行,单独配置需要的 instrumentation 条目
instrumentations: [getNodeAutoInstrumentations()]
});
// 初始化 SDK,成功启动之后,再启动 Midway 框架
sdk.start()
// 在进程关闭时,同时关闭数据采集
process.on('SIGTERM', () => {
sdk.shutdown()
.then(() => console.log('Tracing terminated'))
.catch((error) => console.log('Error terminating tracing', error))
.finally(() => process.exit(0));
});
Bootstrap
.configure(/**/)
.run();
使用 egg-scripts 部署
egg-scripts 由于未提供入口部署,必须采用 --require
的形式加载额外的文件。
我们在根目录添加一个 otel.js
(注意是 js 文件),内容如下。
const process = require('process');
const { NodeSDK, node, resources } = require('@opentelemetry/sdk-node');
const { getNodeAutoInstrumentations } = require('@opentelemetry/auto-instrumentations-node');
// 初始化一个 open-telemetry 的 SDK
const sdk = new NodeSDK({
// 配置当前的导出方式,比如这里配置了一个输出到控制台的,也可以配置其他的 Exporter,比如 Jaeger
traceExporter: new node.ConsoleSpanExporter(),
// 这里配置了默认自带的一些监控模块,比如 http 模块等
instrumentations: [getNodeAutoInstrumentations()]
});
// 初始化 SDK
sdk.start()
// 在进程关闭时,同时关闭数据采集
process.on('SIGTERM', () => {
sdk.shutdown()
.then(() => console.log('Tracing terminated'))
.catch((error) => console.log('Error terminating tracing', error))
.finally(() => process.exit(0));
});
修改 package.json
中的启动命令。
{
// ...
"scripts": {
"start": "egg-scripts start --daemon --title=**** --framework=@midwayjs/web --require=./otel.js",
},
}
开发调试入口
midway-bin
使用 --entryFile
参数指定入口文件
例如 package.json
文件
{
"scripts": {
"start": "cross-env NODE_ENV=local midway-bin dev --ts --entryFile=bootstrap.js"
}
}
常用概念
open-telemetry 提供了一些抽象封装,将监控的整个过程包装为几个步骤,每个步骤都可自定义配置,其也有一些用户不太理解的术语,在下面做一些解释。
完整的英文概念请参考 Concepts。
API
用于生成和关联 Tracing、Metrics 和 Logs 记录数据的数据类型和操作的一组 API 抽象,具体表现为 @opentelemetry/api
这个包,里面是一些接口和空实现。
SDK
API 的特定语言实现,比如 Node.js 的实现(@opentelemetry/sdk-node
),其他监控平台的采集 SDK 实现等等。
Instrumentations
open-telemetry 提供了一些常见库的 shim 代码,使用 hooks 或者 monkey-patching 的方法来拦截方法,自动在特定方法调用时保存链路数据,支持 http,gRPC , redis,mysql 等模块,用户直接配置即可使用。
比如上面示例引入的 @opentelemetry/auto-instrumentations-node
就是一个已经默认封装好常用库的 instrumentations 集合包,里面包括了大部分会用到的库,具体的依赖请参考 Github。
Exporter
将接收到的链路数据发送到特定端的实现,比如 Jaeger,zipkin 等。
示例
添加三方 instrumentation
在 SDK 初始化时,添加到 instrumentations
数组中即可。
const { RedisInstrumentation } = require('@opentelemetry/instrumentation-redis');
// ...
// 初始化一个 open-telemetry 的 SDK
const sdk = new NodeSDK({
// ...
// 这里仅是添加的示例,如果使用了 auto-instrumentations-node,已经包含了下面的 instrumentation
instrumentations: [
new RedisInstrumentation(),
]
});
添加 Jaeger Exporter
这里以 Jaeger Exporter 作为示例,其他 Exporter 类似。
先添加依赖。
$ npm install --save @opentelemetry/exporter-jaeger @opentelemetry/propagator-jaeger
在 SDK 中配置。
const { JaegerExporter } = require('@opentelemetry/exporter-jaeger');
const { JaegerPropagator } = require('@opentelemetry/propagator-jaeger');
// ...
const exporter = new JaegerExporter({
tags: [], // optional
// You can use the default UDPSender
host: 'localhost', // optional
port: 6832, // optional
// OR you can use the HTTPSender as follows
// endpoint: 'http://localhost:14268/api/traces',
maxPacketSize: 65000 // optional
});
// 初始化一个 open-telemetry 的 SDK
const sdk = new NodeSDK({
traceExporter: exporter,
textMapPropagator: new JaegerPropagator()
// ...
});
具体参数请参考:
阿里云 ARMS
阿里云应用实时监控服务(ARMS)已经支持了 open-telemetry 格式的指标,同时提供一个 sdk 进行接入。
首先,安装 opentelemetry-arms
。
# arms sdk
$ npm install --save opentelemetry-arms
然后在启动时添加环境变量参数以及 -r
参数即可。
$ SERVICE_NAME=nodejs-opentelemetry-express AUTHENTICATION=**** ENDPOINT=grpc://**** node -r opentelemetry-arms bootstrap.js
- 1、这种方式接入,无需在
bootstrap.js
中添加代码。 - 2、默认 sdk 仅提供了 http/express/koa 模块的链路支持,未包含其他 instrumentations,如有需求,可以拷贝源码至
bootstrap.js
中自定义。
框架能力支持
注意,组件只是包裹了 otel 的接口,如果不需要下述接口使用,无需安装本组件
先安装依赖。
$ npm i @midwayjs/otel@3 --save
启用 otel
组件。
import { Configuration } from '@midwayjs/core';
import * as otel from '@midwayjs/otel';
@Configuration({
imports: [
// ...
otel
]
})
export class MainConfiguration {
}
ctx.traceId
组件提供了 ctx.traceId
字段。
你可以在支持的组件下进行获取(egg/koa)。
ctx.traceId => *****
装饰器支持
Midway 针对用户侧的需求,添加一个装饰器用于增加链路节点。
Otel 组件提供了一个 @Trace 装饰器,可以添加在方法上。
export class UserService {
@Trace('user.get')
async getUser() {
// ...
}
}
该装饰器需要传入一个节点名字,这样链路会自动添加一个该方法的链路节点,并记录执行的时间,方法执行成功或者失败。