@ngn-net/nestjs-telescope 0.3.9 → 0.3.11
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/ui/manifest.json
CHANGED
|
@@ -5,8 +5,6 @@ export declare class RabbitMQWatcher implements OnModuleInit {
|
|
|
5
5
|
private readonly logger;
|
|
6
6
|
constructor(telescope: TelescopeService);
|
|
7
7
|
onModuleInit(): void;
|
|
8
|
-
private
|
|
9
|
-
private
|
|
10
|
-
private instrumentChannel;
|
|
11
|
-
private sanitizeMessage;
|
|
8
|
+
private patchExistingChannels;
|
|
9
|
+
private patchAmqpConnectionPublish;
|
|
12
10
|
}
|
|
@@ -14,6 +14,111 @@ exports.RabbitMQWatcher = void 0;
|
|
|
14
14
|
const common_1 = require("@nestjs/common");
|
|
15
15
|
const telescope_service_1 = require("../telescope.service");
|
|
16
16
|
const entry_type_enum_1 = require("../enums/entry-type.enum");
|
|
17
|
+
let telescopeRef = null;
|
|
18
|
+
function sanitizeMessage(msg) {
|
|
19
|
+
if (!msg)
|
|
20
|
+
return null;
|
|
21
|
+
if (typeof msg === 'string') {
|
|
22
|
+
try {
|
|
23
|
+
return sanitizeMessage(JSON.parse(msg));
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
return msg.length > 1000 ? msg.substring(0, 1000) + '...[truncated]' : msg;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
if (Buffer.isBuffer(msg))
|
|
30
|
+
return '[Buffer]';
|
|
31
|
+
if (typeof msg === 'object') {
|
|
32
|
+
const sanitized = {};
|
|
33
|
+
for (const [key, value] of Object.entries(msg)) {
|
|
34
|
+
if (/password|secret|token/i.test(key))
|
|
35
|
+
sanitized[key] = '[REDACTED]';
|
|
36
|
+
else if (typeof value === 'string' && value.length > 500)
|
|
37
|
+
sanitized[key] = value.substring(0, 500) + '...[truncated]';
|
|
38
|
+
else
|
|
39
|
+
sanitized[key] = value;
|
|
40
|
+
}
|
|
41
|
+
return sanitized;
|
|
42
|
+
}
|
|
43
|
+
return msg;
|
|
44
|
+
}
|
|
45
|
+
function instrumentChannel(channel) {
|
|
46
|
+
if (channel.__telescopePatched)
|
|
47
|
+
return;
|
|
48
|
+
channel.__telescopePatched = true;
|
|
49
|
+
const originalConsume = channel.consume;
|
|
50
|
+
if (!originalConsume)
|
|
51
|
+
return;
|
|
52
|
+
channel.consume = function (queue, onMessage, options) {
|
|
53
|
+
const wrappedOnMessage = (msg) => {
|
|
54
|
+
if (!msg || !msg.content)
|
|
55
|
+
return onMessage(msg);
|
|
56
|
+
if (telescopeRef) {
|
|
57
|
+
let parsed;
|
|
58
|
+
try {
|
|
59
|
+
parsed = JSON.parse(msg.content.toString());
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
parsed = msg.content.toString();
|
|
63
|
+
}
|
|
64
|
+
telescopeRef.record({
|
|
65
|
+
type: entry_type_enum_1.EntryType.RABBITMQ,
|
|
66
|
+
content: {
|
|
67
|
+
direction: 'incoming',
|
|
68
|
+
queue,
|
|
69
|
+
exchange: msg.fields?.exchange || '',
|
|
70
|
+
routingKey: msg.fields?.routingKey || queue,
|
|
71
|
+
message: sanitizeMessage(parsed),
|
|
72
|
+
deliveryTag: msg.fields?.deliveryTag,
|
|
73
|
+
timestamp: new Date().toISOString(),
|
|
74
|
+
},
|
|
75
|
+
}).catch(() => { });
|
|
76
|
+
}
|
|
77
|
+
return onMessage(msg);
|
|
78
|
+
};
|
|
79
|
+
return originalConsume.call(this, queue, wrappedOnMessage, options);
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
// Patch at file load time — intercept AmqpConnection.createChannel
|
|
83
|
+
try {
|
|
84
|
+
const { AmqpConnection } = require('@golevelup/nestjs-rabbitmq');
|
|
85
|
+
if (AmqpConnection && AmqpConnection.prototype) {
|
|
86
|
+
const origCreateChannel = AmqpConnection.prototype.createChannel;
|
|
87
|
+
if (origCreateChannel) {
|
|
88
|
+
AmqpConnection.prototype.createChannel = async function (...args) {
|
|
89
|
+
const channel = await origCreateChannel.apply(this, args);
|
|
90
|
+
if (channel && typeof channel.consume === 'function') {
|
|
91
|
+
instrumentChannel(channel);
|
|
92
|
+
}
|
|
93
|
+
return channel;
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
// Also patch the internal init method that creates channels
|
|
97
|
+
const origInit = AmqpConnection.prototype.init;
|
|
98
|
+
if (origInit) {
|
|
99
|
+
AmqpConnection.prototype.init = async function (...args) {
|
|
100
|
+
const result = await origInit.apply(this, args);
|
|
101
|
+
// After init, patch any existing channel
|
|
102
|
+
if (this.channel && typeof this.channel.consume === 'function') {
|
|
103
|
+
instrumentChannel(this.channel);
|
|
104
|
+
}
|
|
105
|
+
return result;
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
// Patch the 'managedConnection' setter to catch channel creation
|
|
109
|
+
const origConnect = AmqpConnection.prototype.connect;
|
|
110
|
+
if (origConnect) {
|
|
111
|
+
AmqpConnection.prototype.connect = async function (...args) {
|
|
112
|
+
const result = await origConnect.apply(this, args);
|
|
113
|
+
if (this.channel && typeof this.channel.consume === 'function') {
|
|
114
|
+
instrumentChannel(this.channel);
|
|
115
|
+
}
|
|
116
|
+
return result;
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
catch { }
|
|
17
122
|
let RabbitMQWatcher = RabbitMQWatcher_1 = class RabbitMQWatcher {
|
|
18
123
|
telescope;
|
|
19
124
|
logger = new common_1.Logger(RabbitMQWatcher_1.name);
|
|
@@ -21,10 +126,28 @@ let RabbitMQWatcher = RabbitMQWatcher_1 = class RabbitMQWatcher {
|
|
|
21
126
|
this.telescope = telescope;
|
|
22
127
|
}
|
|
23
128
|
onModuleInit() {
|
|
24
|
-
this.
|
|
25
|
-
this.
|
|
129
|
+
telescopeRef = this.telescope;
|
|
130
|
+
this.patchAmqpConnectionPublish();
|
|
131
|
+
this.patchExistingChannels();
|
|
132
|
+
this.logger.log('RabbitMQWatcher initialized — incoming & outgoing messages will be recorded');
|
|
133
|
+
}
|
|
134
|
+
patchExistingChannels() {
|
|
135
|
+
try {
|
|
136
|
+
const { AmqpConnection } = require('@golevelup/nestjs-rabbitmq');
|
|
137
|
+
if (!AmqpConnection)
|
|
138
|
+
return;
|
|
139
|
+
// Try to get the instance from the global scope
|
|
140
|
+
// @golevelup/nestjs-rabbitmq stores it as a module provider
|
|
141
|
+
const instances = AmqpConnection.__instances || [];
|
|
142
|
+
for (const inst of instances) {
|
|
143
|
+
if (inst?.channel && typeof inst.channel.consume === 'function') {
|
|
144
|
+
instrumentChannel(inst.channel);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
catch { }
|
|
26
149
|
}
|
|
27
|
-
|
|
150
|
+
patchAmqpConnectionPublish() {
|
|
28
151
|
try {
|
|
29
152
|
const { AmqpConnection } = require('@golevelup/nestjs-rabbitmq');
|
|
30
153
|
if (!AmqpConnection || !AmqpConnection.prototype)
|
|
@@ -43,121 +166,15 @@ let RabbitMQWatcher = RabbitMQWatcher_1 = class RabbitMQWatcher {
|
|
|
43
166
|
direction: 'outgoing',
|
|
44
167
|
exchange,
|
|
45
168
|
routingKey,
|
|
46
|
-
message:
|
|
169
|
+
message: sanitizeMessage(msg),
|
|
47
170
|
duration,
|
|
48
171
|
status: 'sent',
|
|
49
172
|
},
|
|
50
173
|
}).catch(() => { });
|
|
51
174
|
return result;
|
|
52
175
|
};
|
|
53
|
-
this.logger.log('Patched AmqpConnection.publish');
|
|
54
|
-
}
|
|
55
|
-
catch (e) {
|
|
56
|
-
this.logger.warn('Failed to patch AmqpConnection.publish: ' + (e?.message || e));
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
patchAmqplibConnect() {
|
|
60
|
-
try {
|
|
61
|
-
const amqplib = require('amqplib');
|
|
62
|
-
if (!amqplib || typeof amqplib.connect !== 'function') {
|
|
63
|
-
this.logger.warn('amqplib.connect not found');
|
|
64
|
-
return;
|
|
65
|
-
}
|
|
66
|
-
const originalConnect = amqplib.connect;
|
|
67
|
-
const self = this;
|
|
68
|
-
let patched = false;
|
|
69
|
-
amqplib.connect = async function (...args) {
|
|
70
|
-
const conn = await originalConnect.apply(this, args);
|
|
71
|
-
if (conn && typeof conn.createChannel === 'function') {
|
|
72
|
-
const originalCreateChannel = conn.createChannel.bind(conn);
|
|
73
|
-
conn.createChannel = async function (...channelArgs) {
|
|
74
|
-
const channel = await originalCreateChannel(...channelArgs);
|
|
75
|
-
if (channel && typeof channel.consume === 'function') {
|
|
76
|
-
self.instrumentChannel(channel);
|
|
77
|
-
self.logger.log('Patched amqplib channel.consume');
|
|
78
|
-
}
|
|
79
|
-
return channel;
|
|
80
|
-
};
|
|
81
|
-
}
|
|
82
|
-
return conn;
|
|
83
|
-
};
|
|
84
|
-
patched = true;
|
|
85
|
-
this.logger.log('Patched amqplib.connect');
|
|
86
|
-
}
|
|
87
|
-
catch (e) {
|
|
88
|
-
this.logger.warn('Failed to patch amqplib.connect: ' + (e?.message || e));
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
instrumentChannel(channel) {
|
|
92
|
-
const self = this;
|
|
93
|
-
if (channel.__telescopePatched)
|
|
94
|
-
return;
|
|
95
|
-
channel.__telescopePatched = true;
|
|
96
|
-
const originalConsume = channel.consume;
|
|
97
|
-
if (!originalConsume)
|
|
98
|
-
return;
|
|
99
|
-
channel.consume = function (queue, onMessage, options) {
|
|
100
|
-
self.logger.log(`Intercepting consume for queue: ${queue}`);
|
|
101
|
-
const wrappedOnMessage = (msg) => {
|
|
102
|
-
if (!msg || !msg.content)
|
|
103
|
-
return onMessage(msg);
|
|
104
|
-
self.logger.log(`Incoming message on queue: ${queue}`);
|
|
105
|
-
const startTime = Date.now();
|
|
106
|
-
let parsed;
|
|
107
|
-
try {
|
|
108
|
-
parsed = JSON.parse(msg.content.toString());
|
|
109
|
-
}
|
|
110
|
-
catch {
|
|
111
|
-
parsed = msg.content.toString();
|
|
112
|
-
}
|
|
113
|
-
self.telescope.record({
|
|
114
|
-
type: entry_type_enum_1.EntryType.RABBITMQ,
|
|
115
|
-
content: {
|
|
116
|
-
direction: 'incoming',
|
|
117
|
-
queue,
|
|
118
|
-
exchange: msg.fields?.exchange || '',
|
|
119
|
-
routingKey: msg.fields?.routingKey || queue,
|
|
120
|
-
message: self.sanitizeMessage(parsed),
|
|
121
|
-
deliveryTag: msg.fields?.deliveryTag,
|
|
122
|
-
timestamp: new Date().toISOString(),
|
|
123
|
-
},
|
|
124
|
-
}).catch(() => { });
|
|
125
|
-
return onMessage(msg);
|
|
126
|
-
};
|
|
127
|
-
return originalConsume.call(this, queue, wrappedOnMessage, options);
|
|
128
|
-
};
|
|
129
|
-
}
|
|
130
|
-
sanitizeMessage(msg) {
|
|
131
|
-
if (!msg)
|
|
132
|
-
return null;
|
|
133
|
-
if (typeof msg === 'string') {
|
|
134
|
-
try {
|
|
135
|
-
const parsed = JSON.parse(msg);
|
|
136
|
-
return this.sanitizeMessage(parsed);
|
|
137
|
-
}
|
|
138
|
-
catch {
|
|
139
|
-
return msg.length > 1000 ? msg.substring(0, 1000) + '...[truncated]' : msg;
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
if (Buffer.isBuffer(msg)) {
|
|
143
|
-
return '[Buffer]';
|
|
144
|
-
}
|
|
145
|
-
if (typeof msg === 'object') {
|
|
146
|
-
const sanitized = {};
|
|
147
|
-
for (const [key, value] of Object.entries(msg)) {
|
|
148
|
-
if (key.toLowerCase().includes('password') || key.toLowerCase().includes('secret') || key.toLowerCase().includes('token')) {
|
|
149
|
-
sanitized[key] = '[REDACTED]';
|
|
150
|
-
}
|
|
151
|
-
else if (typeof value === 'string' && value.length > 500) {
|
|
152
|
-
sanitized[key] = value.substring(0, 500) + '...[truncated]';
|
|
153
|
-
}
|
|
154
|
-
else {
|
|
155
|
-
sanitized[key] = value;
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
return sanitized;
|
|
159
176
|
}
|
|
160
|
-
|
|
177
|
+
catch { }
|
|
161
178
|
}
|
|
162
179
|
};
|
|
163
180
|
exports.RabbitMQWatcher = RabbitMQWatcher;
|