@ryanzeng/nest-observe 0.1.3 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +98 -3
- package/dist/index.js +6 -5
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +6 -5
- package/dist/index.mjs.map +1 -1
- package/dist/register.js +6 -5
- package/dist/register.js.map +1 -1
- package/dist/register.mjs +6 -5
- package/dist/register.mjs.map +1 -1
- package/package.json +2 -3
package/README.md
CHANGED
|
@@ -44,7 +44,7 @@ OBSERVE_ENVIRONMENT=production
|
|
|
44
44
|
|
|
45
45
|
## Nest Module
|
|
46
46
|
|
|
47
|
-
`register` 已能在加载阶段自动挂载 HTTP、Nest Controller、Provider
|
|
47
|
+
`register` 已能在加载阶段自动挂载 HTTP、Nest Controller、Provider 和 Logger instrumentation。也可以导入全局 Module;它为较晚初始化或测试场景提供 Discovery fallback,并在 Nest 关闭时 flush/shutdown:
|
|
48
48
|
|
|
49
49
|
```ts
|
|
50
50
|
import { Module } from '@nestjs/common';
|
|
@@ -58,6 +58,75 @@ export class AppModule {}
|
|
|
58
58
|
|
|
59
59
|
Module-only 模式还会注册一个全局 Nest interceptor,补采因 HTTP 模块已经加载而无法挂载底层 hook 的已匹配 Controller 请求指标。它与早期 HTTP instrumentation 共享请求状态,不会重复计数。
|
|
60
60
|
|
|
61
|
+
## 可选 Instrumentation
|
|
62
|
+
|
|
63
|
+
核心包不内置 Prisma、Redis 或其他数据库客户端 instrumentation。应用只安装自己实际使用的适配包,并在目标客户端被加载之前注册:
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
# Prisma
|
|
67
|
+
pnpm add @prisma/instrumentation
|
|
68
|
+
|
|
69
|
+
# node-redis
|
|
70
|
+
pnpm add @opentelemetry/instrumentation-redis
|
|
71
|
+
|
|
72
|
+
# ioredis
|
|
73
|
+
pnpm add @opentelemetry/instrumentation-ioredis
|
|
74
|
+
|
|
75
|
+
# Pino / Winston(日志类,见"自动采集内容 → Logs")
|
|
76
|
+
pnpm add @opentelemetry/instrumentation-pino
|
|
77
|
+
pnpm add @opentelemetry/instrumentation-winston
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
创建一个只负责注册应用级 instrumentation 的文件,例如 `src/observability.instrumentation.ts`:
|
|
81
|
+
|
|
82
|
+
```ts
|
|
83
|
+
import { registerInstrumentations } from '@opentelemetry/instrumentation';
|
|
84
|
+
import { PrismaInstrumentation } from '@prisma/instrumentation';
|
|
85
|
+
import { IORedisInstrumentation } from '@opentelemetry/instrumentation-ioredis';
|
|
86
|
+
|
|
87
|
+
export const unregisterApplicationInstrumentations = registerInstrumentations({
|
|
88
|
+
instrumentations: [
|
|
89
|
+
new PrismaInstrumentation(),
|
|
90
|
+
new IORedisInstrumentation(),
|
|
91
|
+
],
|
|
92
|
+
});
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
如果应用使用的是 `redis` 包而不是 `ioredis`,替换为:
|
|
96
|
+
|
|
97
|
+
```ts
|
|
98
|
+
import { RedisInstrumentation } from '@opentelemetry/instrumentation-redis';
|
|
99
|
+
|
|
100
|
+
new RedisInstrumentation();
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
然后将注册文件放在应用入口的最前面。使用早期 `register` 初始化时:
|
|
104
|
+
|
|
105
|
+
```ts
|
|
106
|
+
import './observability.instrumentation';
|
|
107
|
+
import '@ryanzeng/nest-observe/register';
|
|
108
|
+
import { NestFactory } from '@nestjs/core';
|
|
109
|
+
import { AppModule } from './app.module';
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
仅使用 `ObserveModule.forRoot()` 时同样先加载应用级 instrumentation:
|
|
113
|
+
|
|
114
|
+
```ts
|
|
115
|
+
import './observability.instrumentation';
|
|
116
|
+
import { NestFactory } from '@nestjs/core';
|
|
117
|
+
import { AppModule } from './app.module';
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
应用级 instrumentation 会使用 `nest-observe` 注册的全局 tracer provider,因此不应再创建第二个 `NodeTracerProvider` 或第二套 OTLP exporter。对于会重排 import 的 bundler,生产环境应使用 Node 的 `--require`(CommonJS)或 `--import`(ESM)预加载编译后的 instrumentation 文件,确保它早于 Prisma/Redis 客户端执行。
|
|
121
|
+
|
|
122
|
+
### 为什么必须放在入口第一条 import
|
|
123
|
+
|
|
124
|
+
OTel instrumentation 通过 hook Node 的模块加载器工作:当目标模块(如 `@prisma/client`)第一次被 require/import 时,hook 替换它的导出(`PrismaClient` 类等);模块一旦进入 `require.cache`,加载流程不会再走一遍,hook 永远不会再触发。事后补救也不可行——ESM 命名空间只读,CJS 下应用已持有的类引用也不受缓存修改影响。
|
|
125
|
+
|
|
126
|
+
而 import 的求值顺序是"先 imports、后模块体":`import { AppModule }` 会把整个依赖图(包括 `PrismaService` → `@prisma/client`)加载完,之后才执行 main.ts 的函数体。因此任何运行在 Nest DI 生命周期里的注册(包括 `ObserveModule.forRoot()`)都晚于客户端加载——这正是 `forRoot()` 不提供第三方客户端 instrumentation 参数的原因,只有 import 声明顺序的第一位早于它。Nest 语义层采集(Controller/Provider 方法)不受此限制:那些对象由 DI 在运行时逐个创建,天然晚于 instrumentation 就位。
|
|
127
|
+
|
|
128
|
+
OTel 官方对"注册过晚"的处理同样只是警告、无法补救(`_warnOnPreloadedModules`),所以放错位置的注册不会报错,只会静默失效——排查"span 没有出现"问题时,请先检查注册文件是否在入口第一条 import。
|
|
129
|
+
|
|
61
130
|
## 装饰器
|
|
62
131
|
|
|
63
132
|
```ts
|
|
@@ -90,17 +159,43 @@ export class OrderService {
|
|
|
90
159
|
Traces:
|
|
91
160
|
|
|
92
161
|
- Node HTTP client/server、Nest Controller/handler、Provider method
|
|
93
|
-
- Prisma instrumentation
|
|
162
|
+
- 应用可按需组合 Prisma、Redis、ioredis 等标准 OpenTelemetry instrumentation
|
|
94
163
|
- `@Trace()` 自定义 span 和自然 parent/child context
|
|
95
164
|
- Controller/Provider 异常事件、错误状态及 stack trace
|
|
96
165
|
|
|
97
166
|
Logs:
|
|
98
167
|
|
|
99
168
|
- 默认 Nest `Logger` 的 `verbose/debug/log/warn/error/fatal`
|
|
169
|
+
- 尊重 Nest 11+ 的实例级日志级别过滤(`logLevels` / `setLogLevels` / `Logger.overrideLogger`),上传内容与控制台输出一致
|
|
100
170
|
- 对象和数组消息会在脱敏后保持结构化 body,由观测后端或查询侧决定如何展开、展示或序列化
|
|
101
171
|
- OTLP severity、`nestjs.context`、`trace_id`、`span_id`
|
|
102
172
|
- `service.name`、`service.version`、`deployment.environment.name`
|
|
103
|
-
|
|
173
|
+
|
|
174
|
+
**采集边界**:自动桥接覆盖所有经过 `ConsoleLogger` 的日志(包括默认 `Logger`)。自定义 `LoggerService` 若完全不经过 `ConsoleLogger`(例如直接封装 Pino/Winston),不会被自动采集。两种接入方式:
|
|
175
|
+
|
|
176
|
+
一是用 `StructuredLogEmitter` 桥接,在自定义 Logger 里显式 emit:
|
|
177
|
+
|
|
178
|
+
```ts
|
|
179
|
+
import { Injectable, LoggerService } from '@nestjs/common';
|
|
180
|
+
import { OpenTelemetryLogEmitter, redact } from '@ryanzeng/nest-observe';
|
|
181
|
+
|
|
182
|
+
@Injectable()
|
|
183
|
+
export class CustomLogger implements LoggerService {
|
|
184
|
+
private readonly emitter = new OpenTelemetryLogEmitter();
|
|
185
|
+
|
|
186
|
+
error(message: unknown, context?: string): void {
|
|
187
|
+
// 继续执行你的 Pino/Winston 输出……
|
|
188
|
+
this.emitter.emit({
|
|
189
|
+
body: redact(message),
|
|
190
|
+
severityText: 'ERROR',
|
|
191
|
+
attributes: context ? { 'nestjs.context': context } : {},
|
|
192
|
+
timestamp: Date.now(),
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
二是注册 OTel 官方的 `@opentelemetry/instrumentation-pino` 或 `@opentelemetry/instrumentation-winston`(方式同上方"可选 Instrumentation"),日志会使用 SDK 注册的全局 Logger Provider,进入同一条 OTLP 管道。
|
|
104
199
|
|
|
105
200
|
Metrics:
|
|
106
201
|
|
package/dist/index.js
CHANGED
|
@@ -368,6 +368,10 @@ var NestLoggerInstrumentation = class {
|
|
|
368
368
|
const resourceAttributes = this.resourceAttributes;
|
|
369
369
|
prototype[method] = function(message, ...args) {
|
|
370
370
|
try {
|
|
371
|
+
const gated = this;
|
|
372
|
+
if (typeof gated.isLevelEnabled === "function" && !gated.isLevelEnabled(method)) {
|
|
373
|
+
return original.call(this, message, ...args);
|
|
374
|
+
}
|
|
371
375
|
const spanContext = import_api3.trace.getActiveSpan()?.spanContext();
|
|
372
376
|
const contextName = this.context ?? (typeof args.at(-1) === "string" ? String(args.at(-1)) : void 0);
|
|
373
377
|
const attributes = { ...resourceAttributes };
|
|
@@ -697,7 +701,6 @@ var import_exporter_metrics_otlp_proto = require("@opentelemetry/exporter-metric
|
|
|
697
701
|
var import_exporter_trace_otlp_proto = require("@opentelemetry/exporter-trace-otlp-proto");
|
|
698
702
|
var import_instrumentation2 = require("@opentelemetry/instrumentation");
|
|
699
703
|
var import_instrumentation_http = require("@opentelemetry/instrumentation-http");
|
|
700
|
-
var import_instrumentation3 = require("@prisma/instrumentation");
|
|
701
704
|
var import_sdk_logs = require("@opentelemetry/sdk-logs");
|
|
702
705
|
var import_sdk_metrics = require("@opentelemetry/sdk-metrics");
|
|
703
706
|
var import_sdk_trace_base = require("@opentelemetry/sdk-trace-base");
|
|
@@ -863,7 +866,7 @@ var import_node_os2 = require("os");
|
|
|
863
866
|
// package.json
|
|
864
867
|
var package_default = {
|
|
865
868
|
name: "@ryanzeng/nest-observe",
|
|
866
|
-
version: "0.1.
|
|
869
|
+
version: "0.1.4",
|
|
867
870
|
description: "Zero-config, vendor-neutral OpenTelemetry observability for NestJS",
|
|
868
871
|
keywords: [
|
|
869
872
|
"nestjs",
|
|
@@ -948,8 +951,7 @@ var package_default = {
|
|
|
948
951
|
"@opentelemetry/sdk-logs": "^0.221.0",
|
|
949
952
|
"@opentelemetry/sdk-metrics": "^2.10.0",
|
|
950
953
|
"@opentelemetry/sdk-trace-base": "^2.10.0",
|
|
951
|
-
"@opentelemetry/sdk-trace-node": "^2.10.0"
|
|
952
|
-
"@prisma/instrumentation": "^7.10.0"
|
|
954
|
+
"@opentelemetry/sdk-trace-node": "^2.10.0"
|
|
953
955
|
},
|
|
954
956
|
devDependencies: {
|
|
955
957
|
"@nestjs/common": "^12.0.1",
|
|
@@ -1264,7 +1266,6 @@ function observe(options = {}) {
|
|
|
1264
1266
|
providerTracing: config.providerTracing,
|
|
1265
1267
|
controllerTracing: config.controllerTracing
|
|
1266
1268
|
}));
|
|
1267
|
-
if (config.traces) instrumentations.push(new import_instrumentation3.PrismaInstrumentation());
|
|
1268
1269
|
(0, import_instrumentation2.registerInstrumentations)({
|
|
1269
1270
|
instrumentations,
|
|
1270
1271
|
...tracerProvider ? { tracerProvider } : {},
|