@vtvlive/interactive-apm 0.0.2 → 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.
Files changed (43) hide show
  1. package/README.md +201 -123
  2. package/dist/factories/tracing-provider.factory.d.ts +8 -3
  3. package/dist/factories/tracing-provider.factory.d.ts.map +1 -1
  4. package/dist/factories/tracing-provider.factory.js +17 -13
  5. package/dist/index.d.ts +12 -10
  6. package/dist/index.d.ts.map +1 -1
  7. package/dist/index.js +3 -1
  8. package/dist/init/elastic-apm-init.d.ts +25 -3
  9. package/dist/init/elastic-apm-init.d.ts.map +1 -1
  10. package/dist/init/elastic-apm-init.js +29 -12
  11. package/dist/init/opentelemetry-init.d.ts +8 -1
  12. package/dist/init/opentelemetry-init.d.ts.map +1 -1
  13. package/dist/init/opentelemetry-init.js +145 -44
  14. package/dist/interfaces/tracing-provider.interface.d.ts +13 -3
  15. package/dist/interfaces/tracing-provider.interface.d.ts.map +1 -1
  16. package/dist/modules/tracing.module.d.ts +5 -5
  17. package/dist/modules/tracing.module.d.ts.map +1 -1
  18. package/dist/modules/tracing.module.js +2 -1
  19. package/dist/providers/elastic-apm.tracing-provider.d.ts +23 -5
  20. package/dist/providers/elastic-apm.tracing-provider.d.ts.map +1 -1
  21. package/dist/providers/elastic-apm.tracing-provider.js +127 -28
  22. package/dist/providers/opentelemetry.tracing-provider.d.ts +12 -4
  23. package/dist/providers/opentelemetry.tracing-provider.d.ts.map +1 -1
  24. package/dist/providers/opentelemetry.tracing-provider.js +328 -67
  25. package/dist/services/tracing.service.d.ts +6 -5
  26. package/dist/services/tracing.service.d.ts.map +1 -1
  27. package/dist/services/tracing.service.js +2 -2
  28. package/dist/types/apm.types.d.ts +162 -0
  29. package/dist/types/apm.types.d.ts.map +1 -0
  30. package/dist/types/apm.types.js +37 -0
  31. package/dist/types/otlp-transport.type.d.ts +14 -0
  32. package/dist/types/otlp-transport.type.d.ts.map +1 -0
  33. package/dist/types/otlp-transport.type.js +17 -0
  34. package/dist/utils/debug-exporter-wrapper.d.ts +36 -0
  35. package/dist/utils/debug-exporter-wrapper.d.ts.map +1 -0
  36. package/dist/utils/debug-exporter-wrapper.js +247 -0
  37. package/dist/utils/debug-logger.d.ts +81 -0
  38. package/dist/utils/debug-logger.d.ts.map +1 -0
  39. package/dist/utils/debug-logger.js +236 -0
  40. package/dist/utils/tracing.helper.d.ts +2 -2
  41. package/dist/utils/tracing.helper.d.ts.map +1 -1
  42. package/dist/utils/tracing.helper.js +8 -4
  43. package/package.json +24 -3
@@ -0,0 +1,236 @@
1
+ "use strict";
2
+ /**
3
+ * Debug Logger for APM Package
4
+ *
5
+ * Provides centralized debug logging that only outputs when APM_DEBUG=true.
6
+ * Used by both Elastic APM and OpenTelemetry providers.
7
+ */
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.isDebugEnabled = isDebugEnabled;
10
+ exports.sanitizeHeaders = sanitizeHeaders;
11
+ exports.sanitizeConfig = sanitizeConfig;
12
+ exports.debugLog = debugLog;
13
+ exports.infoLog = infoLog;
14
+ exports.errorLog = errorLog;
15
+ exports.logInitialization = logInitialization;
16
+ exports.logExportSuccess = logExportSuccess;
17
+ exports.logExportFailure = logExportFailure;
18
+ exports.logRequest = logRequest;
19
+ exports.logResponse = logResponse;
20
+ exports.logSpan = logSpan;
21
+ /**
22
+ * Check if debug mode is enabled via environment variable
23
+ */
24
+ function isDebugEnabled() {
25
+ return process.env.APM_DEBUG === "true";
26
+ }
27
+ /**
28
+ * Mask sensitive values in headers and config for logging
29
+ */
30
+ function maskSensitive(value, key) {
31
+ const sensitiveKeys = [
32
+ "authorization",
33
+ "token",
34
+ "secret",
35
+ "password",
36
+ "apikey",
37
+ "api-key",
38
+ "x-api-key",
39
+ ];
40
+ const lowerKey = key.toLowerCase();
41
+ if (sensitiveKeys.some(k => lowerKey.includes(k))) {
42
+ return "[REDACTED]";
43
+ }
44
+ return value;
45
+ }
46
+ /**
47
+ * Sanitize headers for logging (mask sensitive values)
48
+ */
49
+ function sanitizeHeaders(headers) {
50
+ const sanitized = {};
51
+ for (const [key, value] of Object.entries(headers)) {
52
+ sanitized[key] = maskSensitive(value, key);
53
+ }
54
+ return sanitized;
55
+ }
56
+ /**
57
+ * Sanitize config object for logging (mask sensitive values)
58
+ * Handles arrays and circular references safely
59
+ */
60
+ function sanitizeConfig(config, seen = new WeakMap()) {
61
+ // Handle primitives and null
62
+ if (config === null || typeof config !== "object") {
63
+ return config;
64
+ }
65
+ // Handle circular references
66
+ if (seen.has(config)) {
67
+ return "[Circular]";
68
+ }
69
+ seen.set(config, "[Circular]");
70
+ // Handle arrays
71
+ if (Array.isArray(config)) {
72
+ return config.map(item => {
73
+ if (typeof item === "string") {
74
+ return maskSensitive(item, "");
75
+ }
76
+ else if (typeof item === "object" && item !== null) {
77
+ return sanitizeConfig(item, seen);
78
+ }
79
+ return item;
80
+ });
81
+ }
82
+ // Handle objects
83
+ const sanitized = {};
84
+ for (const [key, value] of Object.entries(config)) {
85
+ if (typeof value === "string") {
86
+ sanitized[key] = maskSensitive(value, key);
87
+ }
88
+ else if (Array.isArray(value)) {
89
+ sanitized[key] = value.map(item => {
90
+ if (typeof item === "string") {
91
+ return maskSensitive(item, key);
92
+ }
93
+ else if (typeof item === "object" && item !== null) {
94
+ return sanitizeConfig(item, seen);
95
+ }
96
+ return item;
97
+ });
98
+ }
99
+ else if (typeof value === "object" && value !== null) {
100
+ sanitized[key] = sanitizeConfig(value, seen);
101
+ }
102
+ else {
103
+ sanitized[key] = value;
104
+ }
105
+ }
106
+ return sanitized;
107
+ }
108
+ /**
109
+ * Debug log - only outputs when APM_DEBUG=true
110
+ */
111
+ function debugLog(message, ...args) {
112
+ if (isDebugEnabled()) {
113
+ console.log(`[APM-DEBUG] ${message}`, ...args);
114
+ }
115
+ }
116
+ /**
117
+ * Info log - always outputs
118
+ */
119
+ function infoLog(message, ...args) {
120
+ console.log(`[APM] ${message}`, ...args);
121
+ }
122
+ /**
123
+ * Error log - always outputs
124
+ */
125
+ function errorLog(message, ...args) {
126
+ console.error(`[APM-ERROR] ${message}`, ...args);
127
+ }
128
+ /**
129
+ * Log initialization details
130
+ */
131
+ function logInitialization(provider, config) {
132
+ if (!isDebugEnabled()) {
133
+ return;
134
+ }
135
+ debugLog(`=== ${provider} Initialization ===`);
136
+ debugLog("Configuration:", sanitizeConfig(config));
137
+ debugLog("Environment Variables:", {
138
+ APM_PROVIDER: process.env.APM_PROVIDER,
139
+ ELASTIC_APM_SERVICE_NAME: process.env.ELASTIC_APM_SERVICE_NAME,
140
+ ELASTIC_APM_ENVIRONMENT: process.env.ELASTIC_APM_ENVIRONMENT,
141
+ ELASTIC_OTLP_ENDPOINT: process.env.ELASTIC_OTLP_ENDPOINT,
142
+ ELASTIC_APM_SERVER_URL: process.env.ELASTIC_APM_SERVER_URL,
143
+ APM_DEBUG: process.env.APM_DEBUG,
144
+ });
145
+ debugLog("System Info:", {
146
+ nodeVersion: process.version,
147
+ platform: process.platform,
148
+ arch: process.arch,
149
+ pid: process.pid,
150
+ hostname: require("os").hostname(),
151
+ });
152
+ }
153
+ /**
154
+ * Log span export success (debug mode only)
155
+ */
156
+ function logExportSuccess(provider, spanCount) {
157
+ if (isDebugEnabled()) {
158
+ debugLog(`[${provider}] Exported ${spanCount} span(s)`);
159
+ }
160
+ }
161
+ /**
162
+ * Log span export failure - ALWAYS logs regardless of debug mode
163
+ */
164
+ function logExportFailure(provider, error, requestDetails) {
165
+ // Always log export failures as errors
166
+ const errorMessage = error instanceof Error ? error.message : String(error);
167
+ errorLog(`[${provider}] Export failed:`, errorMessage);
168
+ // Log additional details only in debug mode
169
+ if (isDebugEnabled() && requestDetails) {
170
+ debugLog("Request details:", sanitizeConfig(requestDetails));
171
+ }
172
+ }
173
+ /**
174
+ * Log outgoing HTTP request (for debugging APM communication)
175
+ */
176
+ function logRequest(provider, url, headers, body) {
177
+ if (!isDebugEnabled()) {
178
+ return;
179
+ }
180
+ debugLog(`[${provider}] → Sending request to: ${url}`);
181
+ debugLog("Headers:", sanitizeHeaders(headers));
182
+ // Sanitize request body for sensitive data before logging
183
+ if (body) {
184
+ // WARNING: Request bodies may contain sensitive data - we sanitize common patterns
185
+ let sanitizedBody;
186
+ if (typeof body === "string") {
187
+ // Redact common auth/token patterns in strings
188
+ sanitizedBody = body.replace(/"(password|pass|pwd|token|access_token|auth|authorization|cookie|ssn|creditCard|cardNumber|cvv|secret)"\s*:\s*"[^"]*"/gi, '"$1": "[REDACTED]"');
189
+ sanitizedBody = sanitizedBody.replace(/Bearer\s+[A-Za-z0-9\-._~+/=]*/gi, "Bearer [REDACTED]");
190
+ }
191
+ else if (typeof body === "object" && body !== null) {
192
+ // Deep clone and sanitize object
193
+ sanitizedBody = sanitizeConfig(body);
194
+ }
195
+ else {
196
+ sanitizedBody = String(body);
197
+ }
198
+ debugLog("Body:", typeof sanitizedBody === "string" ? sanitizedBody : JSON.stringify(sanitizedBody, null, 2));
199
+ }
200
+ }
201
+ /**
202
+ * Log incoming HTTP response (for debugging APM communication)
203
+ */
204
+ function logResponse(provider, statusCode, headers, body) {
205
+ if (!isDebugEnabled()) {
206
+ return;
207
+ }
208
+ debugLog(`[${provider}] ← Received response: ${statusCode}`);
209
+ debugLog("Response Headers:", sanitizeHeaders(headers));
210
+ if (body) {
211
+ debugLog("Response Body:", typeof body === "string" ? body : JSON.stringify(body, null, 2));
212
+ }
213
+ }
214
+ /**
215
+ * Log span details (when debug mode is on)
216
+ */
217
+ function logSpan(provider, span) {
218
+ if (!isDebugEnabled()) {
219
+ return;
220
+ }
221
+ try {
222
+ const spanDetails = {
223
+ name: span.name,
224
+ kind: span.kind,
225
+ startTime: span.startTime,
226
+ endTime: span.endTime,
227
+ duration: span.endTime - span.startTime,
228
+ attributes: span.attributes,
229
+ status: span.status,
230
+ };
231
+ debugLog(`[${provider}] Span details:`, JSON.stringify(spanDetails, null, 2));
232
+ }
233
+ catch (error) {
234
+ debugLog(`[${provider}] Could not serialize span:`, error);
235
+ }
236
+ }
@@ -1,4 +1,4 @@
1
- import { Span, SpanKind } from '@opentelemetry/api';
1
+ import { Span, SpanKind } from "@opentelemetry/api";
2
2
  /**
3
3
  * TracingHelper - Static utility class for direct OpenTelemetry usage
4
4
  *
@@ -37,7 +37,7 @@ export declare class TracingHelper {
37
37
  * @param attributes Các attributes metadata
38
38
  * @returns Kết quả của function
39
39
  */
40
- static startSpanWithParent<T>(name: string, fn: (span: Span) => Promise<T>, attributes?: Record<string, string | number>): Promise<T>;
40
+ static startSpanWithParent<T>(name: string, fn: (span: Span | undefined) => Promise<T>, attributes?: Record<string, string | number>): Promise<T>;
41
41
  /**
42
42
  * Capture error vào active span hiện tại
43
43
  * @param error Error object
@@ -1 +1 @@
1
- {"version":3,"file":"tracing.helper.d.ts","sourceRoot":"","sources":["../../src/utils/tracing.helper.ts"],"names":[],"mappings":"AACA,OAAO,EAAkB,IAAI,EAAkB,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAEpF;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,qBAAa,aAAa;IACxB,OAAO,CAAC,MAAM,CAAC,MAAM,CAAsC;IAE3D;;;;;;OAMG;IACH,MAAM,CAAC,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,EAAE,QAAQ,GAAE,QAA4B,GAAG,IAAI;IAa1H;;;;;;OAMG;IACH,MAAM,CAAC,mBAAmB,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,OAAO,CAAC,CAAC,CAAC,EAAE,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;IAiBrI;;;OAGG;IACH,MAAM,CAAC,YAAY,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI;IAQvC;;;;OAIG;IACH,MAAM,CAAC,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI;IAO9D;;;OAGG;IACH,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,IAAI,GAAG,IAAI;IAOjC;;;OAGG;IACH,MAAM,CAAC,aAAa,IAAI,IAAI,GAAG,SAAS;IAIxC;;;OAGG;IACH,MAAM,CAAC,UAAU,IAAI,MAAM,GAAG,SAAS;CAIxC"}
1
+ {"version":3,"file":"tracing.helper.d.ts","sourceRoot":"","sources":["../../src/utils/tracing.helper.ts"],"names":[],"mappings":"AACA,OAAO,EAAkB,IAAI,EAAkB,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAEpF;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,qBAAa,aAAa;IACxB,OAAO,CAAC,MAAM,CAAC,MAAM,CAAsC;IAE3D;;;;;;OAMG;IACH,MAAM,CAAC,SAAS,CACd,IAAI,EAAE,MAAM,EACZ,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,EAC5C,QAAQ,GAAE,QAA4B,GACrC,IAAI;IAaP;;;;;;OAMG;IACH,MAAM,CAAC,mBAAmB,CAAC,CAAC,EAC1B,IAAI,EAAE,MAAM,EACZ,EAAE,EAAE,CAAC,IAAI,EAAE,IAAI,GAAG,SAAS,KAAK,OAAO,CAAC,CAAC,CAAC,EAC1C,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,GAC3C,OAAO,CAAC,CAAC,CAAC;IA2Bb;;;OAGG;IACH,MAAM,CAAC,YAAY,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI;IAQvC;;;;OAIG;IACH,MAAM,CAAC,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI;IAO9D;;;OAGG;IACH,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,IAAI,GAAG,IAAI;IAOjC;;;OAGG;IACH,MAAM,CAAC,aAAa,IAAI,IAAI,GAAG,SAAS;IAIxC;;;OAGG;IACH,MAAM,CAAC,UAAU,IAAI,MAAM,GAAG,SAAS;CAIxC"}
@@ -49,17 +49,21 @@ class TracingHelper {
49
49
  static startSpanWithParent(name, fn, attributes) {
50
50
  return api_1.context.with(api_1.trace.setSpan(api_1.context.active(), TracingHelper.startSpan(name, attributes)), async () => {
51
51
  const span = api_1.trace.getActiveSpan();
52
+ // If no active span, throw an error - this should not happen if the span was just started
53
+ if (!span) {
54
+ throw new Error(`Failed to get active span for "${name}". The OpenTelemetry context may not be properly initialized.`);
55
+ }
52
56
  try {
53
57
  const result = await fn(span);
54
58
  return result;
55
59
  }
56
60
  catch (error) {
57
- span?.recordException(error);
58
- span?.setStatus({ code: api_1.SpanStatusCode.ERROR, message: error.message });
61
+ span.recordException(error);
62
+ span.setStatus({ code: api_1.SpanStatusCode.ERROR, message: error.message });
59
63
  throw error;
60
64
  }
61
65
  finally {
62
- span?.end();
66
+ span.end();
63
67
  }
64
68
  });
65
69
  }
@@ -112,4 +116,4 @@ class TracingHelper {
112
116
  }
113
117
  }
114
118
  exports.TracingHelper = TracingHelper;
115
- TracingHelper.tracer = api_1.trace.getTracer('interactive-apm');
119
+ TracingHelper.tracer = api_1.trace.getTracer("interactive-apm");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vtvlive/interactive-apm",
3
- "version": "0.0.2",
3
+ "version": "0.0.4",
4
4
  "description": "APM integration package supporting both Elastic APM and OpenTelemetry with NestJS integration",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -28,13 +28,20 @@
28
28
  "test:watch": "jest --watch",
29
29
  "test:coverage": "jest --coverage",
30
30
  "test:unit": "jest __tests__/unit",
31
- "test:integration": "jest __tests__/integration"
31
+ "test:integration": "jest __tests__/integration",
32
+ "lint": "eslint src",
33
+ "lint:fix": "eslint src --fix",
34
+ "format": "prettier --write \"src/**/*.ts\"",
35
+ "format:check": "prettier --check \"src/**/*.ts\"",
36
+ "typecheck": "tsc --noEmit"
32
37
  },
