@vtvlive/interactive-apm 0.0.3 → 0.0.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/dist/factories/tracing-provider.factory.d.ts +6 -4
- package/dist/factories/tracing-provider.factory.d.ts.map +1 -1
- package/dist/factories/tracing-provider.factory.js +17 -14
- package/dist/index.d.ts +12 -11
- package/dist/index.d.ts.map +1 -1
- package/dist/init/elastic-apm-init.d.ts +25 -3
- package/dist/init/elastic-apm-init.d.ts.map +1 -1
- package/dist/init/elastic-apm-init.js +12 -12
- package/dist/init/opentelemetry-init.d.ts +6 -2
- package/dist/init/opentelemetry-init.d.ts.map +1 -1
- package/dist/init/opentelemetry-init.js +46 -42
- package/dist/interfaces/tracing-provider.interface.d.ts +13 -3
- package/dist/interfaces/tracing-provider.interface.d.ts.map +1 -1
- package/dist/modules/tracing.module.d.ts +5 -5
- package/dist/modules/tracing.module.d.ts.map +1 -1
- package/dist/modules/tracing.module.js +2 -1
- package/dist/providers/elastic-apm.tracing-provider.d.ts +23 -5
- package/dist/providers/elastic-apm.tracing-provider.d.ts.map +1 -1
- package/dist/providers/elastic-apm.tracing-provider.js +94 -30
- package/dist/providers/opentelemetry.tracing-provider.d.ts +6 -5
- package/dist/providers/opentelemetry.tracing-provider.d.ts.map +1 -1
- package/dist/providers/opentelemetry.tracing-provider.js +178 -85
- package/dist/services/tracing.service.d.ts +6 -5
- package/dist/services/tracing.service.d.ts.map +1 -1
- package/dist/services/tracing.service.js +2 -2
- package/dist/types/apm.types.d.ts +162 -0
- package/dist/types/apm.types.d.ts.map +1 -0
- package/dist/types/apm.types.js +37 -0
- package/dist/utils/debug-exporter-wrapper.d.ts +13 -2
- package/dist/utils/debug-exporter-wrapper.d.ts.map +1 -1
- package/dist/utils/debug-exporter-wrapper.js +82 -42
- package/dist/utils/debug-logger.d.ts +34 -9
- package/dist/utils/debug-logger.d.ts.map +1 -1
- package/dist/utils/debug-logger.js +37 -28
- package/dist/utils/tracing.helper.d.ts +2 -2
- package/dist/utils/tracing.helper.d.ts.map +1 -1
- package/dist/utils/tracing.helper.js +8 -4
- package/package.json +15 -4
|
@@ -45,21 +45,12 @@ class TransactionNameProcessor {
|
|
|
45
45
|
constructor() {
|
|
46
46
|
this.cachedSpanKind = null;
|
|
47
47
|
}
|
|
48
|
-
onStart(
|
|
49
|
-
// No-op - moved to onEnd to read final http.route
|
|
50
|
-
}
|
|
51
|
-
forceFlush() {
|
|
52
|
-
return Promise.resolve();
|
|
53
|
-
}
|
|
54
|
-
shutdown() {
|
|
55
|
-
return Promise.resolve();
|
|
56
|
-
}
|
|
57
|
-
onEnd(_span) {
|
|
48
|
+
onStart(span) {
|
|
58
49
|
// Lazy load SpanKind to avoid top-level import failure when @opentelemetry/api is absent
|
|
59
50
|
if (!this.cachedSpanKind) {
|
|
60
51
|
try {
|
|
61
52
|
// @ts-ignore - Optional peer dependency
|
|
62
|
-
this.cachedSpanKind = require(
|
|
53
|
+
this.cachedSpanKind = require("@opentelemetry/api").SpanKind;
|
|
63
54
|
}
|
|
64
55
|
catch {
|
|
65
56
|
// If @opentelemetry/api is not available, skip processing
|
|
@@ -67,19 +58,30 @@ class TransactionNameProcessor {
|
|
|
67
58
|
}
|
|
68
59
|
}
|
|
69
60
|
// Only process root spans (SERVER spans without parent)
|
|
70
|
-
|
|
61
|
+
const s = span;
|
|
62
|
+
if (s.parent || s.kind !== this.cachedSpanKind?.SERVER) {
|
|
71
63
|
return;
|
|
72
64
|
}
|
|
73
|
-
// Get HTTP attributes
|
|
74
|
-
const method =
|
|
75
|
-
const
|
|
76
|
-
const
|
|
77
|
-
|
|
65
|
+
// Get HTTP attributes - use http.target first (path + query), fallback to http.path
|
|
66
|
+
const method = s.attributes?.["http.method"];
|
|
67
|
+
const target = s.attributes?.["http.target"]; // /api/healthcheck/ping?foo=bar
|
|
68
|
+
const path = s.attributes?.["http.path"]; // fallback - just path
|
|
69
|
+
const fullPath = target || path;
|
|
70
|
+
if (method && fullPath) {
|
|
78
71
|
// Format: GET /api/healthcheck/ping
|
|
79
|
-
const fullName = `${method} ${
|
|
80
|
-
|
|
72
|
+
const fullName = `${method} ${fullPath}`;
|
|
73
|
+
s.updateName?.(fullName);
|
|
81
74
|
}
|
|
82
75
|
}
|
|
76
|
+
forceFlush() {
|
|
77
|
+
return Promise.resolve();
|
|
78
|
+
}
|
|
79
|
+
shutdown() {
|
|
80
|
+
return Promise.resolve();
|
|
81
|
+
}
|
|
82
|
+
onEnd(_span) {
|
|
83
|
+
// No-op - name is set in onStart
|
|
84
|
+
}
|
|
83
85
|
}
|
|
84
86
|
/**
|
|
85
87
|
* Normalize OTLP endpoint URL to ensure consistent format
|
|
@@ -92,23 +94,23 @@ function normalizeEndpoint(endpoint, isGrpc) {
|
|
|
92
94
|
const url = new URL(endpoint);
|
|
93
95
|
// Ensure port is specified
|
|
94
96
|
if (!url.port) {
|
|
95
|
-
const defaultPort = url.protocol ===
|
|
97
|
+
const defaultPort = url.protocol === "https:" ? "443" : "80";
|
|
96
98
|
url.port = defaultPort;
|
|
97
99
|
}
|
|
98
100
|
// Remove trailing slash
|
|
99
|
-
let pathname = url.pathname.replace(/\/+$/,
|
|
101
|
+
let pathname = url.pathname.replace(/\/+$/, "");
|
|
100
102
|
// For gRPC, remove /v1/traces as it uses different path
|
|
101
|
-
if (isGrpc && pathname.endsWith(
|
|
102
|
-
pathname = pathname.replace(
|
|
103
|
+
if (isGrpc && pathname.endsWith("/v1/traces")) {
|
|
104
|
+
pathname = pathname.replace("/v1/traces", "");
|
|
103
105
|
}
|
|
104
106
|
// For HTTP/PROTO, ensure /v1/traces path exists
|
|
105
|
-
if (!isGrpc && !pathname.endsWith(
|
|
106
|
-
pathname = pathname +
|
|
107
|
+
if (!isGrpc && !pathname.endsWith("/v1/traces")) {
|
|
108
|
+
pathname = pathname + "/v1/traces";
|
|
107
109
|
}
|
|
108
110
|
url.pathname = pathname;
|
|
109
111
|
return url.toString();
|
|
110
112
|
}
|
|
111
|
-
catch
|
|
113
|
+
catch {
|
|
112
114
|
// If URL parsing fails, return as-is
|
|
113
115
|
return endpoint;
|
|
114
116
|
}
|
|
@@ -122,24 +124,29 @@ class OpenTelemetryTracingProvider {
|
|
|
122
124
|
this.sdk = null;
|
|
123
125
|
this.tracer = null;
|
|
124
126
|
this.initialized = false;
|
|
125
|
-
this.serviceName =
|
|
126
|
-
|
|
127
|
-
this.
|
|
127
|
+
this.serviceName =
|
|
128
|
+
config.serviceName || process.env.ELASTIC_APM_SERVICE_NAME || "interactive-backend";
|
|
129
|
+
this.environment = config.environment || process.env.ELASTIC_APM_ENVIRONMENT || "development";
|
|
130
|
+
this.otlpEndpoint =
|
|
131
|
+
config.otlpEndpoint || process.env.ELASTIC_OTLP_ENDPOINT || "http://localhost:8200/v1/traces";
|
|
128
132
|
// Determine transport type (HTTP, gRPC, or PROTO)
|
|
129
|
-
const transportType = (config.otlpTransport ||
|
|
130
|
-
|
|
133
|
+
const transportType = (config.otlpTransport ||
|
|
134
|
+
process.env.ELASTIC_OTLP_TRANSPORT ||
|
|
135
|
+
otlp_transport_type_1.OtlpTransport.HTTP).toLowerCase();
|
|
136
|
+
if (transportType === otlp_transport_type_1.OtlpTransport.GRPC || transportType === "grpc") {
|
|
131
137
|
this.otlpTransport = otlp_transport_type_1.OtlpTransport.GRPC;
|
|
132
138
|
}
|
|
133
|
-
else if (transportType === otlp_transport_type_1.OtlpTransport.PROTO || transportType ===
|
|
139
|
+
else if (transportType === otlp_transport_type_1.OtlpTransport.PROTO || transportType === "proto") {
|
|
134
140
|
this.otlpTransport = otlp_transport_type_1.OtlpTransport.PROTO;
|
|
135
141
|
}
|
|
136
142
|
else {
|
|
137
143
|
this.otlpTransport = otlp_transport_type_1.OtlpTransport.HTTP;
|
|
138
144
|
}
|
|
139
|
-
this.secretToken = config.secretToken || process.env.ELASTIC_APM_SECRET_TOKEN ||
|
|
140
|
-
this.otlpAuthToken = config.otlpAuthToken || process.env.ELASTIC_OTLP_AUTH_TOKEN ||
|
|
145
|
+
this.secretToken = config.secretToken || process.env.ELASTIC_APM_SECRET_TOKEN || "";
|
|
146
|
+
this.otlpAuthToken = config.otlpAuthToken || process.env.ELASTIC_OTLP_AUTH_TOKEN || "";
|
|
141
147
|
this.otlpHeaders = config.otlpHeaders || {};
|
|
142
|
-
this.enableConsoleExporter =
|
|
148
|
+
this.enableConsoleExporter =
|
|
149
|
+
config.enableConsoleExporter ?? process.env.ELASTIC_OTLP_ENABLE_CONSOLE_EXPORTER === "true";
|
|
143
150
|
}
|
|
144
151
|
/**
|
|
145
152
|
* Initialize OpenTelemetry SDK
|
|
@@ -152,30 +159,33 @@ class OpenTelemetryTracingProvider {
|
|
|
152
159
|
try {
|
|
153
160
|
// Dynamic import to avoid errors when @opentelemetry packages are not installed
|
|
154
161
|
// @ts-ignore - Optional peer dependency
|
|
155
|
-
const { NodeSDK } = await Promise.resolve().then(() => __importStar(require(
|
|
162
|
+
const { NodeSDK } = await Promise.resolve().then(() => __importStar(require("@opentelemetry/sdk-node")));
|
|
156
163
|
// @ts-ignore - Optional peer dependency
|
|
157
|
-
const { HttpInstrumentation } = await Promise.resolve().then(() => __importStar(require(
|
|
164
|
+
const { HttpInstrumentation } = await Promise.resolve().then(() => __importStar(require("@opentelemetry/instrumentation-http")));
|
|
158
165
|
// @ts-ignore - Optional peer dependency
|
|
159
|
-
const { ExpressInstrumentation } = await Promise.resolve().then(() => __importStar(require(
|
|
166
|
+
const { ExpressInstrumentation } = await Promise.resolve().then(() => __importStar(require("@opentelemetry/instrumentation-express")));
|
|
160
167
|
// @ts-ignore - Optional peer dependency
|
|
161
|
-
const { resourceFromAttributes } = await Promise.resolve().then(() => __importStar(require(
|
|
168
|
+
const { resourceFromAttributes } = await Promise.resolve().then(() => __importStar(require("@opentelemetry/resources")));
|
|
162
169
|
// @ts-ignore - Optional peer dependency
|
|
163
|
-
const { trace } = await Promise.resolve().then(() => __importStar(require(
|
|
170
|
+
const { trace } = await Promise.resolve().then(() => __importStar(require("@opentelemetry/api")));
|
|
164
171
|
const endpoint = config?.otlpEndpoint || this.otlpEndpoint;
|
|
165
172
|
const isGrpc = this.otlpTransport === otlp_transport_type_1.OtlpTransport.GRPC;
|
|
166
173
|
const isProto = this.otlpTransport === otlp_transport_type_1.OtlpTransport.PROTO;
|
|
167
174
|
// Normalize endpoint to ensure consistent format
|
|
168
175
|
const normalizedEndpoint = normalizeEndpoint(endpoint, isGrpc);
|
|
169
176
|
// Log initialization details when debug mode is on
|
|
170
|
-
(0, debug_logger_1.logInitialization)(
|
|
177
|
+
(0, debug_logger_1.logInitialization)("OpenTelemetry", {
|
|
171
178
|
serviceName: config?.serviceName || this.serviceName,
|
|
172
179
|
environment: config?.environment || this.environment,
|
|
173
180
|
otlpEndpoint: normalizedEndpoint,
|
|
174
|
-
transport: isGrpc ?
|
|
175
|
-
hasToken: !!(config?.otlpAuthToken ||
|
|
181
|
+
transport: isGrpc ? "gRPC" : isProto ? "PROTO (protobuf over HTTP)" : "HTTP",
|
|
182
|
+
hasToken: !!(config?.otlpAuthToken ||
|
|
183
|
+
this.otlpAuthToken ||
|
|
184
|
+
config?.secretToken ||
|
|
185
|
+
this.secretToken),
|
|
176
186
|
enableConsoleExporter: config?.enableConsoleExporter ?? this.enableConsoleExporter,
|
|
177
187
|
});
|
|
178
|
-
const transportLabel = isGrpc ?
|
|
188
|
+
const transportLabel = isGrpc ? "gRPC" : isProto ? "PROTO" : "HTTP";
|
|
179
189
|
if ((0, debug_logger_1.isDebugEnabled)()) {
|
|
180
190
|
(0, debug_logger_1.infoLog)(`[OpenTelemetry] Service: ${this.serviceName}, Environment: ${this.environment}`);
|
|
181
191
|
(0, debug_logger_1.infoLog)(`[OpenTelemetry] Endpoint: ${normalizedEndpoint} (${transportLabel})`);
|
|
@@ -188,7 +198,7 @@ class OpenTelemetryTracingProvider {
|
|
|
188
198
|
};
|
|
189
199
|
const token = config?.otlpAuthToken || this.otlpAuthToken || config?.secretToken || this.secretToken;
|
|
190
200
|
if (token) {
|
|
191
|
-
headers[
|
|
201
|
+
headers["Authorization"] = `Bearer ${token}`;
|
|
192
202
|
}
|
|
193
203
|
return Object.keys(headers).length > 0 ? headers : undefined;
|
|
194
204
|
};
|
|
@@ -196,7 +206,7 @@ class OpenTelemetryTracingProvider {
|
|
|
196
206
|
let otlpExporter;
|
|
197
207
|
if (isGrpc) {
|
|
198
208
|
// @ts-ignore - Optional peer dependency
|
|
199
|
-
const { OTLPTraceExporter } = await Promise.resolve().then(() => __importStar(require(
|
|
209
|
+
const { OTLPTraceExporter } = await Promise.resolve().then(() => __importStar(require("@opentelemetry/exporter-trace-otlp-grpc")));
|
|
200
210
|
otlpExporter = new OTLPTraceExporter({
|
|
201
211
|
url: normalizedEndpoint,
|
|
202
212
|
headers: buildHeaders(),
|
|
@@ -204,7 +214,7 @@ class OpenTelemetryTracingProvider {
|
|
|
204
214
|
}
|
|
205
215
|
else if (isProto) {
|
|
206
216
|
// @ts-ignore - Optional peer dependency
|
|
207
|
-
const { OTLPTraceExporter } = await Promise.resolve().then(() => __importStar(require(
|
|
217
|
+
const { OTLPTraceExporter } = await Promise.resolve().then(() => __importStar(require("@opentelemetry/exporter-trace-otlp-proto")));
|
|
208
218
|
otlpExporter = new OTLPTraceExporter({
|
|
209
219
|
url: normalizedEndpoint,
|
|
210
220
|
headers: buildHeaders(),
|
|
@@ -212,7 +222,7 @@ class OpenTelemetryTracingProvider {
|
|
|
212
222
|
}
|
|
213
223
|
else {
|
|
214
224
|
// @ts-ignore - Optional peer dependency
|
|
215
|
-
const { OTLPTraceExporter } = await Promise.resolve().then(() => __importStar(require(
|
|
225
|
+
const { OTLPTraceExporter } = await Promise.resolve().then(() => __importStar(require("@opentelemetry/exporter-trace-otlp-http")));
|
|
216
226
|
otlpExporter = new OTLPTraceExporter({
|
|
217
227
|
url: normalizedEndpoint,
|
|
218
228
|
headers: buildHeaders(),
|
|
@@ -225,7 +235,7 @@ class OpenTelemetryTracingProvider {
|
|
|
225
235
|
let traceExporter = otlpExporterWithLogging;
|
|
226
236
|
if (shouldEnableConsole) {
|
|
227
237
|
// @ts-ignore - Optional peer dependency
|
|
228
|
-
const { ConsoleSpanExporter } = await Promise.resolve().then(() => __importStar(require(
|
|
238
|
+
const { ConsoleSpanExporter } = await Promise.resolve().then(() => __importStar(require("@opentelemetry/sdk-trace-base")));
|
|
229
239
|
const consoleExporter = new ConsoleSpanExporter();
|
|
230
240
|
// Combined Exporter để export ra cả console và OTLP
|
|
231
241
|
traceExporter = {
|
|
@@ -235,43 +245,50 @@ class OpenTelemetryTracingProvider {
|
|
|
235
245
|
const errors = [];
|
|
236
246
|
const checkComplete = () => {
|
|
237
247
|
if (completed === 2) {
|
|
238
|
-
resultCallback(hasError
|
|
248
|
+
resultCallback(hasError
|
|
249
|
+
? { code: 1, error: new Error(errors.map(e => e.message).join("; ")) }
|
|
250
|
+
: { code: 0 });
|
|
239
251
|
}
|
|
240
252
|
};
|
|
241
253
|
// Console exporter
|
|
242
254
|
consoleExporter.export(spans, (result) => {
|
|
243
|
-
|
|
255
|
+
const exportResult = result;
|
|
256
|
+
if (exportResult.error) {
|
|
244
257
|
hasError = true;
|
|
245
|
-
errors.push(
|
|
258
|
+
errors.push(exportResult.error);
|
|
246
259
|
}
|
|
247
260
|
completed++;
|
|
248
261
|
checkComplete();
|
|
249
262
|
});
|
|
250
263
|
// OTLP exporter with logging
|
|
251
264
|
otlpExporterWithLogging.export(spans, (result) => {
|
|
252
|
-
|
|
265
|
+
const exportResult = result;
|
|
266
|
+
if (exportResult.error) {
|
|
253
267
|
hasError = true;
|
|
254
|
-
errors.push(
|
|
268
|
+
errors.push(exportResult.error);
|
|
255
269
|
}
|
|
256
270
|
completed++;
|
|
257
271
|
checkComplete();
|
|
258
272
|
});
|
|
259
273
|
},
|
|
260
274
|
shutdown: async () => {
|
|
261
|
-
await Promise.all([
|
|
275
|
+
await Promise.all([
|
|
276
|
+
consoleExporter.shutdown(),
|
|
277
|
+
otlpExporterWithLogging.shutdown(),
|
|
278
|
+
]);
|
|
262
279
|
},
|
|
263
280
|
};
|
|
264
281
|
}
|
|
265
282
|
// Resource attributes
|
|
266
283
|
const sdkResource = resourceFromAttributes({
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
284
|
+
"service.name": config?.serviceName || this.serviceName,
|
|
285
|
+
"deployment.environment": config?.environment || this.environment,
|
|
286
|
+
"service.version": process.env.npm_package_version || "1.0.0",
|
|
287
|
+
"service.instance.id": `${process.pid}`,
|
|
288
|
+
"host.name": require("os").hostname(),
|
|
272
289
|
});
|
|
273
290
|
// Use BatchSpanProcessor for efficient batching with explicit flush
|
|
274
|
-
const { BatchSpanProcessor } = await Promise.resolve().then(() => __importStar(require(
|
|
291
|
+
const { BatchSpanProcessor } = await Promise.resolve().then(() => __importStar(require("@opentelemetry/sdk-trace-base")));
|
|
275
292
|
// Create a custom processor that extends BatchSpanProcessor behavior
|
|
276
293
|
// but also applies transaction naming
|
|
277
294
|
const transactionNameProcessor = new TransactionNameProcessor();
|
|
@@ -304,7 +321,8 @@ class OpenTelemetryTracingProvider {
|
|
|
304
321
|
// Khởi động SDK
|
|
305
322
|
this.sdk.start();
|
|
306
323
|
// Install HTTP interceptor for debug mode
|
|
307
|
-
const
|
|
324
|
+
const { createHttpRequestInterceptor } = require("../utils/debug-exporter-wrapper");
|
|
325
|
+
const interceptor = createHttpRequestInterceptor();
|
|
308
326
|
if (interceptor) {
|
|
309
327
|
interceptor.install();
|
|
310
328
|
}
|
|
@@ -312,32 +330,99 @@ class OpenTelemetryTracingProvider {
|
|
|
312
330
|
this.tracer = trace.getTracer(config?.serviceName || this.serviceName);
|
|
313
331
|
this.initialized = true;
|
|
314
332
|
if ((0, debug_logger_1.isDebugEnabled)()) {
|
|
315
|
-
(0, debug_logger_1.infoLog)(
|
|
333
|
+
(0, debug_logger_1.infoLog)("[OpenTelemetry] Initialized");
|
|
316
334
|
}
|
|
317
335
|
}
|
|
318
336
|
catch (error) {
|
|
319
|
-
(0, debug_logger_1.errorLog)(
|
|
337
|
+
(0, debug_logger_1.errorLog)("[OpenTelemetry] Failed to initialize:", error);
|
|
320
338
|
throw error;
|
|
321
339
|
}
|
|
322
340
|
}
|
|
323
341
|
/**
|
|
324
342
|
* Bắt đầu một span mới
|
|
325
343
|
*/
|
|
326
|
-
startSpan(name, attributes, spanKind =
|
|
344
|
+
startSpan(name, attributes, spanKind = "INTERNAL") {
|
|
327
345
|
if (!this.initialized || !this.tracer) {
|
|
328
346
|
if ((0, debug_logger_1.isDebugEnabled)()) {
|
|
329
|
-
console.warn(
|
|
347
|
+
console.warn("[OpenTelemetry] Not initialized");
|
|
330
348
|
}
|
|
331
349
|
return null;
|
|
332
350
|
}
|
|
333
351
|
// @ts-ignore - Optional peer dependency
|
|
334
|
-
const { context
|
|
335
|
-
const
|
|
352
|
+
const { context } = require("@opentelemetry/api");
|
|
353
|
+
const tracer = this.tracer;
|
|
354
|
+
const span = tracer.startSpan(name, {
|
|
336
355
|
attributes,
|
|
337
356
|
kind: this.mapSpanKind(spanKind),
|
|
338
357
|
}, context.active());
|
|
339
358
|
// Log span details when debug mode is on
|
|
340
|
-
|
|
359
|
+
if (span) {
|
|
360
|
+
const spanForLog = { name, kind: this.mapSpanKind(spanKind) };
|
|
361
|
+
(0, debug_logger_1.logSpan)("OpenTelemetry", spanForLog);
|
|
362
|
+
}
|
|
363
|
+
// Add end protection to prevent operations on ended span
|
|
364
|
+
if (span) {
|
|
365
|
+
let isEnded = false;
|
|
366
|
+
// Override setAttribute to check if span has ended
|
|
367
|
+
if (typeof span.setAttribute === "function") {
|
|
368
|
+
const originalSetAttribute = span.setAttribute.bind(span);
|
|
369
|
+
span.setAttribute = (key, value) => {
|
|
370
|
+
if (isEnded) {
|
|
371
|
+
if ((0, debug_logger_1.isDebugEnabled)()) {
|
|
372
|
+
console.warn(`[OpenTelemetry] Cannot set attribute "${key}" on ended span "${name}". ` +
|
|
373
|
+
`This attribute will not be sent to APM server.`);
|
|
374
|
+
}
|
|
375
|
+
return span;
|
|
376
|
+
}
|
|
377
|
+
originalSetAttribute(key, value);
|
|
378
|
+
return span;
|
|
379
|
+
};
|
|
380
|
+
}
|
|
381
|
+
// Override recordException to check if span has ended
|
|
382
|
+
if (typeof span.recordException === "function") {
|
|
383
|
+
const originalRecordException = span.recordException.bind(span);
|
|
384
|
+
span.recordException = (error) => {
|
|
385
|
+
if (isEnded) {
|
|
386
|
+
if ((0, debug_logger_1.isDebugEnabled)()) {
|
|
387
|
+
console.warn(`[OpenTelemetry] Cannot record exception on ended span "${name}". ` +
|
|
388
|
+
`This exception will not be sent to APM server.`);
|
|
389
|
+
}
|
|
390
|
+
return span;
|
|
391
|
+
}
|
|
392
|
+
originalRecordException(error);
|
|
393
|
+
return span;
|
|
394
|
+
};
|
|
395
|
+
}
|
|
396
|
+
// Override setStatus to check if span has ended
|
|
397
|
+
if (typeof span.setStatus === "function") {
|
|
398
|
+
const originalSetStatus = span.setStatus.bind(span);
|
|
399
|
+
span.setStatus = (status) => {
|
|
400
|
+
if (isEnded) {
|
|
401
|
+
if ((0, debug_logger_1.isDebugEnabled)()) {
|
|
402
|
+
console.warn(`[OpenTelemetry] Cannot set status on ended span "${name}". ` +
|
|
403
|
+
`This status will not be sent to APM server.`);
|
|
404
|
+
}
|
|
405
|
+
return span;
|
|
406
|
+
}
|
|
407
|
+
originalSetStatus(status);
|
|
408
|
+
return span;
|
|
409
|
+
};
|
|
410
|
+
}
|
|
411
|
+
// Override end to prevent duplicate ends
|
|
412
|
+
if (typeof span.end === "function") {
|
|
413
|
+
const originalEnd = span.end.bind(span);
|
|
414
|
+
span.end = () => {
|
|
415
|
+
if (isEnded) {
|
|
416
|
+
if ((0, debug_logger_1.isDebugEnabled)()) {
|
|
417
|
+
console.warn(`[OpenTelemetry] Span "${name}" has already been ended. Ignoring duplicate end().`);
|
|
418
|
+
}
|
|
419
|
+
return;
|
|
420
|
+
}
|
|
421
|
+
isEnded = true;
|
|
422
|
+
originalEnd();
|
|
423
|
+
};
|
|
424
|
+
}
|
|
425
|
+
}
|
|
341
426
|
return span;
|
|
342
427
|
}
|
|
343
428
|
/**
|
|
@@ -345,7 +430,7 @@ class OpenTelemetryTracingProvider {
|
|
|
345
430
|
*/
|
|
346
431
|
async startSpanWithParent(name, fn, attributes) {
|
|
347
432
|
// @ts-ignore - Optional peer dependency
|
|
348
|
-
const { context, trace, SpanStatusCode } = require(
|
|
433
|
+
const { context, trace, SpanStatusCode } = require("@opentelemetry/api");
|
|
349
434
|
return context.with(trace.setSpan(context.active(), this.startSpan(name, attributes)), async () => {
|
|
350
435
|
const span = trace.getActiveSpan();
|
|
351
436
|
try {
|
|
@@ -353,8 +438,13 @@ class OpenTelemetryTracingProvider {
|
|
|
353
438
|
return result;
|
|
354
439
|
}
|
|
355
440
|
catch (error) {
|
|
356
|
-
span
|
|
357
|
-
|
|
441
|
+
if (span) {
|
|
442
|
+
span.recordException(error);
|
|
443
|
+
span.setStatus({
|
|
444
|
+
code: SpanStatusCode.ERROR,
|
|
445
|
+
message: error.message,
|
|
446
|
+
});
|
|
447
|
+
}
|
|
358
448
|
if ((0, debug_logger_1.isDebugEnabled)()) {
|
|
359
449
|
console.log(`[APM-DEBUG] [OpenTelemetry] Span failed:`, name, error.message || error);
|
|
360
450
|
}
|
|
@@ -373,11 +463,14 @@ class OpenTelemetryTracingProvider {
|
|
|
373
463
|
return;
|
|
374
464
|
}
|
|
375
465
|
// @ts-ignore - Optional peer dependency
|
|
376
|
-
const { trace, SpanStatusCode } = require(
|
|
466
|
+
const { trace, SpanStatusCode } = require("@opentelemetry/api");
|
|
377
467
|
const span = trace.getActiveSpan();
|
|
378
468
|
if (span) {
|
|
379
469
|
span.recordException(error);
|
|
380
|
-
span.setStatus({
|
|
470
|
+
span.setStatus({
|
|
471
|
+
code: SpanStatusCode.ERROR,
|
|
472
|
+
message: error.message,
|
|
473
|
+
});
|
|
381
474
|
}
|
|
382
475
|
}
|
|
383
476
|
/**
|
|
@@ -388,7 +481,7 @@ class OpenTelemetryTracingProvider {
|
|
|
388
481
|
return;
|
|
389
482
|
}
|
|
390
483
|
// @ts-ignore - Optional peer dependency
|
|
391
|
-
const { trace } = require(
|
|
484
|
+
const { trace } = require("@opentelemetry/api");
|
|
392
485
|
const span = trace.getActiveSpan();
|
|
393
486
|
if (span) {
|
|
394
487
|
span.setAttribute(key, value);
|
|
@@ -398,15 +491,15 @@ class OpenTelemetryTracingProvider {
|
|
|
398
491
|
* Force flush all pending spans
|
|
399
492
|
*/
|
|
400
493
|
async forceFlush() {
|
|
401
|
-
if (this.sdk
|
|
494
|
+
if (this.sdk) {
|
|
402
495
|
try {
|
|
403
496
|
await this.sdk.forceFlush();
|
|
404
497
|
if ((0, debug_logger_1.isDebugEnabled)()) {
|
|
405
|
-
console.log(
|
|
498
|
+
console.log("[APM-DEBUG] Force flush completed");
|
|
406
499
|
}
|
|
407
500
|
}
|
|
408
501
|
catch (error) {
|
|
409
|
-
(0, debug_logger_1.errorLog)(
|
|
502
|
+
(0, debug_logger_1.errorLog)("[OpenTelemetry] Force flush failed:", error);
|
|
410
503
|
}
|
|
411
504
|
}
|
|
412
505
|
}
|
|
@@ -418,7 +511,7 @@ class OpenTelemetryTracingProvider {
|
|
|
418
511
|
return;
|
|
419
512
|
}
|
|
420
513
|
// @ts-ignore - Optional peer dependency
|
|
421
|
-
const { trace } = require(
|
|
514
|
+
const { trace } = require("@opentelemetry/api");
|
|
422
515
|
const activeSpan = span || trace.getActiveSpan();
|
|
423
516
|
if (activeSpan) {
|
|
424
517
|
activeSpan.end();
|
|
@@ -437,17 +530,17 @@ class OpenTelemetryTracingProvider {
|
|
|
437
530
|
*/
|
|
438
531
|
mapSpanKind(kind) {
|
|
439
532
|
// @ts-ignore - Optional peer dependency
|
|
440
|
-
const { SpanKind } = require(
|
|
533
|
+
const { SpanKind } = require("@opentelemetry/api");
|
|
441
534
|
switch (kind.toUpperCase()) {
|
|
442
|
-
case
|
|
535
|
+
case "SERVER":
|
|
443
536
|
return SpanKind.SERVER;
|
|
444
|
-
case
|
|
537
|
+
case "CLIENT":
|
|
445
538
|
return SpanKind.CLIENT;
|
|
446
|
-
case
|
|
539
|
+
case "PRODUCER":
|
|
447
540
|
return SpanKind.PRODUCER;
|
|
448
|
-
case
|
|
541
|
+
case "CONSUMER":
|
|
449
542
|
return SpanKind.CONSUMER;
|
|
450
|
-
case
|
|
543
|
+
case "INTERNAL":
|
|
451
544
|
default:
|
|
452
545
|
return SpanKind.INTERNAL;
|
|
453
546
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { ITracingProvider } from
|
|
1
|
+
import { ITracingProvider } from "../interfaces/tracing-provider.interface";
|
|
2
|
+
import { ISpan } from "../types/apm.types";
|
|
2
3
|
/**
|
|
3
4
|
* Tracing Service - Wrapper cho ITracingProvider
|
|
4
5
|
* Service này được inject vào controllers/services để sử dụng tracing
|
|
@@ -14,7 +15,7 @@ import { ITracingProvider } from '../interfaces/tracing-provider.interface';
|
|
|
14
15
|
* try {
|
|
15
16
|
* // ... code
|
|
16
17
|
* } finally {
|
|
17
|
-
* span
|
|
18
|
+
* span?.end();
|
|
18
19
|
* }
|
|
19
20
|
* }
|
|
20
21
|
*
|
|
@@ -46,7 +47,7 @@ export declare class TracingService {
|
|
|
46
47
|
* span.end();
|
|
47
48
|
* }
|
|
48
49
|
*/
|
|
49
|
-
startSpan(name: string, attributes?: Record<string, string | number>, spanKind?: string):
|
|
50
|
+
startSpan(name: string, attributes?: Record<string, string | number>, spanKind?: string): ISpan | null;
|
|
50
51
|
/**
|
|
51
52
|
* Thực thi function với context tracing (auto-close span)
|
|
52
53
|
* @param name Tên của span
|
|
@@ -63,7 +64,7 @@ export declare class TracingService {
|
|
|
63
64
|
* }
|
|
64
65
|
* );
|
|
65
66
|
*/
|
|
66
|
-
startSpanWithParent<T>(name: string, fn: (span:
|
|
67
|
+
startSpanWithParent<T>(name: string, fn: (span: ISpan | null) => Promise<T>, attributes?: Record<string, string | number>): Promise<T>;
|
|
67
68
|
/**
|
|
68
69
|
* Capture error vào active span hiện tại
|
|
69
70
|
* @param error Error object
|
|
@@ -79,7 +80,7 @@ export declare class TracingService {
|
|
|
79
80
|
* Kết thúc span manually
|
|
80
81
|
* @param span Span cần end (nếu không truyền, sẽ end active span)
|
|
81
82
|
*/
|
|
82
|
-
endSpan(span?:
|
|
83
|
+
endSpan(span?: ISpan): void;
|
|
83
84
|
/**
|
|
84
85
|
* Flush và shutdown APM provider (cho graceful shutdown)
|
|
85
86
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tracing.service.d.ts","sourceRoot":"","sources":["../../src/services/tracing.service.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,0CAA0C,CAAC;
|
|
1
|
+
{"version":3,"file":"tracing.service.d.ts","sourceRoot":"","sources":["../../src/services/tracing.service.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,0CAA0C,CAAC;AAC5E,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAE3C;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,qBAAa,cAAc;IACb,OAAO,CAAC,QAAQ,CAAC,QAAQ;gBAAR,QAAQ,EAAE,gBAAgB;IAEvD;;;;;;;;;;;;;;;;;;;OAmBG;IACH,SAAS,CACP,IAAI,EAAE,MAAM,EACZ,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,EAC5C,QAAQ,GAAE,MAAmB,GAC5B,KAAK,GAAG,IAAI;IAIf;;;;;;;;;;;;;;;OAeG;IACH,mBAAmB,CAAC,CAAC,EACnB,IAAI,EAAE,MAAM,EACZ,EAAE,EAAE,CAAC,IAAI,EAAE,KAAK,GAAG,IAAI,KAAK,OAAO,CAAC,CAAC,CAAC,EACtC,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,GAC3C,OAAO,CAAC,CAAC,CAAC;IAIb;;;OAGG;IACH,YAAY,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI;IAIhC;;;;OAIG;IACH,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI;IAIvD;;;OAGG;IACH,OAAO,CAAC,IAAI,CAAC,EAAE,KAAK,GAAG,IAAI;IAI3B;;OAEG;IACG,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;CAGhC"}
|
|
@@ -16,7 +16,7 @@ exports.TracingService = void 0;
|
|
|
16
16
|
* try {
|
|
17
17
|
* // ... code
|
|
18
18
|
* } finally {
|
|
19
|
-
* span
|
|
19
|
+
* span?.end();
|
|
20
20
|
* }
|
|
21
21
|
* }
|
|
22
22
|
*
|
|
@@ -49,7 +49,7 @@ class TracingService {
|
|
|
49
49
|
* span.end();
|
|
50
50
|
* }
|
|
51
51
|
*/
|
|
52
|
-
startSpan(name, attributes, spanKind =
|
|
52
|
+
startSpan(name, attributes, spanKind = "INTERNAL") {
|
|
53
53
|
return this.provider.startSpan(name, attributes, spanKind);
|
|
54
54
|
}
|
|
55
55
|
/**
|