Skip to content

Commit 1a83400

Browse files
author
Artem
committed
new connection to monitor client mechanism
1 parent 966ce80 commit 1a83400

File tree

12 files changed

+180
-40
lines changed

12 files changed

+180
-40
lines changed

redisinsight/api/config/default.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ export default {
9696
writeKey: process.env.SEGMENT_WRITE_KEY || 'SOURCE_WRITE_KEY',
9797
},
9898
logger: {
99+
logLevel: process.env.LOG_LEVEL || 'info', // log level
99100
stdout: process.env.STDOUT_LOGGER ? process.env.STDOUT_LOGGER === 'true' : false, // disabled by default
100101
files: process.env.FILES_LOGGER ? process.env.FILES_LOGGER === 'true' : true, // enabled by default
101102
omitSensitiveData: process.env.LOGGER_OMIT_DATA ? process.env.LOGGER_OMIT_DATA === 'true' : true,

redisinsight/api/config/development.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ export default {
1010
migrationsRun: process.env.DB_MIGRATIONS ? process.env.DB_MIGRATIONS === 'true' : false,
1111
},
1212
logger: {
13+
logLevel: process.env.LOG_LEVEL || 'debug',
1314
stdout: process.env.STDOUT_LOGGER ? process.env.STDOUT_LOGGER === 'true' : true, // enabled by default
1415
omitSensitiveData: process.env.LOGGER_OMIT_DATA ? process.env.LOGGER_OMIT_DATA === 'true' : false,
1516
},

redisinsight/api/config/logger.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ if (LOGGER_CONFIG.files) {
5858
const logger: WinstonModuleOptions = {
5959
format: format.errors({ stack: true }),
6060
transports: transportsConfig,
61+
level: LOGGER_CONFIG.logLevel,
6162
};
6263

6364
export default logger;

redisinsight/api/src/__mocks__/monitor.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { v4 as uuidv4 } from 'uuid';
22
import { IClientMonitorObserver } from 'src/modules/profiler/helpers/client-monitor-observer';
33
import { IMonitorObserver, IShardObserver, MonitorObserverStatus } from 'src/modules/profiler/helpers/monitor-observer';
4+
import { ILogsEmitter } from 'src/modules/profiler/interfaces/logs-emitter.interface';
45

56
export const mockClientMonitorObserver: IClientMonitorObserver = {
67
id: uuidv4(),
@@ -34,3 +35,11 @@ export const mockRedisMonitorObserver: IShardObserver = {
3435
once: jest.fn(),
3536
disconnect: jest.fn(),
3637
};
38+
39+
export const mockLogEmitter: ILogsEmitter = {
40+
id: 'test',
41+
emit: jest.fn(),
42+
addProfilerClient: jest.fn(),
43+
removeProfilerClient: jest.fn(),
44+
flushLogs: jest.fn(),
45+
};

redisinsight/api/src/modules/profiler/constants/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ export enum ProfilerServerEvents {
1010
}
1111

1212
export enum RedisObserverStatus {
13+
Empty = 'empty',
14+
Initializing = 'initializing',
15+
Connected = 'connected',
1316
Wait = 'wait',
1417
Ready = 'ready',
1518
End = 'end',

redisinsight/api/src/modules/profiler/models/log-file.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ export class LogFile {
3838
if (!this.writeStream) {
3939
this.writeStream = fs.createWriteStream(this.filePath, { flags: 'a' });
4040
}
41-
41+
this.writeStream.on('error', () => {});
4242
return this.writeStream;
4343
}
4444

redisinsight/api/src/modules/profiler/models/profiler.client.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
import { Socket } from 'socket.io';
22
import { debounce } from 'lodash';
33
import { WsException } from '@nestjs/websockets';
4+
import { Logger } from '@nestjs/common';
45
import { ProfilerServerEvents } from 'src/modules/profiler/constants';
56
import { ILogsEmitter } from 'src/modules/profiler/interfaces/logs-emitter.interface';
67
import { IMonitorData } from 'src/modules/profiler/interfaces/monitor-data.interface';
78
import ERROR_MESSAGES from 'src/constants/error-messages';
89

910
export class ProfilerClient {
11+
private logger = new Logger('ProfilerClient');
12+
1013
public readonly id: string;
1114

1215
private readonly client: Socket;
@@ -57,6 +60,7 @@ export class ProfilerClient {
5760
public addLogsEmitter(emitter: ILogsEmitter) {
5861
this.logsEmitters.set(emitter.id, emitter);
5962
emitter.addProfilerClient(this.id);
63+
this.logCurrentState();
6064
}
6165

6266
async flushLogs() {
@@ -66,4 +70,14 @@ export class ProfilerClient {
6670
public destroy() {
6771
this.logsEmitters.forEach((emitter) => emitter.removeProfilerClient(this.id));
6872
}
73+
74+
/**
75+
* Logs useful information about current state for debug purposes
76+
* @private
77+
*/
78+
private logCurrentState() {
79+
this.logger.debug(
80+
`Emitters: ${this.logsEmitters.size}`,
81+
);
82+
}
6983
}

redisinsight/api/src/modules/profiler/models/redis.observer.ts

Lines changed: 88 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,42 @@
11
import IORedis from 'ioredis';
2-
import { ForbiddenException, ServiceUnavailableException } from '@nestjs/common';
2+
import { ForbiddenException, Logger, ServiceUnavailableException } from '@nestjs/common';
33
import { RedisErrorCodes } from 'src/constants';
44
import { ProfilerClient } from 'src/modules/profiler/models/profiler.client';
55
import { RedisObserverStatus } from 'src/modules/profiler/constants';
66
import { IShardObserver } from 'src/modules/profiler/interfaces/shard-observer.interface';
77
import ERROR_MESSAGES from 'src/constants/error-messages';
8+
import { EventEmitter2 } from '@nestjs/event-emitter';
89

9-
export class RedisObserver {
10-
private readonly redis: IORedis.Redis | IORedis.Cluster;
10+
export class RedisObserver extends EventEmitter2 {
11+
private logger = new Logger('RedisObserver');
12+
13+
private redis: IORedis.Redis | IORedis.Cluster;
1114

1215
private profilerClients: Map<string, ProfilerClient> = new Map();
1316

17+
private profilerClientsListeners: Map<string, any[]> = new Map();
18+
1419
private shardsObservers: IShardObserver[] = [];
1520

1621
public status: RedisObserverStatus;
1722

18-
constructor(redis: IORedis.Redis | IORedis.Cluster) {
19-
this.redis = redis;
20-
this.status = RedisObserverStatus.Wait;
23+
constructor() {
24+
super();
25+
this.status = RedisObserverStatus.Empty;
26+
}
27+
28+
init(func: () => Promise<IORedis.Redis | IORedis.Cluster>) {
29+
this.status = RedisObserverStatus.Initializing;
30+
31+
func()
32+
.then((redis) => {
33+
this.redis = redis;
34+
this.status = RedisObserverStatus.Connected;
35+
this.emit('connect');
36+
})
37+
.catch((err) => {
38+
this.emit('connect_error', err);
39+
});
2140
}
2241

2342
/**
@@ -35,36 +54,84 @@ export class RedisObserver {
3554
return;
3655
}
3756

57+
if (!this.profilerClientsListeners.get(profilerClient.id)) {
58+
this.profilerClientsListeners.set(profilerClient.id, []);
59+
}
60+
61+
const profilerListeners = this.profilerClientsListeners.get(profilerClient.id);
62+
3863
this.shardsObservers.forEach((observer) => {
39-
observer.on('monitor', (time, args, source, database) => {
64+
const monitorListenerFn = (time, args, source, database) => {
4065
profilerClient.handleOnData({
4166
time, args, database, source, shardOptions: observer.options,
4267
});
43-
});
44-
observer.on('end', () => {
68+
};
69+
const endListenerFn = () => {
4570
profilerClient.handleOnDisconnect();
4671
this.clear();
47-
});
72+
};
73+
74+
observer.on('monitor', monitorListenerFn);
75+
observer.on('end', endListenerFn);
76+
77+
profilerListeners.push(monitorListenerFn, endListenerFn);
78+
this.logger.debug(`Subscribed to shard observer. Current listeners: ${observer.listenerCount('monitor')}`);
4879
});
4980
this.profilerClients.set(profilerClient.id, profilerClient);
81+
82+
this.logger.debug(`Profiler Client with id:${profilerClient.id} was added`);
83+
this.logCurrentState();
84+
}
85+
86+
public removeShardsListeners(profilerClientId: string) {
87+
this.shardsObservers.forEach((observer) => {
88+
(this.profilerClientsListeners.get(profilerClientId) || []).forEach((listener) => {
89+
observer.removeListener('monitor', listener);
90+
observer.removeListener('end', listener);
91+
});
92+
93+
this.logger.debug(
94+
`Unsubscribed from from shard observer. Current listeners: ${observer.listenerCount('monitor')}`,
95+
);
96+
});
5097
}
5198

5299
public unsubscribe(id: string) {
100+
this.removeShardsListeners(id);
53101
this.profilerClients.delete(id);
102+
this.profilerClientsListeners.delete(id);
54103
if (this.profilerClients.size === 0) {
55104
this.clear();
56105
}
106+
107+
this.logger.debug(`Profiler Client with id:${id} was unsubscribed`);
108+
this.logCurrentState();
57109
}
58110

59111
public disconnect(id: string) {
60-
const userClient = this.profilerClients.get(id);
61-
if (userClient) {
62-
userClient.destroy();
112+
this.removeShardsListeners(id);
113+
const profilerClient = this.profilerClients.get(id);
114+
if (profilerClient) {
115+
profilerClient.destroy();
63116
}
64117
this.profilerClients.delete(id);
118+
this.profilerClientsListeners.delete(id);
65119
if (this.profilerClients.size === 0) {
66120
this.clear();
67121
}
122+
123+
this.logger.debug(`Profiler Client with id:${id} was disconnected`);
124+
this.logCurrentState();
125+
}
126+
127+
/**
128+
* Logs useful inforation about current state for debug purposes
129+
* @private
130+
*/
131+
private logCurrentState() {
132+
this.logger.debug(
133+
`Status: ${this.status}; Shards: ${this.shardsObservers.length}; Listeners: ${this.getProfilerClientsSize()}`,
134+
);
68135
}
69136

70137
public clear() {
@@ -98,6 +165,13 @@ export class RedisObserver {
98165
} else {
99166
this.shardsObservers = [await RedisObserver.createShardObserver(this.redis)];
100167
}
168+
169+
this.shardsObservers.forEach((observer) => {
170+
observer.on('error', (e) => {
171+
this.logger.error('Error on shard observer', e);
172+
});
173+
});
174+
101175
this.status = RedisObserverStatus.Ready;
102176
} catch (error) {
103177
this.status = RedisObserverStatus.Error;
@@ -130,6 +204,7 @@ export class RedisObserver {
130204
...redis.options,
131205
monitor: false,
132206
lazyLoading: false,
207+
connectionName: `redisinsight-monitor-perm-check-${Math.random()}`,
133208
});
134209

135210
await duplicate.send_command('monitor');

redisinsight/api/src/modules/profiler/profiler.controller.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ export class ProfilerController {
2525
res.setHeader('Content-Type', 'application/octet-stream');
2626
res.setHeader('Content-Disposition', `attachment;filename="${filename}.txt"`);
2727

28-
stream.pipe(res);
28+
stream
29+
.on('error', () => res.status(404).send())
30+
.pipe(res);
2931
}
3032
}

redisinsight/api/src/modules/profiler/providers/log-file.provider.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ export class LogFileProvider implements OnModuleDestroy {
1111
* Get or create Profiler Log File to work with
1212
* @param id
1313
*/
14-
async getOrCreate(id: string): Promise<LogFile> {
14+
getOrCreate(id: string): LogFile {
1515
if (!this.profilerLogFiles.has(id)) {
1616
this.profilerLogFiles.set(id, new LogFile(id));
1717
}
@@ -23,7 +23,7 @@ export class LogFileProvider implements OnModuleDestroy {
2323
* Get Profiler Log File or throw an error
2424
* @param id
2525
*/
26-
async get(id: string): Promise<LogFile> {
26+
get(id: string): LogFile {
2727
if (!this.profilerLogFiles.has(id)) {
2828
throw new NotFoundException(ERROR_MESSAGES.PROFILER_LOG_FILE_NOT_FOUND);
2929
}

0 commit comments

Comments
 (0)