feat: Added verbose logs

This commit is contained in:
Davidson Gomes 2023-07-06 11:37:30 -03:00
parent d66b751c2e
commit f23ebf1e99
7 changed files with 422 additions and 7 deletions

View File

@ -7,6 +7,7 @@
* Added organization name in vcard * Added organization name in vcard
* Added email in vcard * Added email in vcard
* Added url in vcard * Added url in vcard
* Added verbose logs
### Fixed ### Fixed

View File

@ -2,16 +2,20 @@ import mongoose from 'mongoose';
import { configService, Database } from '../config/env.config'; import { configService, Database } from '../config/env.config';
import { Logger } from '../config/logger.config'; import { Logger } from '../config/logger.config';
const logger = new Logger('Db Connection'); const logger = new Logger('MongoDB');
const db = configService.get<Database>('DATABASE'); const db = configService.get<Database>('DATABASE');
export const dbserver = (() => { export const dbserver = (() => {
if (db.ENABLED) { if (db.ENABLED) {
logger.verbose('connecting');
const dbs = mongoose.createConnection(db.CONNECTION.URI, { const dbs = mongoose.createConnection(db.CONNECTION.URI, {
dbName: db.CONNECTION.DB_PREFIX_NAME + '-whatsapp-api', dbName: db.CONNECTION.DB_PREFIX_NAME + '-whatsapp-api',
}); });
logger.verbose('connected in ' + db.CONNECTION.URI);
logger.info('ON - dbName: ' + dbs['$dbName']); logger.info('ON - dbName: ' + dbs['$dbName']);
process.on('beforeExit', () => { process.on('beforeExit', () => {
logger.verbose('instance destroyed');
dbserver.destroy(true, (error) => logger.error(error)); dbserver.destroy(true, (error) => logger.error(error));
}); });

View File

@ -43,8 +43,12 @@ export class AuthService {
{ expiresIn: jwtOpts.EXPIRIN_IN, encoding: 'utf8', subject: 'g-t' }, { expiresIn: jwtOpts.EXPIRIN_IN, encoding: 'utf8', subject: 'g-t' },
); );
this.logger.verbose('JWT token created: ' + token);
const auth = await this.repository.auth.create({ jwt: token }, instance.instanceName); const auth = await this.repository.auth.create({ jwt: token }, instance.instanceName);
this.logger.verbose('JWT token saved in database');
if (auth['error']) { if (auth['error']) {
this.logger.error({ this.logger.error({
localError: AuthService.name + '.jwt', localError: AuthService.name + '.jwt',
@ -59,8 +63,14 @@ export class AuthService {
private async apikey(instance: InstanceDto, token?: string) { private async apikey(instance: InstanceDto, token?: string) {
const apikey = token ? token : v4().toUpperCase(); const apikey = token ? token : v4().toUpperCase();
this.logger.verbose(
token ? 'APIKEY defined: ' + apikey : 'APIKEY created: ' + apikey,
);
const auth = await this.repository.auth.create({ apikey }, instance.instanceName); const auth = await this.repository.auth.create({ apikey }, instance.instanceName);
this.logger.verbose('APIKEY saved in database');
if (auth['error']) { if (auth['error']) {
this.logger.error({ this.logger.error({
localError: AuthService.name + '.apikey', localError: AuthService.name + '.apikey',
@ -75,54 +85,77 @@ export class AuthService {
public async checkDuplicateToken(token: string) { public async checkDuplicateToken(token: string) {
const instances = await this.waMonitor.instanceInfo(); const instances = await this.waMonitor.instanceInfo();
this.logger.verbose('checking duplicate token');
const instance = instances.find((instance) => instance.instance.apikey === token); const instance = instances.find((instance) => instance.instance.apikey === token);
if (instance) { if (instance) {
throw new BadRequestException('Token already exists'); throw new BadRequestException('Token already exists');
} }
this.logger.verbose('available token');
return true; return true;
} }
public async generateHash(instance: InstanceDto, token?: string) { public async generateHash(instance: InstanceDto, token?: string) {
const options = this.configService.get<Auth>('AUTHENTICATION'); const options = this.configService.get<Auth>('AUTHENTICATION');
this.logger.verbose(
'generating hash ' + options.TYPE + ' to instance: ' + instance.instanceName,
);
return (await this[options.TYPE](instance, token)) as return (await this[options.TYPE](instance, token)) as
| { jwt: string } | { jwt: string }
| { apikey: string }; | { apikey: string };
} }
public async refreshToken({ oldToken }: OldToken) { public async refreshToken({ oldToken }: OldToken) {
this.logger.verbose('refreshing token');
if (!isJWT(oldToken)) { if (!isJWT(oldToken)) {
throw new BadRequestException('Invalid "oldToken"'); throw new BadRequestException('Invalid "oldToken"');
} }
try { try {
const jwtOpts = this.configService.get<Auth>('AUTHENTICATION').JWT; const jwtOpts = this.configService.get<Auth>('AUTHENTICATION').JWT;
this.logger.verbose('checking oldToken');
const decode = verify(oldToken, jwtOpts.SECRET, { const decode = verify(oldToken, jwtOpts.SECRET, {
ignoreExpiration: true, ignoreExpiration: true,
}) as Pick<JwtPayload, 'apiName' | 'instanceName' | 'tokenId'>; }) as Pick<JwtPayload, 'apiName' | 'instanceName' | 'tokenId'>;
this.logger.verbose('checking token in database');
const tokenStore = await this.repository.auth.find(decode.instanceName); const tokenStore = await this.repository.auth.find(decode.instanceName);
const decodeTokenStore = verify(tokenStore.jwt, jwtOpts.SECRET, { const decodeTokenStore = verify(tokenStore.jwt, jwtOpts.SECRET, {
ignoreExpiration: true, ignoreExpiration: true,
}) as Pick<JwtPayload, 'apiName' | 'instanceName' | 'tokenId'>; }) as Pick<JwtPayload, 'apiName' | 'instanceName' | 'tokenId'>;
this.logger.verbose('checking tokenId');
if (decode.tokenId !== decodeTokenStore.tokenId) { if (decode.tokenId !== decodeTokenStore.tokenId) {
throw new BadRequestException('Invalid "oldToken"'); throw new BadRequestException('Invalid "oldToken"');
} }
this.logger.verbose('generating new token');
const token = { const token = {
jwt: (await this.jwt({ instanceName: decode.instanceName })).jwt, jwt: (await this.jwt({ instanceName: decode.instanceName })).jwt,
instanceName: decode.instanceName, instanceName: decode.instanceName,
}; };
try { try {
this.logger.verbose('checking webhook');
const webhook = await this.repository.webhook.find(decode.instanceName); const webhook = await this.repository.webhook.find(decode.instanceName);
if ( if (
webhook?.enabled && webhook?.enabled &&
this.configService.get<Webhook>('WEBHOOK').EVENTS.NEW_JWT_TOKEN this.configService.get<Webhook>('WEBHOOK').EVENTS.NEW_JWT_TOKEN
) { ) {
this.logger.verbose('sending webhook');
const httpService = axios.create({ baseURL: webhook.url }); const httpService = axios.create({ baseURL: webhook.url });
await httpService.post( await httpService.post(
'', '',
@ -138,6 +171,8 @@ export class AuthService {
this.logger.error(error); this.logger.error(error);
} }
this.logger.verbose('token refreshed');
return token; return token;
} catch (error) { } catch (error) {
this.logger.error({ this.logger.error({

View File

@ -24,6 +24,8 @@ export class WAMonitoringService {
private readonly repository: RepositoryBroker, private readonly repository: RepositoryBroker,
private readonly cache: RedisCache, private readonly cache: RedisCache,
) { ) {
this.logger.verbose('instance created');
this.removeInstance(); this.removeInstance();
this.noConnection(); this.noConnection();
this.delInstanceFiles(); this.delInstanceFiles();
@ -47,6 +49,10 @@ export class WAMonitoringService {
public delInstanceTime(instance: string) { public delInstanceTime(instance: string) {
const time = this.configService.get<DelInstance>('DEL_INSTANCE'); const time = this.configService.get<DelInstance>('DEL_INSTANCE');
if (typeof time === 'number' && time > 0) { if (typeof time === 'number' && time > 0) {
this.logger.verbose(
`Instance "${instance}" don't have connection, will be removed in ${time} minutes`,
);
setTimeout(async () => { setTimeout(async () => {
if (this.waInstances[instance]?.connectionStatus?.state !== 'open') { if (this.waInstances[instance]?.connectionStatus?.state !== 'open') {
if (this.waInstances[instance]?.connectionStatus?.state === 'connecting') { if (this.waInstances[instance]?.connectionStatus?.state === 'connecting') {
@ -66,6 +72,7 @@ export class WAMonitoringService {
} }
public async instanceInfo(instanceName?: string) { public async instanceInfo(instanceName?: string) {
this.logger.verbose('get instance info');
if (instanceName && !this.waInstances[instanceName]) { if (instanceName && !this.waInstances[instanceName]) {
throw new NotFoundException(`Instance "${instanceName}" not found`); throw new NotFoundException(`Instance "${instanceName}" not found`);
} }
@ -74,9 +81,14 @@ export class WAMonitoringService {
for await (const [key, value] of Object.entries(this.waInstances)) { for await (const [key, value] of Object.entries(this.waInstances)) {
if (value) { if (value) {
this.logger.verbose('get instance info: ' + key);
if (value.connectionStatus.state === 'open') { if (value.connectionStatus.state === 'open') {
this.logger.verbose('instance: ' + key + ' - connectionStatus: open');
let apikey: string; let apikey: string;
if (this.configService.get<Auth>('AUTHENTICATION').EXPOSE_IN_FETCH_INSTANCES) { if (this.configService.get<Auth>('AUTHENTICATION').EXPOSE_IN_FETCH_INSTANCES) {
this.logger.verbose(
'instance: ' + key + ' - hash exposed in fetch instances',
);
const tokenStore = await this.repository.auth.find(key); const tokenStore = await this.repository.auth.find(key);
apikey = tokenStore.apikey || 'Apikey not found'; apikey = tokenStore.apikey || 'Apikey not found';
@ -91,6 +103,9 @@ export class WAMonitoringService {
}, },
}); });
} else { } else {
this.logger.verbose(
'instance: ' + key + ' - hash not exposed in fetch instances',
);
instances.push({ instances.push({
instance: { instance: {
instanceName: key, instanceName: key,
@ -102,8 +117,14 @@ export class WAMonitoringService {
}); });
} }
} else { } else {
this.logger.verbose(
'instance: ' + key + ' - connectionStatus: ' + value.connectionStatus.state,
);
let apikey: string; let apikey: string;
if (this.configService.get<Auth>('AUTHENTICATION').EXPOSE_IN_FETCH_INSTANCES) { if (this.configService.get<Auth>('AUTHENTICATION').EXPOSE_IN_FETCH_INSTANCES) {
this.logger.verbose(
'instance: ' + key + ' - hash exposed in fetch instances',
);
const tokenStore = await this.repository.auth.find(key); const tokenStore = await this.repository.auth.find(key);
apikey = tokenStore.apikey || 'Apikey not found'; apikey = tokenStore.apikey || 'Apikey not found';
@ -115,6 +136,9 @@ export class WAMonitoringService {
}, },
}); });
} else { } else {
this.logger.verbose(
'instance: ' + key + ' - hash not exposed in fetch instances',
);
instances.push({ instances.push({
instance: { instance: {
instanceName: key, instanceName: key,
@ -126,10 +150,13 @@ export class WAMonitoringService {
} }
} }
this.logger.verbose('return instance info: ' + instances.length);
return instances.find((i) => i.instance.instanceName === instanceName) ?? instances; return instances.find((i) => i.instance.instanceName === instanceName) ?? instances;
} }
private delInstanceFiles() { private delInstanceFiles() {
this.logger.verbose('cron to delete instance files started');
setInterval(async () => { setInterval(async () => {
if (this.db.ENABLED && this.db.SAVE_DATA.INSTANCE) { if (this.db.ENABLED && this.db.SAVE_DATA.INSTANCE) {
const collections = await this.dbInstance.collections(); const collections = await this.dbInstance.collections();
@ -141,6 +168,7 @@ export class WAMonitoringService {
{ _id: { $regex: /^session-.*/ } }, { _id: { $regex: /^session-.*/ } },
], ],
}); });
this.logger.verbose('instance files deleted: ' + name);
}); });
} else if (this.redis.ENABLED) { } else if (this.redis.ENABLED) {
} else { } else {
@ -158,6 +186,7 @@ export class WAMonitoringService {
}); });
} }
}); });
this.logger.verbose('instance files deleted: ' + dirent.name);
} }
} }
} }
@ -165,7 +194,9 @@ export class WAMonitoringService {
} }
public async cleaningUp(instanceName: string) { public async cleaningUp(instanceName: string) {
this.logger.verbose('cleaning up instance: ' + instanceName);
if (this.db.ENABLED && this.db.SAVE_DATA.INSTANCE) { if (this.db.ENABLED && this.db.SAVE_DATA.INSTANCE) {
this.logger.verbose('cleaning up instance in database: ' + instanceName);
await this.repository.dbServer.connect(); await this.repository.dbServer.connect();
const collections: any[] = await this.dbInstance.collections(); const collections: any[] = await this.dbInstance.collections();
if (collections.length > 0) { if (collections.length > 0) {
@ -175,14 +206,18 @@ export class WAMonitoringService {
} }
if (this.redis.ENABLED) { if (this.redis.ENABLED) {
this.logger.verbose('cleaning up instance in redis: ' + instanceName);
this.cache.reference = instanceName; this.cache.reference = instanceName;
await this.cache.delAll(); await this.cache.delAll();
return; return;
} }
this.logger.verbose('cleaning up instance in files: ' + instanceName);
rmSync(join(INSTANCE_DIR, instanceName), { recursive: true, force: true }); rmSync(join(INSTANCE_DIR, instanceName), { recursive: true, force: true });
} }
public async loadInstance() { public async loadInstance() {
this.logger.verbose('load instances');
const set = async (name: string) => { const set = async (name: string) => {
const instance = new WAStartupService( const instance = new WAStartupService(
this.configService, this.configService,
@ -191,38 +226,50 @@ export class WAMonitoringService {
this.cache, this.cache,
); );
instance.instanceName = name; instance.instanceName = name;
this.logger.verbose('instance loaded: ' + name);
await instance.connectToWhatsapp(); await instance.connectToWhatsapp();
this.logger.verbose('connectToWhatsapp: ' + name);
this.waInstances[name] = instance; this.waInstances[name] = instance;
}; };
try { try {
if (this.redis.ENABLED) { if (this.redis.ENABLED) {
this.logger.verbose('redis enabled');
await this.cache.connect(this.redis as Redis); await this.cache.connect(this.redis as Redis);
const keys = await this.cache.instanceKeys(); const keys = await this.cache.instanceKeys();
if (keys?.length > 0) { if (keys?.length > 0) {
this.logger.verbose('reading instance keys and setting instances');
keys.forEach(async (k) => await set(k.split(':')[1])); keys.forEach(async (k) => await set(k.split(':')[1]));
} else { } else {
this.logger.verbose('no instance keys found');
initInstance(); initInstance();
} }
return; return;
} }
if (this.db.ENABLED && this.db.SAVE_DATA.INSTANCE) { if (this.db.ENABLED && this.db.SAVE_DATA.INSTANCE) {
this.logger.verbose('database enabled');
await this.repository.dbServer.connect(); await this.repository.dbServer.connect();
const collections: any[] = await this.dbInstance.collections(); const collections: any[] = await this.dbInstance.collections();
if (collections.length > 0) { if (collections.length > 0) {
this.logger.verbose('reading collections and setting instances');
collections.forEach( collections.forEach(
async (coll) => await set(coll.namespace.replace(/^[\w-]+\./, '')), async (coll) => await set(coll.namespace.replace(/^[\w-]+\./, '')),
); );
} else { } else {
this.logger.verbose('no collections found');
initInstance(); initInstance();
} }
return; return;
} }
this.logger.verbose('store in files enabled');
const dir = opendirSync(INSTANCE_DIR, { encoding: 'utf-8' }); const dir = opendirSync(INSTANCE_DIR, { encoding: 'utf-8' });
for await (const dirent of dir) { for await (const dirent of dir) {
if (dirent.isDirectory()) { if (dirent.isDirectory()) {
this.logger.verbose('reading instance files and setting instances');
const files = readdirSync(join(INSTANCE_DIR, dirent.name), { const files = readdirSync(join(INSTANCE_DIR, dirent.name), {
encoding: 'utf-8', encoding: 'utf-8',
}); });
@ -233,6 +280,7 @@ export class WAMonitoringService {
await set(dirent.name); await set(dirent.name);
} else { } else {
this.logger.verbose('no instance files found');
initInstance(); initInstance();
} }
} }
@ -243,18 +291,23 @@ export class WAMonitoringService {
private removeInstance() { private removeInstance() {
this.eventEmitter.on('remove.instance', async (instanceName: string) => { this.eventEmitter.on('remove.instance', async (instanceName: string) => {
this.logger.verbose('remove instance: ' + instanceName);
try { try {
this.logger.verbose('instance: ' + instanceName + ' - removing from memory');
this.waInstances[instanceName] = undefined; this.waInstances[instanceName] = undefined;
} catch {} } catch {}
try { try {
this.logger.verbose('request cleaning up instance: ' + instanceName);
this.cleaningUp(instanceName); this.cleaningUp(instanceName);
} finally { } finally {
this.logger.warn(`Instance "${instanceName}" - REMOVED`); this.logger.warn(`Instance "${instanceName}" - REMOVED`);
} }
}); });
this.eventEmitter.on('logout.instance', async (instanceName: string) => { this.eventEmitter.on('logout.instance', async (instanceName: string) => {
this.logger.verbose('logout instance: ' + instanceName);
try { try {
this.logger.verbose('request cleaning up instance: ' + instanceName);
this.cleaningUp(instanceName); this.cleaningUp(instanceName);
} finally { } finally {
this.logger.warn(`Instance "${instanceName}" - LOGOUT`); this.logger.warn(`Instance "${instanceName}" - LOGOUT`);
@ -263,9 +316,13 @@ export class WAMonitoringService {
} }
private noConnection() { private noConnection() {
this.logger.verbose('checking instances without connection');
this.eventEmitter.on('no.connection', async (instanceName) => { this.eventEmitter.on('no.connection', async (instanceName) => {
try { try {
this.logger.verbose('instance: ' + instanceName + ' - removing from memory');
this.waInstances[instanceName] = undefined; this.waInstances[instanceName] = undefined;
this.logger.verbose('request cleaning up instance: ' + instanceName);
this.cleaningUp(instanceName); this.cleaningUp(instanceName);
} catch (error) { } catch (error) {
this.logger.error({ this.logger.error({

View File

@ -1,11 +1,15 @@
import { InstanceDto } from '../dto/instance.dto'; import { InstanceDto } from '../dto/instance.dto';
import { WebhookDto } from '../dto/webhook.dto'; import { WebhookDto } from '../dto/webhook.dto';
import { WAMonitoringService } from './monitor.service'; import { WAMonitoringService } from './monitor.service';
import { Logger } from '../../config/logger.config';
export class WebhookService { export class WebhookService {
constructor(private readonly waMonitor: WAMonitoringService) {} constructor(private readonly waMonitor: WAMonitoringService) {}
private readonly logger = new Logger(WebhookService.name);
public create(instance: InstanceDto, data: WebhookDto) { public create(instance: InstanceDto, data: WebhookDto) {
this.logger.verbose('create webhook: ' + instance.instanceName);
this.waMonitor.waInstances[instance.instanceName].setWebhook(data); this.waMonitor.waInstances[instance.instanceName].setWebhook(data);
return { webhook: { ...instance, webhook: data } }; return { webhook: { ...instance, webhook: data } };
@ -13,6 +17,7 @@ export class WebhookService {
public async find(instance: InstanceDto): Promise<WebhookDto> { public async find(instance: InstanceDto): Promise<WebhookDto> {
try { try {
this.logger.verbose('find webhook: ' + instance.instanceName);
return await this.waMonitor.waInstances[instance.instanceName].findWebhook(); return await this.waMonitor.waInstances[instance.instanceName].findWebhook();
} catch (error) { } catch (error) {
return { enabled: null, url: '' }; return { enabled: null, url: '' };

File diff suppressed because it is too large Load Diff

View File

@ -85,6 +85,7 @@ export async function initInstance() {
const mode = configService.get<Auth>('AUTHENTICATION').INSTANCE.MODE; const mode = configService.get<Auth>('AUTHENTICATION').INSTANCE.MODE;
logger.verbose('Sending data webhook for event: ' + Events.APPLICATION_STARTUP);
instance.sendDataWebhook( instance.sendDataWebhook(
Events.APPLICATION_STARTUP, Events.APPLICATION_STARTUP,
{ {
@ -95,9 +96,14 @@ export async function initInstance() {
); );
if (mode === 'container') { if (mode === 'container') {
logger.verbose('Application startup in container mode');
const instanceName = configService.get<Auth>('AUTHENTICATION').INSTANCE.NAME; const instanceName = configService.get<Auth>('AUTHENTICATION').INSTANCE.NAME;
logger.verbose('Instance name: ' + instanceName);
const instanceWebhook = const instanceWebhook =
configService.get<Auth>('AUTHENTICATION').INSTANCE.WEBHOOK_URL; configService.get<Auth>('AUTHENTICATION').INSTANCE.WEBHOOK_URL;
logger.verbose('Instance webhook: ' + instanceWebhook);
instance.instanceName = instanceName; instance.instanceName = instanceName;
@ -106,13 +112,17 @@ export async function initInstance() {
const hash = await authService.generateHash({ const hash = await authService.generateHash({
instanceName: instance.instanceName, instanceName: instance.instanceName,
token: configService.get<Auth>('AUTHENTICATION').API_KEY.KEY,
}); });
logger.verbose('Hash generated: ' + hash);
if (instanceWebhook) { if (instanceWebhook) {
logger.verbose('Creating webhook for instance: ' + instanceName);
try { try {
webhookService.create(instance, { enabled: true, url: instanceWebhook }); webhookService.create(instance, { enabled: true, url: instanceWebhook });
logger.verbose('Webhook created');
} catch (error) { } catch (error) {
this.logger.log(error); logger.log(error);
} }
} }
@ -130,7 +140,7 @@ export async function initInstance() {
return await this.connectionState({ instanceName }); return await this.connectionState({ instanceName });
} }
} catch (error) { } catch (error) {
this.logger.log(error); logger.log(error);
} }
const result = { const result = {