33
38
  "peerDependencies": {
34
39
  "@nestjs/common": "^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0",
35
40
  "@nestjs/config": "^1.0.0 || ^2.0.0 || ^3.0.0 || ^4.0.0",
36
41
  "@opentelemetry/api": "^1.0.0",
37
42
  "@opentelemetry/exporter-trace-otlp-http": ">=0.200.0",
43
+ "@opentelemetry/exporter-trace-otlp-grpc": ">=0.200.0",
44
+ "@opentelemetry/exporter-trace-otlp-proto": ">=0.200.0",
38
45
  "@opentelemetry/instrumentation-express": ">=0.50.0",
39
46
  "@opentelemetry/instrumentation-http": ">=0.200.0",
40
47
  "@opentelemetry/resources": ">=1.0.0",
@@ -56,6 +63,12 @@
56
63
  "@opentelemetry/exporter-trace-otlp-http": {
57
64
  "optional": true
58
65
  },
66
+ "@opentelemetry/exporter-trace-otlp-grpc": {
67
+ "optional": true
68
+ },
69
+ "@opentelemetry/exporter-trace-otlp-proto": {
70
+ "optional": true
71
+ },
59
72
  "@opentelemetry/instrumentation-express": {
60
73
  "optional": true
61
74
  },
@@ -83,6 +96,8 @@
83
96
  "@nestjs/config": "^4.0.3",
84
97
  "@opentelemetry/api": "^1.9.0",
85
98
  "@opentelemetry/exporter-trace-otlp-http": "^0.213.0",
99
+ "@opentelemetry/exporter-trace-otlp-grpc": "^0.213.0",
100
+ "@opentelemetry/exporter-trace-otlp-proto": "^0.213.0",
86
101
  "@opentelemetry/instrumentation-express": "^0.61.0",
87
102
  "@opentelemetry/instrumentation-http": "^0.213.0",
88
103
  "@opentelemetry/resources": "^2.6.0",
@@ -92,9 +107,15 @@
92
107
  "@types/jest": "^29.5.0",
93
108
  "@types/node": "^20.0.0",
94
109
  "elastic-apm-node": "^4.15.0",
110
+ "eslint": "^9.18.0",
111
+ "eslint-config-prettier": "^9.1.0",
112
+ "eslint-plugin-prettier": "^5.2.1",
95
113
  "jest": "^29.7.0",
114
+ "prettier": "^3.4.2",
96
115
  "ts-jest": "^29.1.0",
97
- "typescript": "^5.3.0"
116
+ "typescript": "^5.3.0",
117
+ "@typescript-eslint/eslint-plugin": "^8.19.1",
118
+ "@typescript-eslint/parser": "^8.19.1"
98
119
  },
99
120
  "engines": {
100
121
  "node": ">=14.0.0"