@ngn-net/nestjs-telescope 0.3.9 → 0.3.10

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.10",
4
+ "builtAt": "2026-06-13T11:16:48.902Z"
5
5
  }
@@ -5,8 +5,5 @@ 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 patchAmqpConnectionPublish;
12
9
  }
@@ -14,6 +14,107 @@ 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
+ // Patch amqplib.connect at file load time (before any NestJS module initializes)
18
+ // This ensures we intercept channels BEFORE @golevelup/nestjs-rabbitmq creates them
19
+ let telescopeRef = null;
20
+ let amqplibPatched = false;
21
+ function patchAmqplibEarly() {
22
+ if (amqplibPatched)
23
+ return;
24
+ try {
25
+ const amqplib = require('amqplib');
26
+ if (!amqplib || typeof amqplib.connect !== 'function')
27
+ return;
28
+ const originalConnect = amqplib.connect;
29
+ amqplib.connect = async function (...args) {
30
+ const conn = await originalConnect.apply(this, args);
31
+ if (conn && typeof conn.createChannel === 'function') {
32
+ const originalCreateChannel = conn.createChannel.bind(conn);
33
+ conn.createChannel = async function (...channelArgs) {
34
+ const channel = await originalCreateChannel(...channelArgs);
35
+ if (channel && typeof channel.consume === 'function') {
36
+ instrumentChannel(channel);
37
+ }
38
+ return channel;
39
+ };
40
+ }
41
+ return conn;
42
+ };
43
+ amqplibPatched = true;
44
+ }
45
+ catch { }
46
+ }
47
+ function instrumentChannel(channel) {
48
+ if (channel.__telescopePatched)
49
+ return;
50
+ channel.__telescopePatched = true;
51
+ const originalConsume = channel.consume;
52
+ if (!originalConsume)
53
+ return;
54
+ channel.consume = function (queue, onMessage, options) {
55
+ const wrappedOnMessage = (msg) => {
56
+ if (!msg || !msg.content)
57
+ return onMessage(msg);
58
+ if (telescopeRef) {
59
+ const startTime = Date.now();
60
+ let parsed;
61
+ try {
62
+ parsed = JSON.parse(msg.content.toString());
63
+ }
64
+ catch {
65
+ parsed = msg.content.toString();
66
+ }
67
+ telescopeRef.record({
68
+ type: entry_type_enum_1.EntryType.RABBITMQ,
69
+ content: {
70
+ direction: 'incoming',
71
+ queue,
72
+ exchange: msg.fields?.exchange || '',
73
+ routingKey: msg.fields?.routingKey || queue,
74
+ message: sanitizeMessage(parsed),
75
+ deliveryTag: msg.fields?.deliveryTag,
76
+ timestamp: new Date().toISOString(),
77
+ },
78
+ }).catch(() => { });
79
+ }
80
+ return onMessage(msg);
81
+ };
82
+ return originalConsume.call(this, queue, wrappedOnMessage, options);
83
+ };
84
+ }
85
+ function sanitizeMessage(msg) {
86
+ if (!msg)
87
+ return null;
88
+ if (typeof msg === 'string') {
89
+ try {
90
+ const parsed = JSON.parse(msg);
91
+ return sanitizeMessage(parsed);
92
+ }
93
+ catch {
94
+ return msg.length > 1000 ? msg.substring(0, 1000) + '...[truncated]' : msg;
95
+ }
96
+ }
97
+ if (Buffer.isBuffer(msg))
98
+ return '[Buffer]';
99
+ if (typeof msg === 'object') {
100
+ const sanitized = {};
101
+ for (const [key, value] of Object.entries(msg)) {
102
+ if (key.toLowerCase().includes('password') || key.toLowerCase().includes('secret') || key.toLowerCase().includes('token')) {
103
+ sanitized[key] = '[REDACTED]';
104
+ }
105
+ else if (typeof value === 'string' && value.length > 500) {
106
+ sanitized[key] = value.substring(0, 500) + '...[truncated]';
107
+ }
108
+ else {
109
+ sanitized[key] = value;
110
+ }
111
+ }
112
+ return sanitized;
113
+ }
114
+ return msg;
115
+ }
116
+ // Execute the patch immediately when this file is imported
117
+ patchAmqplibEarly();
17
118
  let RabbitMQWatcher = RabbitMQWatcher_1 = class RabbitMQWatcher {
18
119
  telescope;
19
120
  logger = new common_1.Logger(RabbitMQWatcher_1.name);
@@ -21,10 +122,11 @@ let RabbitMQWatcher = RabbitMQWatcher_1 = class RabbitMQWatcher {
21
122
  this.telescope = telescope;
22
123
  }
23
124
  onModuleInit() {
24
- this.patchAmqpConnection();
25
- this.patchAmqplibConnect();
125
+ telescopeRef = this.telescope;
126
+ this.patchAmqpConnectionPublish();
127
+ this.logger.log('RabbitMQWatcher initialized — incoming & outgoing messages will be recorded');
26
128
  }
27
- patchAmqpConnection() {
129
+ patchAmqpConnectionPublish() {
28
130
  try {
29
131
  const { AmqpConnection } = require('@golevelup/nestjs-rabbitmq');
30
132
  if (!AmqpConnection || !AmqpConnection.prototype)
@@ -43,121 +145,15 @@ let RabbitMQWatcher = RabbitMQWatcher_1 = class RabbitMQWatcher {
43
145
  direction: 'outgoing',
44
146
  exchange,
45
147
  routingKey,
46
- message: self.sanitizeMessage(msg),
148
+ message: sanitizeMessage(msg),
47
149
  duration,
48
150
  status: 'sent',
49
151
  },
50
152
  }).catch(() => { });
51
153
  return result;
52
154
  };
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
155
  }
160
- return msg;
156
+ catch { }
161
157
  }
162
158
  };
163
159
  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.10",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },