@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
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OTLP Transport Type
|
|
3
|
+
*
|
|
4
|
+
* Defines the transport protocol used for exporting traces to APM
|
|
5
|
+
* - HTTP: Uses HTTP/JSON protocol (@opentelemetry/exporter-trace-otlp-http)
|
|
6
|
+
* - GRPC: Uses gRPC/protobuf protocol (@opentelemetry/exporter-trace-otlp-grpc)
|
|
7
|
+
* - PROTO: Uses protobuf over HTTP (@opentelemetry/exporter-trace-otlp-proto) - recommended for Elastic APM
|
|
8
|
+
*/
|
|
9
|
+
export declare enum OtlpTransport {
|
|
10
|
+
HTTP = "http",
|
|
11
|
+
GRPC = "grpc",
|
|
12
|
+
PROTO = "proto"
|
|
13
|
+
}
|
|
14
|
+
//# sourceMappingURL=otlp-transport.type.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"otlp-transport.type.d.ts","sourceRoot":"","sources":["../../src/types/otlp-transport.type.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,oBAAY,aAAa;IACvB,IAAI,SAAS;IACb,IAAI,SAAS;IACb,KAAK,UAAU;CAChB"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.OtlpTransport = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* OTLP Transport Type
|
|
6
|
+
*
|
|
7
|
+
* Defines the transport protocol used for exporting traces to APM
|
|
8
|
+
* - HTTP: Uses HTTP/JSON protocol (@opentelemetry/exporter-trace-otlp-http)
|
|
9
|
+
* - GRPC: Uses gRPC/protobuf protocol (@opentelemetry/exporter-trace-otlp-grpc)
|
|
10
|
+
* - PROTO: Uses protobuf over HTTP (@opentelemetry/exporter-trace-otlp-proto) - recommended for Elastic APM
|
|
11
|
+
*/
|
|
12
|
+
var OtlpTransport;
|
|
13
|
+
(function (OtlpTransport) {
|
|
14
|
+
OtlpTransport["HTTP"] = "http";
|
|
15
|
+
OtlpTransport["GRPC"] = "grpc";
|
|
16
|
+
OtlpTransport["PROTO"] = "proto";
|
|
17
|
+
})(OtlpTransport || (exports.OtlpTransport = OtlpTransport = {}));
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Debug Exporter Wrapper for OpenTelemetry OTLP
|
|
3
|
+
*
|
|
4
|
+
* Wraps the OTLPTraceExporter to log full request/response details
|
|
5
|
+
* when APM_DEBUG=true. This helps debug connection issues with APM servers.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Wrap OTLPTraceExporter with debug logging
|
|
9
|
+
*
|
|
10
|
+
* This intercepts the export calls to log:
|
|
11
|
+
* - Outgoing request details (URL, headers, body)
|
|
12
|
+
* - Response details (status code, headers, body)
|
|
13
|
+
* - Success/failure of exports
|
|
14
|
+
*
|
|
15
|
+
* @param baseExporter The original OTLPTraceExporter instance
|
|
16
|
+
* @param endpointUrl The OTLP endpoint URL for logging
|
|
17
|
+
* @returns Wrapped exporter with debug capabilities
|
|
18
|
+
*/
|
|
19
|
+
export declare function createDebugExporter(baseExporter: any, endpointUrl: string): any;
|
|
20
|
+
/**
|
|
21
|
+
* Create an HTTP interceptor for more detailed debugging
|
|
22
|
+
* This can be used to intercept the actual HTTP requests made by OTLP exporter
|
|
23
|
+
*/
|
|
24
|
+
export declare function createHttpRequestInterceptor(): any;
|
|
25
|
+
//# sourceMappingURL=debug-exporter-wrapper.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"debug-exporter-wrapper.d.ts","sourceRoot":"","sources":["../../src/utils/debug-exporter-wrapper.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH;;;;;;;;;;;GAWG;AACH,wBAAgB,mBAAmB,CAAC,YAAY,EAAE,GAAG,EAAE,WAAW,EAAE,MAAM,GAAG,GAAG,CA6E/E;AAyBD;;;GAGG;AACH,wBAAgB,4BAA4B,IAAI,GAAG,CA4FlD"}
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Debug Exporter Wrapper for OpenTelemetry OTLP
|
|
4
|
+
*
|
|
5
|
+
* Wraps the OTLPTraceExporter to log full request/response details
|
|
6
|
+
* when APM_DEBUG=true. This helps debug connection issues with APM servers.
|
|
7
|
+
*/
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.createDebugExporter = createDebugExporter;
|
|
10
|
+
exports.createHttpRequestInterceptor = createHttpRequestInterceptor;
|
|
11
|
+
const debug_logger_1 = require("./debug-logger");
|
|
12
|
+
/**
|
|
13
|
+
* Wrap OTLPTraceExporter with debug logging
|
|
14
|
+
*
|
|
15
|
+
* This intercepts the export calls to log:
|
|
16
|
+
* - Outgoing request details (URL, headers, body)
|
|
17
|
+
* - Response details (status code, headers, body)
|
|
18
|
+
* - Success/failure of exports
|
|
19
|
+
*
|
|
20
|
+
* @param baseExporter The original OTLPTraceExporter instance
|
|
21
|
+
* @param endpointUrl The OTLP endpoint URL for logging
|
|
22
|
+
* @returns Wrapped exporter with debug capabilities
|
|
23
|
+
*/
|
|
24
|
+
function createDebugExporter(baseExporter, endpointUrl) {
|
|
25
|
+
if (!(0, debug_logger_1.isDebugEnabled)()) {
|
|
26
|
+
// When debug is off, just log errors only
|
|
27
|
+
return {
|
|
28
|
+
export: (spans, resultCallback) => {
|
|
29
|
+
baseExporter.export(spans, (result) => {
|
|
30
|
+
if (result.error) {
|
|
31
|
+
(0, debug_logger_1.logExportFailure)('OpenTelemetry', result.error);
|
|
32
|
+
}
|
|
33
|
+
resultCallback(result);
|
|
34
|
+
});
|
|
35
|
+
},
|
|
36
|
+
shutdown: async () => {
|
|
37
|
+
await baseExporter.shutdown();
|
|
38
|
+
},
|
|
39
|
+
forceFlush: async () => {
|
|
40
|
+
if (baseExporter.forceFlush) {
|
|
41
|
+
await baseExporter.forceFlush();
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
// Debug mode ON - intercept everything
|
|
47
|
+
const wrappedExporter = {
|
|
48
|
+
export: (spans, resultCallback) => {
|
|
49
|
+
const startTime = Date.now();
|
|
50
|
+
// Log what we're about to send
|
|
51
|
+
(0, debug_logger_1.logRequest)('OpenTelemetry', endpointUrl, getExporterHeaders(baseExporter), {
|
|
52
|
+
spanCount: spans.length,
|
|
53
|
+
spans: spans.map((s) => ({
|
|
54
|
+
name: s.name,
|
|
55
|
+
kind: s.kind,
|
|
56
|
+
startTime: s.startTime,
|
|
57
|
+
endTime: s.endTime,
|
|
58
|
+
})),
|
|
59
|
+
});
|
|
60
|
+
// Wrap the result callback to capture response
|
|
61
|
+
baseExporter.export(spans, (result) => {
|
|
62
|
+
const duration = Date.now() - startTime;
|
|
63
|
+
if (result.error) {
|
|
64
|
+
(0, debug_logger_1.logExportFailure)('OpenTelemetry', result.error, {
|
|
65
|
+
endpoint: endpointUrl,
|
|
66
|
+
spanCount: spans.length,
|
|
67
|
+
duration: `${duration}ms`,
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
(0, debug_logger_1.logExportSuccess)('OpenTelemetry', spans.length);
|
|
72
|
+
(0, debug_logger_1.logResponse)('OpenTelemetry', 200, { 'content-type': 'application/json' }, {
|
|
73
|
+
message: `Exported ${spans.length} span(s) in ${duration}ms`,
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
resultCallback(result);
|
|
77
|
+
});
|
|
78
|
+
},
|
|
79
|
+
shutdown: async () => {
|
|
80
|
+
(0, debug_logger_1.logRequest)('OpenTelemetry', endpointUrl, {}, { action: 'shutdown' });
|
|
81
|
+
try {
|
|
82
|
+
await baseExporter.shutdown();
|
|
83
|
+
(0, debug_logger_1.logResponse)('OpenTelemetry', 200, {}, { message: 'Exporter shutdown complete' });
|
|
84
|
+
}
|
|
85
|
+
catch (err) {
|
|
86
|
+
(0, debug_logger_1.logResponse)('OpenTelemetry', 500, {}, { message: 'Exporter shutdown failed', error: err?.message || err });
|
|
87
|
+
throw err;
|
|
88
|
+
}
|
|
89
|
+
},
|
|
90
|
+
forceFlush: async () => {
|
|
91
|
+
if (baseExporter.forceFlush) {
|
|
92
|
+
await baseExporter.forceFlush();
|
|
93
|
+
}
|
|
94
|
+
},
|
|
95
|
+
};
|
|
96
|
+
return wrappedExporter;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Get headers from the exporter instance for logging
|
|
100
|
+
* The OTLPTraceExporter stores headers internally, we need to extract them
|
|
101
|
+
*/
|
|
102
|
+
function getExporterHeaders(exporter) {
|
|
103
|
+
try {
|
|
104
|
+
// Try to access headers from the exporter
|
|
105
|
+
// @ts-ignore - accessing internal property
|
|
106
|
+
if (exporter._headers) {
|
|
107
|
+
// @ts-ignore
|
|
108
|
+
return { ...exporter._headers };
|
|
109
|
+
}
|
|
110
|
+
// @ts-ignore - alternative internal property
|
|
111
|
+
if (exporter.headers) {
|
|
112
|
+
// @ts-ignore
|
|
113
|
+
return { ...exporter.headers };
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
catch (error) {
|
|
117
|
+
// Silently fail if we can't access headers
|
|
118
|
+
}
|
|
119
|
+
return {};
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Create an HTTP interceptor for more detailed debugging
|
|
123
|
+
* This can be used to intercept the actual HTTP requests made by OTLP exporter
|
|
124
|
+
*/
|
|
125
|
+
function createHttpRequestInterceptor() {
|
|
126
|
+
if (!(0, debug_logger_1.isDebugEnabled)()) {
|
|
127
|
+
return null;
|
|
128
|
+
}
|
|
129
|
+
const originalRequest = require('http').request;
|
|
130
|
+
const originalHttpsRequest = require('https').request;
|
|
131
|
+
return {
|
|
132
|
+
install: () => {
|
|
133
|
+
const { logRequest: logReq, logResponse: logResp } = require('./debug-logger');
|
|
134
|
+
// Override http.request
|
|
135
|
+
require('http').request = function (options, ...args) {
|
|
136
|
+
const url = typeof options === 'string' ? options : `${options.protocol || 'http:'}//${options.hostname || options.host}${options.path || '/'}`;
|
|
137
|
+
// Only intercept APM-related requests
|
|
138
|
+
if (url.includes('/v1/traces') || url.includes('/v1/metrics') || url.includes('/intake/v2/traces')) {
|
|
139
|
+
const req = originalRequest.call(this, options, ...args);
|
|
140
|
+
const originalWrite = req.write.bind(req);
|
|
141
|
+
req.write = function (chunk, ...rest) {
|
|
142
|
+
// Handle binary data safely - don't stringify protobuf/binary payloads
|
|
143
|
+
let logData;
|
|
144
|
+
if (Buffer.isBuffer(chunk) || chunk instanceof Uint8Array) {
|
|
145
|
+
logData = `<binary data: ${chunk.length} bytes>`;
|
|
146
|
+
}
|
|
147
|
+
else if (typeof chunk === 'string') {
|
|
148
|
+
logData = chunk;
|
|
149
|
+
}
|
|
150
|
+
else {
|
|
151
|
+
logData = chunk?.toString();
|
|
152
|
+
}
|
|
153
|
+
logReq('OpenTelemetry', url, options.headers || {}, logData);
|
|
154
|
+
return originalWrite(chunk, ...rest);
|
|
155
|
+
};
|
|
156
|
+
req.on('response', (res) => {
|
|
157
|
+
let body = '';
|
|
158
|
+
res.on('data', (chunk) => { body += chunk; });
|
|
159
|
+
res.on('end', () => {
|
|
160
|
+
logResp('OpenTelemetry', res.statusCode, res.headers, body);
|
|
161
|
+
});
|
|
162
|
+
});
|
|
163
|
+
return req;
|
|
164
|
+
}
|
|
165
|
+
return originalRequest.call(this, options, ...args);
|
|
166
|
+
};
|
|
167
|
+
// Override https.request
|
|
168
|
+
require('https').request = function (options, ...args) {
|
|
169
|
+
const url = typeof options === 'string' ? options : `${options.protocol || 'https:'}//${options.hostname || options.host}${options.path || '/'}`;
|
|
170
|
+
// Only intercept APM-related requests
|
|
171
|
+
if (url.includes('/v1/traces') || url.includes('/v1/metrics') || url.includes('/intake/v2/traces')) {
|
|
172
|
+
const req = originalHttpsRequest.call(this, options, ...args);
|
|
173
|
+
const originalWrite = req.write.bind(req);
|
|
174
|
+
req.write = function (chunk, ...rest) {
|
|
175
|
+
// Handle binary data safely - don't stringify protobuf/binary payloads
|
|
176
|
+
let logData;
|
|
177
|
+
if (Buffer.isBuffer(chunk) || chunk instanceof Uint8Array) {
|
|
178
|
+
logData = `<binary data: ${chunk.length} bytes>`;
|
|
179
|
+
}
|
|
180
|
+
else if (typeof chunk === 'string') {
|
|
181
|
+
logData = chunk;
|
|
182
|
+
}
|
|
183
|
+
else {
|
|
184
|
+
logData = chunk?.toString();
|
|
185
|
+
}
|
|
186
|
+
logReq('OpenTelemetry', url, options.headers || {}, logData);
|
|
187
|
+
return originalWrite(chunk, ...rest);
|
|
188
|
+
};
|
|
189
|
+
req.on('response', (res) => {
|
|
190
|
+
let body = '';
|
|
191
|
+
res.on('data', (chunk) => { body += chunk; });
|
|
192
|
+
res.on('end', () => {
|
|
193
|
+
logResp('OpenTelemetry', res.statusCode, res.headers, body);
|
|
194
|
+
});
|
|
195
|
+
});
|
|
196
|
+
return req;
|
|
197
|
+
}
|
|
198
|
+
return originalHttpsRequest.call(this, options, ...args);
|
|
199
|
+
};
|
|
200
|
+
},
|
|
201
|
+
uninstall: () => {
|
|
202
|
+
// Restore original functions
|
|
203
|
+
require('http').request = originalRequest;
|
|
204
|
+
require('https').request = originalHttpsRequest;
|
|
205
|
+
},
|
|
206
|
+
};
|
|
207
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Debug Logger for APM Package
|
|
3
|
+
*
|
|
4
|
+
* Provides centralized debug logging that only outputs when APM_DEBUG=true.
|
|
5
|
+
* Used by both Elastic APM and OpenTelemetry providers.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Check if debug mode is enabled via environment variable
|
|
9
|
+
*/
|
|
10
|
+
export declare function isDebugEnabled(): boolean;
|
|
11
|
+
/**
|
|
12
|
+
* Sanitize headers for logging (mask sensitive values)
|
|
13
|
+
*/
|
|
14
|
+
export declare function sanitizeHeaders(headers: Record<string, string>): Record<string, string>;
|
|
15
|
+
/**
|
|
16
|
+
* Sanitize config object for logging (mask sensitive values)
|
|
17
|
+
* Handles arrays and circular references safely
|
|
18
|
+
*/
|
|
19
|
+
export declare function sanitizeConfig(config: any, seen?: WeakMap<object, any>): any;
|
|
20
|
+
/**
|
|
21
|
+
* Debug log - only outputs when APM_DEBUG=true
|
|
22
|
+
*/
|
|
23
|
+
export declare function debugLog(message: string, ...args: any[]): void;
|
|
24
|
+
/**
|
|
25
|
+
* Info log - always outputs
|
|
26
|
+
*/
|
|
27
|
+
export declare function infoLog(message: string, ...args: any[]): void;
|
|
28
|
+
/**
|
|
29
|
+
* Error log - always outputs
|
|
30
|
+
*/
|
|
31
|
+
export declare function errorLog(message: string, ...args: any[]): void;
|
|
32
|
+
/**
|
|
33
|
+
* Log initialization details
|
|
34
|
+
*/
|
|
35
|
+
export declare function logInitialization(provider: string, config: Record<string, any>): void;
|
|
36
|
+
/**
|
|
37
|
+
* Log span export success (debug mode only)
|
|
38
|
+
*/
|
|
39
|
+
export declare function logExportSuccess(provider: string, spanCount: number): void;
|
|
40
|
+
/**
|
|
41
|
+
* Log span export failure - ALWAYS logs regardless of debug mode
|
|
42
|
+
*/
|
|
43
|
+
export declare function logExportFailure(provider: string, error: any, requestDetails?: any): void;
|
|
44
|
+
/**
|
|
45
|
+
* Log outgoing HTTP request (for debugging APM communication)
|
|
46
|
+
*/
|
|
47
|
+
export declare function logRequest(provider: string, url: string, headers: Record<string, string>, body?: any): void;
|
|
48
|
+
/**
|
|
49
|
+
* Log incoming HTTP response (for debugging APM communication)
|
|
50
|
+
*/
|
|
51
|
+
export declare function logResponse(provider: string, statusCode: number, headers: Record<string, string>, body?: any): void;
|
|
52
|
+
/**
|
|
53
|
+
* Log span details (when debug mode is on)
|
|
54
|
+
*/
|
|
55
|
+
export declare function logSpan(provider: string, span: any): void;
|
|
56
|
+
//# sourceMappingURL=debug-logger.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"debug-logger.d.ts","sourceRoot":"","sources":["../../src/utils/debug-logger.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH;;GAEG;AACH,wBAAgB,cAAc,IAAI,OAAO,CAExC;AAgBD;;GAEG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAMvF;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,GAAG,EAAE,IAAI,GAAE,OAAO,CAAC,MAAM,EAAE,GAAG,CAAiB,GAAG,GAAG,CA8C3F;AAED;;GAEG;AACH,wBAAgB,QAAQ,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,GAAG,EAAE,GAAG,IAAI,CAI9D;AAED;;GAEG;AACH,wBAAgB,OAAO,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,GAAG,EAAE,GAAG,IAAI,CAE7D;AAED;;GAEG;AACH,wBAAgB,QAAQ,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,GAAG,EAAE,GAAG,IAAI,CAE9D;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,IAAI,CAsBrF;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI,CAI1E;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,EAAE,cAAc,CAAC,EAAE,GAAG,GAAG,IAAI,CAQzF;AAED;;GAEG;AACH,wBAAgB,UAAU,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,IAAI,CAAC,EAAE,GAAG,GAAG,IAAI,CAwB3G;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,IAAI,CAAC,EAAE,GAAG,GAAG,IAAI,CAUnH;AAED;;GAEG;AACH,wBAAgB,OAAO,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,GAAG,IAAI,CAmBzD"}
|
|
@@ -0,0 +1,227 @@
|
|
|
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 = ['authorization', 'token', 'secret', 'password', 'apikey', 'api-key', 'x-api-key'];
|
|
32
|
+
const lowerKey = key.toLowerCase();
|
|
33
|
+
if (sensitiveKeys.some(k => lowerKey.includes(k))) {
|
|
34
|
+
return '[REDACTED]';
|
|
35
|
+
}
|
|
36
|
+
return value;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Sanitize headers for logging (mask sensitive values)
|
|
40
|
+
*/
|
|
41
|
+
function sanitizeHeaders(headers) {
|
|
42
|
+
const sanitized = {};
|
|
43
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
44
|
+
sanitized[key] = maskSensitive(value, key);
|
|
45
|
+
}
|
|
46
|
+
return sanitized;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Sanitize config object for logging (mask sensitive values)
|
|
50
|
+
* Handles arrays and circular references safely
|
|
51
|
+
*/
|
|
52
|
+
function sanitizeConfig(config, seen = new WeakMap()) {
|
|
53
|
+
// Handle primitives and null
|
|
54
|
+
if (config === null || typeof config !== 'object') {
|
|
55
|
+
return config;
|
|
56
|
+
}
|
|
57
|
+
// Handle circular references
|
|
58
|
+
if (seen.has(config)) {
|
|
59
|
+
return '[Circular]';
|
|
60
|
+
}
|
|
61
|
+
seen.set(config, '[Circular]');
|
|
62
|
+
// Handle arrays
|
|
63
|
+
if (Array.isArray(config)) {
|
|
64
|
+
return config.map(item => {
|
|
65
|
+
if (typeof item === 'string') {
|
|
66
|
+
return maskSensitive(item, '');
|
|
67
|
+
}
|
|
68
|
+
else if (typeof item === 'object' && item !== null) {
|
|
69
|
+
return sanitizeConfig(item, seen);
|
|
70
|
+
}
|
|
71
|
+
return item;
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
// Handle objects
|
|
75
|
+
const sanitized = {};
|
|
76
|
+
for (const [key, value] of Object.entries(config)) {
|
|
77
|
+
if (typeof value === 'string') {
|
|
78
|
+
sanitized[key] = maskSensitive(value, key);
|
|
79
|
+
}
|
|
80
|
+
else if (Array.isArray(value)) {
|
|
81
|
+
sanitized[key] = value.map(item => {
|
|
82
|
+
if (typeof item === 'string') {
|
|
83
|
+
return maskSensitive(item, key);
|
|
84
|
+
}
|
|
85
|
+
else if (typeof item === 'object' && item !== null) {
|
|
86
|
+
return sanitizeConfig(item, seen);
|
|
87
|
+
}
|
|
88
|
+
return item;
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
else if (typeof value === 'object' && value !== null) {
|
|
92
|
+
sanitized[key] = sanitizeConfig(value, seen);
|
|
93
|
+
}
|
|
94
|
+
else {
|
|
95
|
+
sanitized[key] = value;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return sanitized;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Debug log - only outputs when APM_DEBUG=true
|
|
102
|
+
*/
|
|
103
|
+
function debugLog(message, ...args) {
|
|
104
|
+
if (isDebugEnabled()) {
|
|
105
|
+
console.log(`[APM-DEBUG] ${message}`, ...args);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Info log - always outputs
|
|
110
|
+
*/
|
|
111
|
+
function infoLog(message, ...args) {
|
|
112
|
+
console.log(`[APM] ${message}`, ...args);
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Error log - always outputs
|
|
116
|
+
*/
|
|
117
|
+
function errorLog(message, ...args) {
|
|
118
|
+
console.error(`[APM-ERROR] ${message}`, ...args);
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Log initialization details
|
|
122
|
+
*/
|
|
123
|
+
function logInitialization(provider, config) {
|
|
124
|
+
if (!isDebugEnabled()) {
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
debugLog(`=== ${provider} Initialization ===`);
|
|
128
|
+
debugLog('Configuration:', sanitizeConfig(config));
|
|
129
|
+
debugLog('Environment Variables:', {
|
|
130
|
+
APM_PROVIDER: process.env.APM_PROVIDER,
|
|
131
|
+
ELASTIC_APM_SERVICE_NAME: process.env.ELASTIC_APM_SERVICE_NAME,
|
|
132
|
+
ELASTIC_APM_ENVIRONMENT: process.env.ELASTIC_APM_ENVIRONMENT,
|
|
133
|
+
ELASTIC_OTLP_ENDPOINT: process.env.ELASTIC_OTLP_ENDPOINT,
|
|
134
|
+
ELASTIC_APM_SERVER_URL: process.env.ELASTIC_APM_SERVER_URL,
|
|
135
|
+
APM_DEBUG: process.env.APM_DEBUG,
|
|
136
|
+
});
|
|
137
|
+
debugLog('System Info:', {
|
|
138
|
+
nodeVersion: process.version,
|
|
139
|
+
platform: process.platform,
|
|
140
|
+
arch: process.arch,
|
|
141
|
+
pid: process.pid,
|
|
142
|
+
hostname: require('os').hostname(),
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Log span export success (debug mode only)
|
|
147
|
+
*/
|
|
148
|
+
function logExportSuccess(provider, spanCount) {
|
|
149
|
+
if (isDebugEnabled()) {
|
|
150
|
+
debugLog(`[${provider}] Exported ${spanCount} span(s)`);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Log span export failure - ALWAYS logs regardless of debug mode
|
|
155
|
+
*/
|
|
156
|
+
function logExportFailure(provider, error, requestDetails) {
|
|
157
|
+
// Always log export failures as errors
|
|
158
|
+
errorLog(`[${provider}] Export failed:`, error);
|
|
159
|
+
// Log additional details only in debug mode
|
|
160
|
+
if (isDebugEnabled() && requestDetails) {
|
|
161
|
+
debugLog('Request details:', sanitizeConfig(requestDetails));
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Log outgoing HTTP request (for debugging APM communication)
|
|
166
|
+
*/
|
|
167
|
+
function logRequest(provider, url, headers, body) {
|
|
168
|
+
if (!isDebugEnabled()) {
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
debugLog(`[${provider}] → Sending request to: ${url}`);
|
|
172
|
+
debugLog('Headers:', sanitizeHeaders(headers));
|
|
173
|
+
// Sanitize request body for sensitive data before logging
|
|
174
|
+
if (body) {
|
|
175
|
+
// WARNING: Request bodies may contain sensitive data - we sanitize common patterns
|
|
176
|
+
let sanitizedBody;
|
|
177
|
+
if (typeof body === 'string') {
|
|
178
|
+
// Redact common auth/token patterns in strings
|
|
179
|
+
sanitizedBody = body.replace(/"(?:password|pass|pwd|token|access_token|auth|authorization|cookie|ssn|creditCard|cardNumber|cvv|secret)"\s*:\s*"[^"]*"/gi, '"$1": "[REDACTED]"');
|
|
180
|
+
sanitizedBody = sanitizedBody.replace(/Bearer\s+[A-Za-z0-9\-._~+/=]*/gi, 'Bearer [REDACTED]');
|
|
181
|
+
}
|
|
182
|
+
else if (typeof body === 'object' && body !== null) {
|
|
183
|
+
// Deep clone and sanitize object
|
|
184
|
+
sanitizedBody = sanitizeConfig(body);
|
|
185
|
+
}
|
|
186
|
+
else {
|
|
187
|
+
sanitizedBody = body;
|
|
188
|
+
}
|
|
189
|
+
debugLog('Body:', typeof sanitizedBody === 'string' ? sanitizedBody : JSON.stringify(sanitizedBody, null, 2));
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Log incoming HTTP response (for debugging APM communication)
|
|
194
|
+
*/
|
|
195
|
+
function logResponse(provider, statusCode, headers, body) {
|
|
196
|
+
if (!isDebugEnabled()) {
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
debugLog(`[${provider}] ← Received response: ${statusCode}`);
|
|
200
|
+
debugLog('Response Headers:', sanitizeHeaders(headers));
|
|
201
|
+
if (body) {
|
|
202
|
+
debugLog('Response Body:', typeof body === 'string' ? body : JSON.stringify(body, null, 2));
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Log span details (when debug mode is on)
|
|
207
|
+
*/
|
|
208
|
+
function logSpan(provider, span) {
|
|
209
|
+
if (!isDebugEnabled()) {
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
try {
|
|
213
|
+
const spanDetails = {
|
|
214
|
+
name: span.name,
|
|
215
|
+
kind: span.kind,
|
|
216
|
+
startTime: span.startTime,
|
|
217
|
+
endTime: span.endTime,
|
|
218
|
+
duration: span.endTime - span.startTime,
|
|
219
|
+
attributes: span.attributes,
|
|
220
|
+
status: span.status,
|
|
221
|
+
};
|
|
222
|
+
debugLog(`[${provider}] Span details:`, JSON.stringify(spanDetails, null, 2));
|
|
223
|
+
}
|
|
224
|
+
catch (error) {
|
|
225
|
+
debugLog(`[${provider}] Could not serialize span:`, error);
|
|
226
|
+
}
|
|
227
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vtvlive/interactive-apm",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.3",
|
|
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",
|
|
@@ -35,6 +35,8 @@
|
|
|
35
35
|
"@nestjs/config": "^1.0.0 || ^2.0.0 || ^3.0.0 || ^4.0.0",
|
|
36
36
|
"@opentelemetry/api": "^1.0.0",
|
|
37
37
|
"@opentelemetry/exporter-trace-otlp-http": ">=0.200.0",
|
|
38
|
+
"@opentelemetry/exporter-trace-otlp-grpc": ">=0.200.0",
|
|
39
|
+
"@opentelemetry/exporter-trace-otlp-proto": ">=0.200.0",
|
|
38
40
|
"@opentelemetry/instrumentation-express": ">=0.50.0",
|
|
39
41
|
"@opentelemetry/instrumentation-http": ">=0.200.0",
|
|
40
42
|
"@opentelemetry/resources": ">=1.0.0",
|
|
@@ -56,6 +58,12 @@
|
|
|
56
58
|
"@opentelemetry/exporter-trace-otlp-http": {
|
|
57
59
|
"optional": true
|
|
58
60
|
},
|
|
61
|
+
"@opentelemetry/exporter-trace-otlp-grpc": {
|
|
62
|
+
"optional": true
|
|
63
|
+
},
|
|
64
|
+
"@opentelemetry/exporter-trace-otlp-proto": {
|
|
65
|
+
"optional": true
|
|
66
|
+
},
|
|
59
67
|
"@opentelemetry/instrumentation-express": {
|
|
60
68
|
"optional": true
|
|
61
69
|
},
|
|
@@ -83,6 +91,8 @@
|
|
|
83
91
|
"@nestjs/config": "^4.0.3",
|
|
84
92
|
"@opentelemetry/api": "^1.9.0",
|
|
85
93
|
"@opentelemetry/exporter-trace-otlp-http": "^0.213.0",
|
|
94
|
+
"@opentelemetry/exporter-trace-otlp-grpc": "^0.213.0",
|
|
95
|
+
"@opentelemetry/exporter-trace-otlp-proto": "^1.22.0",
|
|
86
96
|
"@opentelemetry/instrumentation-express": "^0.61.0",
|
|
87
97
|
"@opentelemetry/instrumentation-http": "^0.213.0",
|
|
88
98
|
"@opentelemetry/resources": "^2.6.0",
|