@vtvlive/interactive-apm 0.0.2 → 0.0.3
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 +201 -123
- package/dist/factories/tracing-provider.factory.d.ts +3 -0
- package/dist/factories/tracing-provider.factory.d.ts.map +1 -1
- package/dist/factories/tracing-provider.factory.js +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/init/elastic-apm-init.d.ts.map +1 -1
- package/dist/init/elastic-apm-init.js +24 -7
- package/dist/init/opentelemetry-init.d.ts +3 -0
- package/dist/init/opentelemetry-init.d.ts.map +1 -1
- package/dist/init/opentelemetry-init.js +122 -25
- package/dist/providers/elastic-apm.tracing-provider.d.ts.map +1 -1
- package/dist/providers/elastic-apm.tracing-provider.js +45 -10
- package/dist/providers/opentelemetry.tracing-provider.d.ts +7 -0
- package/dist/providers/opentelemetry.tracing-provider.d.ts.map +1 -1
- package/dist/providers/opentelemetry.tracing-provider.js +195 -27
- package/dist/types/otlp-transport.type.d.ts +14 -0
- package/dist/types/otlp-transport.type.d.ts.map +1 -0
- package/dist/types/otlp-transport.type.js +17 -0
- package/dist/utils/debug-exporter-wrapper.d.ts +25 -0
- package/dist/utils/debug-exporter-wrapper.d.ts.map +1 -0
- package/dist/utils/debug-exporter-wrapper.js +207 -0
- package/dist/utils/debug-logger.d.ts +56 -0
- package/dist/utils/debug-logger.d.ts.map +1 -0
- package/dist/utils/debug-logger.js +227 -0
- package/package.json +11 -1
|
@@ -52,6 +52,41 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
52
52
|
exports.shouldUseOpenTelemetry = shouldUseOpenTelemetry;
|
|
53
53
|
exports.initOpenTelemetry = initOpenTelemetry;
|
|
54
54
|
const apm_provider_type_1 = require("../types/apm-provider.type");
|
|
55
|
+
const otlp_transport_type_1 = require("../types/otlp-transport.type");
|
|
56
|
+
const debug_logger_1 = require("../utils/debug-logger");
|
|
57
|
+
const debug_exporter_wrapper_1 = require("../utils/debug-exporter-wrapper");
|
|
58
|
+
// @ts-ignore - Optional peer dependency
|
|
59
|
+
const api_1 = require("@opentelemetry/api");
|
|
60
|
+
/**
|
|
61
|
+
* Transaction Name Processor for OpenTelemetry
|
|
62
|
+
* Sets transaction names to format: "GET /api/healthcheck/ping"
|
|
63
|
+
*/
|
|
64
|
+
class TransactionNameProcessor {
|
|
65
|
+
onStart(_span) {
|
|
66
|
+
// No-op - moved to onEnd to read final http.route
|
|
67
|
+
}
|
|
68
|
+
forceFlush() {
|
|
69
|
+
return Promise.resolve();
|
|
70
|
+
}
|
|
71
|
+
shutdown() {
|
|
72
|
+
return Promise.resolve();
|
|
73
|
+
}
|
|
74
|
+
onEnd(_span) {
|
|
75
|
+
// Only process root spans (SERVER spans without parent)
|
|
76
|
+
if (_span.parent || _span.kind !== api_1.SpanKind.SERVER) {
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
// Get HTTP attributes (http.route is often set after routing completes)
|
|
80
|
+
const method = _span.attributes?.['http.method'];
|
|
81
|
+
const route = _span.attributes?.['http.route']; // /api/healthcheck/ping
|
|
82
|
+
const target = _span.attributes?.['http.target']; // fallback
|
|
83
|
+
if (method && (route || target)) {
|
|
84
|
+
// Format: GET /api/healthcheck/ping
|
|
85
|
+
const fullName = `${method} ${route || target}`;
|
|
86
|
+
_span.updateName(fullName);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
55
90
|
/**
|
|
56
91
|
* Check if APM should use OpenTelemetry provider based on configuration
|
|
57
92
|
* @returns true if OpenTelemetry should be used
|
|
@@ -60,6 +95,38 @@ function shouldUseOpenTelemetry() {
|
|
|
60
95
|
const provider = process.env.APM_PROVIDER || apm_provider_type_1.ApmProvider.OPENTELEMETRY;
|
|
61
96
|
return provider !== apm_provider_type_1.ApmProvider.ELASTIC_APM && provider !== 'elastic-apm';
|
|
62
97
|
}
|
|
98
|
+
/**
|
|
99
|
+
* Normalize OTLP endpoint URL to ensure consistent format
|
|
100
|
+
* - Ensure port is included
|
|
101
|
+
* - Remove trailing slashes
|
|
102
|
+
* - For gRPC, don't add /v1/traces (it uses different path)
|
|
103
|
+
*/
|
|
104
|
+
function normalizeEndpoint(endpoint, isGrpc) {
|
|
105
|
+
try {
|
|
106
|
+
const url = new URL(endpoint);
|
|
107
|
+
// Ensure port is specified
|
|
108
|
+
if (!url.port) {
|
|
109
|
+
const defaultPort = url.protocol === 'https:' ? '443' : '80';
|
|
110
|
+
url.port = defaultPort;
|
|
111
|
+
}
|
|
112
|
+
// Remove trailing slash
|
|
113
|
+
let pathname = url.pathname.replace(/\/+$/, '');
|
|
114
|
+
// For gRPC, remove /v1/traces as it uses different path
|
|
115
|
+
if (isGrpc && pathname.endsWith('/v1/traces')) {
|
|
116
|
+
pathname = pathname.replace('/v1/traces', '');
|
|
117
|
+
}
|
|
118
|
+
// For HTTP/PROTO, ensure /v1/traces path exists
|
|
119
|
+
if (!isGrpc && !pathname.endsWith('/v1/traces')) {
|
|
120
|
+
pathname = pathname.endsWith('/') ? pathname + 'v1/traces' : pathname + '/v1/traces';
|
|
121
|
+
}
|
|
122
|
+
url.pathname = pathname;
|
|
123
|
+
return url.toString();
|
|
124
|
+
}
|
|
125
|
+
catch (error) {
|
|
126
|
+
// If URL parsing fails, return as-is
|
|
127
|
+
return endpoint;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
63
130
|
/**
|
|
64
131
|
* Initialize OpenTelemetry SDK for standalone usage
|
|
65
132
|
*
|
|
@@ -71,12 +138,16 @@ async function initOpenTelemetry(options = {}) {
|
|
|
71
138
|
const { NodeSDK } = await Promise.resolve().then(() => __importStar(require('@opentelemetry/sdk-node')));
|
|
72
139
|
// @ts-ignore - Optional peer dependency
|
|
73
140
|
const { resourceFromAttributes } = await Promise.resolve().then(() => __importStar(require('@opentelemetry/resources')));
|
|
74
|
-
// @ts-ignore - Optional peer dependency
|
|
75
|
-
const { OTLPTraceExporter } = await Promise.resolve().then(() => __importStar(require('@opentelemetry/exporter-trace-otlp-http')));
|
|
76
|
-
console.log('[OpenTelemetry] Initializing...');
|
|
77
141
|
const serviceName = options.serviceName || process.env.ELASTIC_APM_SERVICE_NAME || 'interactive-backend';
|
|
78
142
|
const environment = options.environment || process.env.ELASTIC_APM_ENVIRONMENT || 'development';
|
|
143
|
+
// Determine transport type (HTTP, gRPC, or PROTO)
|
|
144
|
+
const transportType = (options.otlpTransport || process.env.ELASTIC_OTLP_TRANSPORT || otlp_transport_type_1.OtlpTransport.HTTP).toLowerCase();
|
|
145
|
+
const isGrpc = transportType === otlp_transport_type_1.OtlpTransport.GRPC || transportType === 'grpc';
|
|
146
|
+
const isProto = transportType === otlp_transport_type_1.OtlpTransport.PROTO || transportType === 'proto';
|
|
147
|
+
// Get endpoint from config or env (same endpoint for both transports)
|
|
79
148
|
const otlpEndpoint = options.otlpEndpoint || process.env.ELASTIC_OTLP_ENDPOINT || 'http://localhost:8200/v1/traces';
|
|
149
|
+
// Validate and normalize endpoint
|
|
150
|
+
const normalizedEndpoint = normalizeEndpoint(otlpEndpoint, isGrpc);
|
|
80
151
|
const secretToken = options.secretToken || process.env.ELASTIC_APM_SECRET_TOKEN;
|
|
81
152
|
const otlpAuthToken = options.otlpAuthToken || process.env.ELASTIC_OTLP_AUTH_TOKEN;
|
|
82
153
|
// Build headers: merge options.otlpHeaders with Authorization if token is provided
|
|
@@ -85,29 +156,49 @@ async function initOpenTelemetry(options = {}) {
|
|
|
85
156
|
if (token) {
|
|
86
157
|
headers['Authorization'] = `Bearer ${token}`;
|
|
87
158
|
}
|
|
88
|
-
|
|
89
|
-
|
|
159
|
+
// Log initialization details when debug mode is on
|
|
160
|
+
(0, debug_logger_1.logInitialization)('OpenTelemetry', {
|
|
161
|
+
serviceName,
|
|
162
|
+
environment,
|
|
163
|
+
otlpEndpoint: normalizedEndpoint,
|
|
164
|
+
transport: isGrpc ? 'gRPC' : isProto ? 'PROTO (protobuf over HTTP)' : 'HTTP',
|
|
165
|
+
hasToken: !!token,
|
|
166
|
+
enableConsoleExporter: options.enableConsoleExporter ?? process.env.ELASTIC_OTLP_ENABLE_CONSOLE_EXPORTER === 'true',
|
|
167
|
+
enableHttpInstrumentation: options.enableHttpInstrumentation !== false,
|
|
168
|
+
enableExpressInstrumentation: options.enableExpressInstrumentation !== false,
|
|
169
|
+
});
|
|
170
|
+
const transportLabel = isGrpc ? 'gRPC' : isProto ? 'PROTO' : 'HTTP';
|
|
171
|
+
(0, debug_logger_1.infoLog)(`[OpenTelemetry] Initializing with ${transportLabel} transport...`);
|
|
90
172
|
// Build exporters array
|
|
91
173
|
const exporters = [];
|
|
92
|
-
// OTLP
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
174
|
+
// Create OTLP exporter based on transport type
|
|
175
|
+
let otlpExporterBase;
|
|
176
|
+
if (isGrpc) {
|
|
177
|
+
// @ts-ignore - Optional peer dependency
|
|
178
|
+
const { OTLPTraceExporter } = await Promise.resolve().then(() => __importStar(require('@opentelemetry/exporter-trace-otlp-grpc')));
|
|
179
|
+
otlpExporterBase = new OTLPTraceExporter({
|
|
180
|
+
url: normalizedEndpoint,
|
|
181
|
+
headers: Object.keys(headers).length > 0 ? headers : undefined,
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
else if (isProto) {
|
|
185
|
+
// @ts-ignore - Optional peer dependency
|
|
186
|
+
const { OTLPTraceExporter } = await Promise.resolve().then(() => __importStar(require('@opentelemetry/exporter-trace-otlp-proto')));
|
|
187
|
+
otlpExporterBase = new OTLPTraceExporter({
|
|
188
|
+
url: normalizedEndpoint,
|
|
189
|
+
headers: Object.keys(headers).length > 0 ? headers : undefined,
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
else {
|
|
193
|
+
// @ts-ignore - Optional peer dependency
|
|
194
|
+
const { OTLPTraceExporter } = await Promise.resolve().then(() => __importStar(require('@opentelemetry/exporter-trace-otlp-http')));
|
|
195
|
+
otlpExporterBase = new OTLPTraceExporter({
|
|
196
|
+
url: normalizedEndpoint,
|
|
197
|
+
headers: Object.keys(headers).length > 0 ? headers : undefined,
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
// Wrap with debug exporter for detailed request/response logging
|
|
201
|
+
const otlpExporter = (0, debug_exporter_wrapper_1.createDebugExporter)(otlpExporterBase, normalizedEndpoint);
|
|
111
202
|
exporters.push(otlpExporter);
|
|
112
203
|
// Console Exporter for debugging
|
|
113
204
|
if (options.enableConsoleExporter ?? process.env.ELASTIC_OTLP_ENABLE_CONSOLE_EXPORTER === 'true') {
|
|
@@ -141,10 +232,16 @@ async function initOpenTelemetry(options = {}) {
|
|
|
141
232
|
traceExporter: exporters.length === 1 ? exporters[0] : exporters, // Handle single/multiple exporters
|
|
142
233
|
instrumentations,
|
|
143
234
|
resource: sdkResource,
|
|
235
|
+
spanProcessor: new TransactionNameProcessor(),
|
|
144
236
|
});
|
|
145
237
|
// Start SDK
|
|
146
238
|
sdk.start();
|
|
147
|
-
|
|
239
|
+
// Install HTTP interceptor for debug mode
|
|
240
|
+
const interceptor = (0, debug_exporter_wrapper_1.createHttpRequestInterceptor)();
|
|
241
|
+
if (interceptor) {
|
|
242
|
+
interceptor.install();
|
|
243
|
+
}
|
|
244
|
+
(0, debug_logger_1.infoLog)('[OpenTelemetry] Initialized');
|
|
148
245
|
// Setup graceful shutdown handlers
|
|
149
246
|
const shutdown = async () => {
|
|
150
247
|
await sdk.shutdown();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"elastic-apm.tracing-provider.d.ts","sourceRoot":"","sources":["../../src/providers/elastic-apm.tracing-provider.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,0CAA0C,CAAC;
|
|
1
|
+
{"version":3,"file":"elastic-apm.tracing-provider.d.ts","sourceRoot":"","sources":["../../src/providers/elastic-apm.tracing-provider.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,0CAA0C,CAAC;AAG5E;;;GAGG;AACH,qBAAa,yBAA0B,YAAW,gBAAgB;IAChE,OAAO,CAAC,GAAG,CAAa;IACxB,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;IACrC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;gBAEzB,MAAM,GAAE;QAAE,WAAW,CAAC,EAAE,MAAM,CAAC;QAAC,WAAW,CAAC,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAC;QAAC,WAAW,CAAC,EAAE,MAAM,CAAC;QAAC,cAAc,CAAC,EAAE,MAAM,CAAA;KAAO;IAK1I;;;OAGG;IACH,UAAU,CAAC,MAAM,CAAC,EAAE;QAAE,WAAW,CAAC,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAC;QAAC,WAAW,CAAC,EAAE,MAAM,CAAC;QAAC,WAAW,CAAC,EAAE,MAAM,CAAC;QAAC,cAAc,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI;IAgD5I;;OAEG;IACH,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,EAAE,QAAQ,GAAE,MAAmB,GAAG,GAAG;IAyDzG;;OAEG;IACG,mBAAmB,CAAC,CAAC,EACzB,IAAI,EAAE,MAAM,EACZ,EAAE,EAAE,CAAC,IAAI,EAAE,GAAG,KAAK,OAAO,CAAC,CAAC,CAAC,EAC7B,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,GAC3C,OAAO,CAAC,CAAC,CAAC;IAsBb;;OAEG;IACH,YAAY,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI;IAMhC;;OAEG;IACH,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI;IAOvD;;OAEG;IACH,OAAO,CAAC,IAAI,CAAC,EAAE,GAAG,GAAG,IAAI;IAOzB;;OAEG;IACG,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;IAc/B;;OAEG;IACH,OAAO,CAAC,iBAAiB;CAe1B"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.ElasticApmTracingProvider = void 0;
|
|
4
|
+
const debug_logger_1 = require("../utils/debug-logger");
|
|
4
5
|
/**
|
|
5
6
|
* Elastic APM Tracing Provider Implementation
|
|
6
7
|
* Sử dụng elastic-apm-node agent để gửi traces về Elastic APM server
|
|
@@ -22,22 +23,39 @@ class ElasticApmTracingProvider {
|
|
|
22
23
|
// If agent is already started, just get reference
|
|
23
24
|
if (apm.isStarted()) {
|
|
24
25
|
this.apm = apm;
|
|
26
|
+
if ((0, debug_logger_1.isDebugEnabled)()) {
|
|
27
|
+
console.log(`[APM-DEBUG] [ElasticAPM] Agent already started, using existing instance`);
|
|
28
|
+
}
|
|
25
29
|
return;
|
|
26
30
|
}
|
|
31
|
+
const serverUrl = config?.serverUrl || process.env.ELASTIC_APM_SERVER_URL || 'http://localhost:8200';
|
|
32
|
+
const secretToken = config?.secretToken || process.env.ELASTIC_APM_SECRET_TOKEN;
|
|
33
|
+
const serviceVersion = config?.serviceVersion || process.env.npm_package_version || '1.0.0';
|
|
34
|
+
const environment = config?.environment || this.environment;
|
|
35
|
+
// Log initialization details when debug mode is on
|
|
36
|
+
(0, debug_logger_1.logInitialization)('ElasticAPM', {
|
|
37
|
+
serviceName: config?.serviceName || this.serviceName,
|
|
38
|
+
serverUrl,
|
|
39
|
+
hasSecretToken: !!secretToken,
|
|
40
|
+
serviceVersion,
|
|
41
|
+
environment,
|
|
42
|
+
});
|
|
27
43
|
// Start the agent with provided config
|
|
28
44
|
this.apm = apm.start({
|
|
29
45
|
serviceName: config?.serviceName || this.serviceName,
|
|
30
|
-
serverUrl
|
|
31
|
-
secretToken
|
|
32
|
-
serviceVersion
|
|
33
|
-
environment
|
|
34
|
-
logLevel: 'error',
|
|
46
|
+
serverUrl,
|
|
47
|
+
secretToken,
|
|
48
|
+
serviceVersion,
|
|
49
|
+
environment,
|
|
50
|
+
logLevel: (0, debug_logger_1.isDebugEnabled)() ? 'info' : 'error',
|
|
35
51
|
});
|
|
36
|
-
|
|
37
|
-
|
|
52
|
+
if ((0, debug_logger_1.isDebugEnabled)()) {
|
|
53
|
+
(0, debug_logger_1.infoLog)(`[ElasticAPM] Service: ${this.serviceName}, Environment: ${this.environment}`);
|
|
54
|
+
(0, debug_logger_1.infoLog)('[ElasticAPM] Initialized');
|
|
55
|
+
}
|
|
38
56
|
}
|
|
39
57
|
catch (error) {
|
|
40
|
-
|
|
58
|
+
(0, debug_logger_1.errorLog)('[ElasticAPM] Failed to initialize:', error);
|
|
41
59
|
throw error;
|
|
42
60
|
}
|
|
43
61
|
}
|
|
@@ -46,7 +64,9 @@ class ElasticApmTracingProvider {
|
|
|
46
64
|
*/
|
|
47
65
|
startSpan(name, attributes, spanKind = 'INTERNAL') {
|
|
48
66
|
if (!this.apm) {
|
|
49
|
-
|
|
67
|
+
if ((0, debug_logger_1.isDebugEnabled)()) {
|
|
68
|
+
console.warn('[ElasticAPM] Not initialized');
|
|
69
|
+
}
|
|
50
70
|
return null;
|
|
51
71
|
}
|
|
52
72
|
// Elastic APM cần transaction để tạo span
|
|
@@ -75,10 +95,13 @@ class ElasticApmTracingProvider {
|
|
|
75
95
|
}
|
|
76
96
|
// Set span type dựa trên kind
|
|
77
97
|
span.setType(this.mapSpanKindToType(spanKind));
|
|
98
|
+
// Log span details when debug mode is on
|
|
99
|
+
(0, debug_logger_1.logSpan)('ElasticAPM', span);
|
|
78
100
|
// Override end() để end transaction sau
|
|
79
101
|
const originalEnd = span.end.bind(span);
|
|
80
102
|
span.end = () => {
|
|
81
103
|
originalEnd();
|
|
104
|
+
// End transaction if we created it
|
|
82
105
|
if (createdTransaction && transaction) {
|
|
83
106
|
transaction.end();
|
|
84
107
|
}
|
|
@@ -99,6 +122,9 @@ class ElasticApmTracingProvider {
|
|
|
99
122
|
if (span) {
|
|
100
123
|
span.outcome = 'failure';
|
|
101
124
|
}
|
|
125
|
+
if ((0, debug_logger_1.isDebugEnabled)()) {
|
|
126
|
+
(0, debug_logger_1.infoLog)(`[ElasticAPM] Span failed: ${name} ${error.message || error}`);
|
|
127
|
+
}
|
|
102
128
|
throw error;
|
|
103
129
|
}
|
|
104
130
|
finally {
|
|
@@ -138,7 +164,16 @@ class ElasticApmTracingProvider {
|
|
|
138
164
|
*/
|
|
139
165
|
async shutdown() {
|
|
140
166
|
if (this.apm) {
|
|
141
|
-
|
|
167
|
+
try {
|
|
168
|
+
await this.apm.flush();
|
|
169
|
+
if ((0, debug_logger_1.isDebugEnabled)()) {
|
|
170
|
+
(0, debug_logger_1.infoLog)('[ElasticAPM] Shutdown completed successfully');
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
catch (error) {
|
|
174
|
+
(0, debug_logger_1.errorLog)('[ElasticAPM] Shutdown failed:', error);
|
|
175
|
+
throw error;
|
|
176
|
+
}
|
|
142
177
|
}
|
|
143
178
|
}
|
|
144
179
|
/**
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { ITracingProvider } from '../interfaces/tracing-provider.interface';
|
|
2
|
+
import { OtlpTransport } from '../types/otlp-transport.type';
|
|
2
3
|
/**
|
|
3
4
|
* OpenTelemetry Tracing Provider Implementation
|
|
4
5
|
* Sử dụng OpenTelemetry SDK với OTLP exporter để gửi traces về Elastic APM
|
|
@@ -9,6 +10,7 @@ export declare class OpenTelemetryTracingProvider implements ITracingProvider {
|
|
|
9
10
|
private readonly serviceName;
|
|
10
11
|
private readonly environment;
|
|
11
12
|
private readonly otlpEndpoint;
|
|
13
|
+
private readonly otlpTransport;
|
|
12
14
|
private readonly secretToken;
|
|
13
15
|
private readonly otlpAuthToken;
|
|
14
16
|
private readonly otlpHeaders;
|
|
@@ -18,6 +20,7 @@ export declare class OpenTelemetryTracingProvider implements ITracingProvider {
|
|
|
18
20
|
serviceName?: string;
|
|
19
21
|
environment?: string;
|
|
20
22
|
otlpEndpoint?: string;
|
|
23
|
+
otlpTransport?: OtlpTransport | string;
|
|
21
24
|
secretToken?: string;
|
|
22
25
|
otlpAuthToken?: string;
|
|
23
26
|
otlpHeaders?: Record<string, string>;
|
|
@@ -52,6 +55,10 @@ export declare class OpenTelemetryTracingProvider implements ITracingProvider {
|
|
|
52
55
|
* Set attribute cho active span
|
|
53
56
|
*/
|
|
54
57
|
setAttribute(key: string, value: string | number): void;
|
|
58
|
+
/**
|
|
59
|
+
* Force flush all pending spans
|
|
60
|
+
*/
|
|
61
|
+
forceFlush(): Promise<void>;
|
|
55
62
|
/**
|
|
56
63
|
* End span manually
|
|
57
64
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"opentelemetry.tracing-provider.d.ts","sourceRoot":"","sources":["../../src/providers/opentelemetry.tracing-provider.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,0CAA0C,CAAC;
|
|
1
|
+
{"version":3,"file":"opentelemetry.tracing-provider.d.ts","sourceRoot":"","sources":["../../src/providers/opentelemetry.tracing-provider.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,0CAA0C,CAAC;AAC5E,OAAO,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAC;AA2F7D;;;GAGG;AACH,qBAAa,4BAA6B,YAAW,gBAAgB;IACnE,OAAO,CAAC,GAAG,CAAa;IACxB,OAAO,CAAC,MAAM,CAAa;IAC3B,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;IACrC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;IACrC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAS;IACtC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAgB;IAC9C,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;IACrC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAS;IACvC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAyB;IACrD,OAAO,CAAC,QAAQ,CAAC,qBAAqB,CAAU;IAChD,OAAO,CAAC,WAAW,CAAS;gBAEhB,MAAM,GAAE;QAClB,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,YAAY,CAAC,EAAE,MAAM,CAAC;QACtB,aAAa,CAAC,EAAE,aAAa,GAAG,MAAM,CAAC;QACvC,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,aAAa,CAAC,EAAE,MAAM,CAAC;QACvB,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QACrC,qBAAqB,CAAC,EAAE,OAAO,CAAC;KAC5B;IAqBN;;;OAGG;IACG,UAAU,CAAC,MAAM,CAAC,EAAE;QACxB,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,YAAY,CAAC,EAAE,MAAM,CAAC;QACtB,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,aAAa,CAAC,EAAE,MAAM,CAAC;QACvB,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QACrC,qBAAqB,CAAC,EAAE,OAAO,CAAC;QAChC,WAAW,CAAC,EAAE,MAAM,CAAC;KACtB,GAAG,OAAO,CAAC,IAAI,CAAC;IAqMjB;;OAEG;IACH,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,EAAE,QAAQ,GAAE,MAAmB,GAAG,GAAG;IA0BzG;;OAEG;IACG,mBAAmB,CAAC,CAAC,EACzB,IAAI,EAAE,MAAM,EACZ,EAAE,EAAE,CAAC,IAAI,EAAE,GAAG,KAAK,OAAO,CAAC,CAAC,CAAC,EAC7B,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,GAC3C,OAAO,CAAC,CAAC,CAAC;IAwBb;;OAEG;IACH,YAAY,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI;IAchC;;OAEG;IACH,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI;IAavD;;OAEG;IACG,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAajC;;OAEG;IACH,OAAO,CAAC,IAAI,CAAC,EAAE,GAAG,GAAG,IAAI;IAazB;;OAEG;IACG,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;IAM/B;;OAEG;IACH,OAAO,CAAC,WAAW;CAiBpB"}
|
|
@@ -34,6 +34,85 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.OpenTelemetryTracingProvider = void 0;
|
|
37
|
+
const otlp_transport_type_1 = require("../types/otlp-transport.type");
|
|
38
|
+
const debug_logger_1 = require("../utils/debug-logger");
|
|
39
|
+
const debug_exporter_wrapper_1 = require("../utils/debug-exporter-wrapper");
|
|
40
|
+
/**
|
|
41
|
+
* Transaction Name Processor for OpenTelemetry
|
|
42
|
+
* Sets transaction names to format: "GET /api/healthcheck/ping"
|
|
43
|
+
*/
|
|
44
|
+
class TransactionNameProcessor {
|
|
45
|
+
constructor() {
|
|
46
|
+
this.cachedSpanKind = null;
|
|
47
|
+
}
|
|
48
|
+
onStart(_span) {
|
|
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) {
|
|
58
|
+
// Lazy load SpanKind to avoid top-level import failure when @opentelemetry/api is absent
|
|
59
|
+
if (!this.cachedSpanKind) {
|
|
60
|
+
try {
|
|
61
|
+
// @ts-ignore - Optional peer dependency
|
|
62
|
+
this.cachedSpanKind = require('@opentelemetry/api').SpanKind;
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
// If @opentelemetry/api is not available, skip processing
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
// Only process root spans (SERVER spans without parent)
|
|
70
|
+
if (_span.parent || _span.kind !== this.cachedSpanKind.SERVER) {
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
// Get HTTP attributes (http.route is often set after routing completes)
|
|
74
|
+
const method = _span.attributes?.['http.method'];
|
|
75
|
+
const route = _span.attributes?.['http.route']; // /api/healthcheck/ping
|
|
76
|
+
const target = _span.attributes?.['http.target']; // fallback
|
|
77
|
+
if (method && (route || target)) {
|
|
78
|
+
// Format: GET /api/healthcheck/ping
|
|
79
|
+
const fullName = `${method} ${route || target}`;
|
|
80
|
+
_span.updateName(fullName);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Normalize OTLP endpoint URL to ensure consistent format
|
|
86
|
+
* - Ensure port is included
|
|
87
|
+
* - Remove trailing slashes
|
|
88
|
+
* - For gRPC, don't add /v1/traces (it uses different path)
|
|
89
|
+
*/
|
|
90
|
+
function normalizeEndpoint(endpoint, isGrpc) {
|
|
91
|
+
try {
|
|
92
|
+
const url = new URL(endpoint);
|
|
93
|
+
// Ensure port is specified
|
|
94
|
+
if (!url.port) {
|
|
95
|
+
const defaultPort = url.protocol === 'https:' ? '443' : '80';
|
|
96
|
+
url.port = defaultPort;
|
|
97
|
+
}
|
|
98
|
+
// Remove trailing slash
|
|
99
|
+
let pathname = url.pathname.replace(/\/+$/, '');
|
|
100
|
+
// For gRPC, remove /v1/traces as it uses different path
|
|
101
|
+
if (isGrpc && pathname.endsWith('/v1/traces')) {
|
|
102
|
+
pathname = pathname.replace('/v1/traces', '');
|
|
103
|
+
}
|
|
104
|
+
// For HTTP/PROTO, ensure /v1/traces path exists
|
|
105
|
+
if (!isGrpc && !pathname.endsWith('/v1/traces')) {
|
|
106
|
+
pathname = pathname + '/v1/traces';
|
|
107
|
+
}
|
|
108
|
+
url.pathname = pathname;
|
|
109
|
+
return url.toString();
|
|
110
|
+
}
|
|
111
|
+
catch (error) {
|
|
112
|
+
// If URL parsing fails, return as-is
|
|
113
|
+
return endpoint;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
37
116
|
/**
|
|
38
117
|
* OpenTelemetry Tracing Provider Implementation
|
|
39
118
|
* Sử dụng OpenTelemetry SDK với OTLP exporter để gửi traces về Elastic APM
|
|
@@ -46,6 +125,17 @@ class OpenTelemetryTracingProvider {
|
|
|
46
125
|
this.serviceName = config.serviceName || process.env.ELASTIC_APM_SERVICE_NAME || 'interactive-backend';
|
|
47
126
|
this.environment = config.environment || process.env.ELASTIC_APM_ENVIRONMENT || 'development';
|
|
48
127
|
this.otlpEndpoint = config.otlpEndpoint || process.env.ELASTIC_OTLP_ENDPOINT || 'http://localhost:8200/v1/traces';
|
|
128
|
+
// Determine transport type (HTTP, gRPC, or PROTO)
|
|
129
|
+
const transportType = (config.otlpTransport || process.env.ELASTIC_OTLP_TRANSPORT || otlp_transport_type_1.OtlpTransport.HTTP).toLowerCase();
|
|
130
|
+
if (transportType === otlp_transport_type_1.OtlpTransport.GRPC || transportType === 'grpc') {
|
|
131
|
+
this.otlpTransport = otlp_transport_type_1.OtlpTransport.GRPC;
|
|
132
|
+
}
|
|
133
|
+
else if (transportType === otlp_transport_type_1.OtlpTransport.PROTO || transportType === 'proto') {
|
|
134
|
+
this.otlpTransport = otlp_transport_type_1.OtlpTransport.PROTO;
|
|
135
|
+
}
|
|
136
|
+
else {
|
|
137
|
+
this.otlpTransport = otlp_transport_type_1.OtlpTransport.HTTP;
|
|
138
|
+
}
|
|
49
139
|
this.secretToken = config.secretToken || process.env.ELASTIC_APM_SECRET_TOKEN || '';
|
|
50
140
|
this.otlpAuthToken = config.otlpAuthToken || process.env.ELASTIC_OTLP_AUTH_TOKEN || '';
|
|
51
141
|
this.otlpHeaders = config.otlpHeaders || {};
|
|
@@ -68,13 +158,28 @@ class OpenTelemetryTracingProvider {
|
|
|
68
158
|
// @ts-ignore - Optional peer dependency
|
|
69
159
|
const { ExpressInstrumentation } = await Promise.resolve().then(() => __importStar(require('@opentelemetry/instrumentation-express')));
|
|
70
160
|
// @ts-ignore - Optional peer dependency
|
|
71
|
-
const { OTLPTraceExporter } = await Promise.resolve().then(() => __importStar(require('@opentelemetry/exporter-trace-otlp-http')));
|
|
72
|
-
// @ts-ignore - Optional peer dependency
|
|
73
161
|
const { resourceFromAttributes } = await Promise.resolve().then(() => __importStar(require('@opentelemetry/resources')));
|
|
74
162
|
// @ts-ignore - Optional peer dependency
|
|
75
163
|
const { trace } = await Promise.resolve().then(() => __importStar(require('@opentelemetry/api')));
|
|
76
|
-
|
|
77
|
-
|
|
164
|
+
const endpoint = config?.otlpEndpoint || this.otlpEndpoint;
|
|
165
|
+
const isGrpc = this.otlpTransport === otlp_transport_type_1.OtlpTransport.GRPC;
|
|
166
|
+
const isProto = this.otlpTransport === otlp_transport_type_1.OtlpTransport.PROTO;
|
|
167
|
+
// Normalize endpoint to ensure consistent format
|
|
168
|
+
const normalizedEndpoint = normalizeEndpoint(endpoint, isGrpc);
|
|
169
|
+
// Log initialization details when debug mode is on
|
|
170
|
+
(0, debug_logger_1.logInitialization)('OpenTelemetry', {
|
|
171
|
+
serviceName: config?.serviceName || this.serviceName,
|
|
172
|
+
environment: config?.environment || this.environment,
|
|
173
|
+
otlpEndpoint: normalizedEndpoint,
|
|
174
|
+
transport: isGrpc ? 'gRPC' : isProto ? 'PROTO (protobuf over HTTP)' : 'HTTP',
|
|
175
|
+
hasToken: !!(config?.otlpAuthToken || this.otlpAuthToken || config?.secretToken || this.secretToken),
|
|
176
|
+
enableConsoleExporter: config?.enableConsoleExporter ?? this.enableConsoleExporter,
|
|
177
|
+
});
|
|
178
|
+
const transportLabel = isGrpc ? 'gRPC' : isProto ? 'PROTO' : 'HTTP';
|
|
179
|
+
if ((0, debug_logger_1.isDebugEnabled)()) {
|
|
180
|
+
(0, debug_logger_1.infoLog)(`[OpenTelemetry] Service: ${this.serviceName}, Environment: ${this.environment}`);
|
|
181
|
+
(0, debug_logger_1.infoLog)(`[OpenTelemetry] Endpoint: ${normalizedEndpoint} (${transportLabel})`);
|
|
182
|
+
}
|
|
78
183
|
// OTLP Exporter - Build headers with Authorization
|
|
79
184
|
const buildHeaders = () => {
|
|
80
185
|
const headers = {
|
|
@@ -87,25 +192,34 @@ class OpenTelemetryTracingProvider {
|
|
|
87
192
|
}
|
|
88
193
|
return Object.keys(headers).length > 0 ? headers : undefined;
|
|
89
194
|
};
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
195
|
+
// Import the correct exporter based on transport type
|
|
196
|
+
let otlpExporter;
|
|
197
|
+
if (isGrpc) {
|
|
198
|
+
// @ts-ignore - Optional peer dependency
|
|
199
|
+
const { OTLPTraceExporter } = await Promise.resolve().then(() => __importStar(require('@opentelemetry/exporter-trace-otlp-grpc')));
|
|
200
|
+
otlpExporter = new OTLPTraceExporter({
|
|
201
|
+
url: normalizedEndpoint,
|
|
202
|
+
headers: buildHeaders(),
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
else if (isProto) {
|
|
206
|
+
// @ts-ignore - Optional peer dependency
|
|
207
|
+
const { OTLPTraceExporter } = await Promise.resolve().then(() => __importStar(require('@opentelemetry/exporter-trace-otlp-proto')));
|
|
208
|
+
otlpExporter = new OTLPTraceExporter({
|
|
209
|
+
url: normalizedEndpoint,
|
|
210
|
+
headers: buildHeaders(),
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
else {
|
|
214
|
+
// @ts-ignore - Optional peer dependency
|
|
215
|
+
const { OTLPTraceExporter } = await Promise.resolve().then(() => __importStar(require('@opentelemetry/exporter-trace-otlp-http')));
|
|
216
|
+
otlpExporter = new OTLPTraceExporter({
|
|
217
|
+
url: normalizedEndpoint,
|
|
218
|
+
headers: buildHeaders(),
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
// Wrap with debug exporter for detailed request/response logging
|
|
222
|
+
const otlpExporterWithLogging = (0, debug_exporter_wrapper_1.createDebugExporter)(otlpExporter, normalizedEndpoint);
|
|
109
223
|
// Use console exporter if enabled
|
|
110
224
|
const shouldEnableConsole = config?.enableConsoleExporter ?? this.enableConsoleExporter;
|
|
111
225
|
let traceExporter = otlpExporterWithLogging;
|
|
@@ -156,22 +270,53 @@ class OpenTelemetryTracingProvider {
|
|
|
156
270
|
'service.instance.id': `${process.pid}`,
|
|
157
271
|
'host.name': require('os').hostname(),
|
|
158
272
|
});
|
|
273
|
+
// Use BatchSpanProcessor for efficient batching with explicit flush
|
|
274
|
+
const { BatchSpanProcessor } = await Promise.resolve().then(() => __importStar(require('@opentelemetry/sdk-trace-base')));
|
|
275
|
+
// Create a custom processor that extends BatchSpanProcessor behavior
|
|
276
|
+
// but also applies transaction naming
|
|
277
|
+
const transactionNameProcessor = new TransactionNameProcessor();
|
|
278
|
+
const batchProcessor = new BatchSpanProcessor(traceExporter);
|
|
279
|
+
// Wrap the batch processor to also do transaction naming
|
|
280
|
+
const processorWithNaming = {
|
|
281
|
+
onStart: (span, context) => {
|
|
282
|
+
transactionNameProcessor.onStart(span);
|
|
283
|
+
batchProcessor.onStart(span, context);
|
|
284
|
+
},
|
|
285
|
+
onEnd: (span) => {
|
|
286
|
+
transactionNameProcessor.onEnd(span);
|
|
287
|
+
batchProcessor.onEnd(span);
|
|
288
|
+
},
|
|
289
|
+
forceFlush: async () => {
|
|
290
|
+
await batchProcessor.forceFlush();
|
|
291
|
+
},
|
|
292
|
+
shutdown: async () => {
|
|
293
|
+
await batchProcessor.shutdown();
|
|
294
|
+
},
|
|
295
|
+
};
|
|
159
296
|
// Khởi tạo SDK
|
|
160
297
|
this.sdk = new NodeSDK({
|
|
161
298
|
serviceName: config?.serviceName || this.serviceName,
|
|
162
|
-
traceExporter,
|
|
299
|
+
traceExporter: undefined, // Don't use traceExporter when using spanProcessor
|
|
163
300
|
instrumentations: [new HttpInstrumentation(), new ExpressInstrumentation()],
|
|
164
301
|
resource: sdkResource,
|
|
302
|
+
spanProcessor: processorWithNaming,
|
|
165
303
|
});
|
|
166
304
|
// Khởi động SDK
|
|
167
305
|
this.sdk.start();
|
|
306
|
+
// Install HTTP interceptor for debug mode
|
|
307
|
+
const interceptor = (0, debug_exporter_wrapper_1.createHttpRequestInterceptor)();
|
|
308
|
+
if (interceptor) {
|
|
309
|
+
interceptor.install();
|
|
310
|
+
}
|
|
168
311
|
// Get tracer
|
|
169
312
|
this.tracer = trace.getTracer(config?.serviceName || this.serviceName);
|
|
170
313
|
this.initialized = true;
|
|
171
|
-
|
|
314
|
+
if ((0, debug_logger_1.isDebugEnabled)()) {
|
|
315
|
+
(0, debug_logger_1.infoLog)('[OpenTelemetry] Initialized');
|
|
316
|
+
}
|
|
172
317
|
}
|
|
173
318
|
catch (error) {
|
|
174
|
-
|
|
319
|
+
(0, debug_logger_1.errorLog)('[OpenTelemetry] Failed to initialize:', error);
|
|
175
320
|
throw error;
|
|
176
321
|
}
|
|
177
322
|
}
|
|
@@ -180,7 +325,9 @@ class OpenTelemetryTracingProvider {
|
|
|
180
325
|
*/
|
|
181
326
|
startSpan(name, attributes, spanKind = 'INTERNAL') {
|
|
182
327
|
if (!this.initialized || !this.tracer) {
|
|
183
|
-
|
|
328
|
+
if ((0, debug_logger_1.isDebugEnabled)()) {
|
|
329
|
+
console.warn('[OpenTelemetry] Not initialized');
|
|
330
|
+
}
|
|
184
331
|
return null;
|
|
185
332
|
}
|
|
186
333
|
// @ts-ignore - Optional peer dependency
|
|
@@ -189,6 +336,8 @@ class OpenTelemetryTracingProvider {
|
|
|
189
336
|
attributes,
|
|
190
337
|
kind: this.mapSpanKind(spanKind),
|
|
191
338
|
}, context.active());
|
|
339
|
+
// Log span details when debug mode is on
|
|
340
|
+
(0, debug_logger_1.logSpan)('OpenTelemetry', span);
|
|
192
341
|
return span;
|
|
193
342
|
}
|
|
194
343
|
/**
|
|
@@ -206,6 +355,9 @@ class OpenTelemetryTracingProvider {
|
|
|
206
355
|
catch (error) {
|
|
207
356
|
span?.recordException(error);
|
|
208
357
|
span?.setStatus({ code: SpanStatusCode.ERROR, message: error.message });
|
|
358
|
+
if ((0, debug_logger_1.isDebugEnabled)()) {
|
|
359
|
+
console.log(`[APM-DEBUG] [OpenTelemetry] Span failed:`, name, error.message || error);
|
|
360
|
+
}
|
|
209
361
|
throw error;
|
|
210
362
|
}
|
|
211
363
|
finally {
|
|
@@ -242,6 +394,22 @@ class OpenTelemetryTracingProvider {
|
|
|
242
394
|
span.setAttribute(key, value);
|
|
243
395
|
}
|
|
244
396
|
}
|
|
397
|
+
/**
|
|
398
|
+
* Force flush all pending spans
|
|
399
|
+
*/
|
|
400
|
+
async forceFlush() {
|
|
401
|
+
if (this.sdk && this.sdk.forceFlush) {
|
|
402
|
+
try {
|
|
403
|
+
await this.sdk.forceFlush();
|
|
404
|
+
if ((0, debug_logger_1.isDebugEnabled)()) {
|
|
405
|
+
console.log('[APM-DEBUG] Force flush completed');
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
catch (error) {
|
|
409
|
+
(0, debug_logger_1.errorLog)('[OpenTelemetry] Force flush failed:', error);
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
}
|
|
245
413
|
/**
|
|
246
414
|
* End span manually
|
|
247
415
|
*/
|