@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.
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "name": "@ngn-net/nestjs-telescope",
3
- "version": "0.3.9",
4
- "builtAt": "2026-06-13T11:13:25.789Z"
3
+ "version": "0.3.11",
4
+ "builtAt": "2026-06-13T11:21:01.736Z"
5
5
  }
@@ -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 patchAmqpConnection;
9
- private patchAmqplibConnect;
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.patchAmqpConnection();
25
- this.patchAmqplibConnect();
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
- patchAmqpConnection() {
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: self.sanitizeMessage(msg),
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
- return msg;
177
+ catch { }
161
178
  }
162
179
  };
163
180
  exports.RabbitMQWatcher = RabbitMQWatcher;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ngn-net/nestjs-telescope",
3
- "version": "0.3.9",
3
+ "version": "0.3.11",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },