From f52c82e82a84018bdc6cbad499bf0dba8f7b87d3 Mon Sep 17 00:00:00 2001 From: Mohamed Marrouchi Date: Mon, 27 Jan 2025 10:46:14 +0100 Subject: [PATCH 1/8] fix: logger ctx --- .../controllers/attachment.controller.spec.ts | 2 +- api/src/logger/logger.service.ts | 12 +++++++++--- api/src/main.ts | 9 +++++---- api/src/seeder.ts | 2 +- 4 files changed, 16 insertions(+), 9 deletions(-) diff --git a/api/src/attachment/controllers/attachment.controller.spec.ts b/api/src/attachment/controllers/attachment.controller.spec.ts index 124bfc13..eb13cdb1 100644 --- a/api/src/attachment/controllers/attachment.controller.spec.ts +++ b/api/src/attachment/controllers/attachment.controller.spec.ts @@ -108,7 +108,7 @@ describe('AttachmentController', () => { helperService = module.get(HelperService); settingService = module.get(SettingService); - loggerService = module.get(LoggerService); + loggerService = await module.resolve(LoggerService); helperService.register( new LocalStorageHelper(settingService, helperService, loggerService), diff --git a/api/src/logger/logger.service.ts b/api/src/logger/logger.service.ts index be15d874..70a429a6 100644 --- a/api/src/logger/logger.service.ts +++ b/api/src/logger/logger.service.ts @@ -1,11 +1,17 @@ /* - * Copyright © 2024 Hexastack. All rights reserved. + * Copyright © 2025 Hexastack. All rights reserved. * * Licensed under the GNU Affero General Public License v3.0 (AGPLv3) with the following additional terms: * 1. The name "Hexabot" is a trademark of Hexastack. You may not use this name in derivative works without express written permission. * 2. All derivative works must include clear attribution to the original creator and software, Hexastack and Hexabot, in a prominent location (e.g., in the software's "About" section, documentation, and README file). */ -import { ConsoleLogger } from '@nestjs/common'; +import { ConsoleLogger, Inject, Injectable, Scope } from '@nestjs/common'; +import { INQUIRER } from '@nestjs/core'; -export class LoggerService extends ConsoleLogger {} +@Injectable({ scope: Scope.TRANSIENT }) +export class LoggerService extends ConsoleLogger { + constructor(@Inject(INQUIRER) private parentClass: object) { + super(parentClass.constructor.name); + } +} diff --git a/api/src/main.ts b/api/src/main.ts index 507c3361..1ba080a4 100644 --- a/api/src/main.ts +++ b/api/src/main.ts @@ -101,10 +101,11 @@ async function bootstrap() { app.useWebSocketAdapter(redisIoAdapter); } - process.on('uncaughtException', (error) => { - if (error.stack?.toLowerCase().includes('smtp')) - app.get(LoggerService).error('SMTP error', error.stack); - else throw error; + process.on('uncaughtException', async (error) => { + if (error.stack?.toLowerCase().includes('smtp')) { + const logger = await app.resolve(LoggerService); + logger.error('SMTP error', error.stack); + } else throw error; }); if (!isProduction) { diff --git a/api/src/seeder.ts b/api/src/seeder.ts index 1811ae3f..c3abfaac 100644 --- a/api/src/seeder.ts +++ b/api/src/seeder.ts @@ -37,7 +37,7 @@ import { UserSeeder } from './user/seeds/user.seed'; import { userModels } from './user/seeds/user.seed-model'; export async function seedDatabase(app: INestApplicationContext) { - const logger = app.get(LoggerService); + const logger = await app.resolve(LoggerService); const modelSeeder = app.get(ModelSeeder); const categorySeeder = app.get(CategorySeeder); const contextVarSeeder = app.get(ContextVarSeeder); From 988141f5a2e56aed2e322dc115d37dd77f6e81e8 Mon Sep 17 00:00:00 2001 From: Mohamed Marrouchi Date: Mon, 27 Jan 2025 10:46:40 +0100 Subject: [PATCH 2/8] fix: logger ctx --- api/src/migration/migration.service.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/src/migration/migration.service.spec.ts b/api/src/migration/migration.service.spec.ts index 724bd798..1abd7464 100644 --- a/api/src/migration/migration.service.spec.ts +++ b/api/src/migration/migration.service.spec.ts @@ -77,7 +77,7 @@ describe('MigrationService', () => { }).compile(); service = module.get(MigrationService); - loggerService = module.get(LoggerService); + loggerService = await module.resolve(LoggerService); metadataService = module.get(MetadataService); }); From da1b1711f6a2933c4e95b48c9849a8881ef82e85 Mon Sep 17 00:00:00 2001 From: abdou6666 Date: Mon, 27 Jan 2025 12:34:57 +0100 Subject: [PATCH 3/8] feat: update logging logic --- api/src/logger/logger.service.ts | 71 ++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) diff --git a/api/src/logger/logger.service.ts b/api/src/logger/logger.service.ts index 70a429a6..cf6bc358 100644 --- a/api/src/logger/logger.service.ts +++ b/api/src/logger/logger.service.ts @@ -9,9 +9,80 @@ import { ConsoleLogger, Inject, Injectable, Scope } from '@nestjs/common'; import { INQUIRER } from '@nestjs/core'; +type TLog = 'log' | 'warn' | 'error' | 'verbose' | 'debug'; + @Injectable({ scope: Scope.TRANSIENT }) export class LoggerService extends ConsoleLogger { constructor(@Inject(INQUIRER) private parentClass: object) { super(parentClass.constructor.name); } + + log(message: any, ...args: any[]) { + super.log(message); + this.logArguments('log', args); + } + + error(message: any, ...args: any[]) { + super.error(message); + this.logArguments('error', args); + } + + warn(message: any, ...args: any[]) { + super.warn(message); + this.logArguments('warn', args); + } + + debug(message: any, ...args: any[]) { + super.debug(message); + this.logArguments('debug', args); + } + + verbose(message: any, ...args: any[]) { + super.verbose(message); + this.logArguments('verbose', args); + } + + private safeStringifyReplacer() { + const seen = new WeakSet(); + return (key: string, value: any) => { + if (typeof value === 'object' && value !== null) { + if (seen.has(value)) return '[Circular Reference]'; + seen.add(value); + } + return value; + }; + } + + private handleError(type: TLog, error: Error) { + const errorInfo = { + message: error.message, + name: error.name, + stack: error.stack, + }; + super[type](JSON.stringify(errorInfo, this.safeStringifyReplacer())); + } + + private logArguments(type: TLog, args: any[]) { + const isDevMode = process.env.NODE_ENV?.includes('dev'); + if (isDevMode) { + args.forEach((arg) => { + if (arg instanceof Error) { + this.handleError(type, arg); + } else if (typeof arg === 'object' && arg !== null) { + super[type](JSON.stringify(arg, this.safeStringifyReplacer())); + } else { + super[type](arg); + } + }); + } else { + // In production we do not stringify the arguments for performance reasons, except for errors + args.forEach((arg) => { + if (arg instanceof Error) { + this.handleError(type, arg); + } else { + super[type](arg); + } + }); + } + } } From 32d57874cd83b6000207a9570cbbbefc97bebaca Mon Sep 17 00:00:00 2001 From: abdou6666 Date: Mon, 27 Jan 2025 12:36:50 +0100 Subject: [PATCH 4/8] fix: remove service name prefix from logged messages --- .../analytics/services/bot-stats.service.ts | 6 +- .../chat/controllers/message.controller.ts | 4 +- api/src/chat/services/block.service.ts | 2 +- api/src/chat/services/bot.service.ts | 18 +-- api/src/chat/services/message.service.ts | 7 +- api/src/chat/services/subscriber.service.ts | 4 +- .../channels/web/base-web-channel.ts | 129 +++++------------- 7 files changed, 50 insertions(+), 120 deletions(-) diff --git a/api/src/analytics/services/bot-stats.service.ts b/api/src/analytics/services/bot-stats.service.ts index 7c47b457..2b319a66 100644 --- a/api/src/analytics/services/bot-stats.service.ts +++ b/api/src/analytics/services/bot-stats.service.ts @@ -1,5 +1,5 @@ /* - * Copyright © 2024 Hexastack. All rights reserved. + * Copyright © 2025 Hexastack. All rights reserved. * * Licensed under the GNU Affero General Public License v3.0 (AGPLv3) with the following additional terms: * 1. The name "Hexabot" is a trademark of Hexastack. You may not use this name in derivative works without express written permission. @@ -127,10 +127,10 @@ export class BotStatsService extends BaseService { try { await this.updateOne(insight.id, { value: insight.value + 1 }); } catch (err) { - this.logger.error('Stats hook : Unable to update insight', err); + this.logger.error('Unable to update insight', err); } } catch (err) { - this.logger.error('Stats hook : Unable to find or create insight', err); + this.logger.error('Unable to find or create insight', err); } } } diff --git a/api/src/chat/controllers/message.controller.ts b/api/src/chat/controllers/message.controller.ts index ca34cd55..dda5430d 100644 --- a/api/src/chat/controllers/message.controller.ts +++ b/api/src/chat/controllers/message.controller.ts @@ -1,5 +1,5 @@ /* - * Copyright © 2024 Hexastack. All rights reserved. + * Copyright © 2025 Hexastack. All rights reserved. * * Licensed under the GNU Affero General Public License v3.0 (AGPLv3) with the following additional terms: * 1. The name "Hexabot" is a trademark of Hexastack. You may not use this name in derivative works without express written permission. @@ -173,7 +173,7 @@ export class MessageController extends BaseController< success: true, }; } catch (err) { - this.logger.debug('MessageController send : Unable to send message', err); + this.logger.debug('Unable to send message', err); throw new BadRequestException( 'MessageController send : unable to send message', ); diff --git a/api/src/chat/services/block.service.ts b/api/src/chat/services/block.service.ts index 2b6f6346..c7fedabf 100644 --- a/api/src/chat/services/block.service.ts +++ b/api/src/chat/services/block.service.ts @@ -286,7 +286,7 @@ export class BlockService extends BaseService< return e.entity === ev.entity; }); } else { - this.logger.warn('Block Service : Unknown NLP match type', ev); + this.logger.warn('Unknown NLP match type', ev); return false; } }); diff --git a/api/src/chat/services/bot.service.ts b/api/src/chat/services/bot.service.ts index 8a9ab5ed..96e87519 100644 --- a/api/src/chat/services/bot.service.ts +++ b/api/src/chat/services/bot.service.ts @@ -1,5 +1,5 @@ /* - * Copyright © 2024 Hexastack. All rights reserved. + * Copyright © 2025 Hexastack. All rights reserved. * * Licensed under the GNU Affero General Public License v3.0 (AGPLv3) with the following additional terms: * 1. The name "Hexabot" is a trademark of Hexastack. You may not use this name in derivative works without express written permission. @@ -62,10 +62,7 @@ export class BotService { context = context || getDefaultConversationContext(); fallback = typeof fallback !== 'undefined' ? fallback : false; const options = block.options; - this.logger.debug( - 'Bot service : Sending message ... ', - event.getSenderForeignId(), - ); + this.logger.debug('Sending message ... ', event.getSenderForeignId()); // Process message : Replace tokens with context data and then send the message const recipient = event.getSender(); const envelope = await this.blockService.processMessage( @@ -116,7 +113,7 @@ export class BotService { assignTo, ); - this.logger.debug('Bot service : Assigned labels ', blockLabels); + this.logger.debug('Assigned labels ', blockLabels); return response; } @@ -375,7 +372,7 @@ export class BotService { ); this.logger.debug( - 'Bot service : Started a new conversation with ', + 'Started a new conversation with ', subscriber.id, block.name, ); @@ -386,14 +383,11 @@ export class BotService { false, ); } catch (err) { - this.logger.error('Bot service : Unable to store context data!', err); + this.logger.error('Unable to store context data!', err); this.eventEmitter.emit('hook:conversation:end', convo, true); } } catch (err) { - this.logger.error( - 'Botservice : Unable to start a new conversation with ', - err, - ); + this.logger.error('Unable to start a new conversation with ', err); } } diff --git a/api/src/chat/services/message.service.ts b/api/src/chat/services/message.service.ts index 6ee4463c..75031541 100644 --- a/api/src/chat/services/message.service.ts +++ b/api/src/chat/services/message.service.ts @@ -1,5 +1,5 @@ /* - * Copyright © 2024 Hexastack. All rights reserved. + * Copyright © 2025 Hexastack. All rights reserved. * * Licensed under the GNU Affero General Public License v3.0 (AGPLv3) with the following additional terms: * 1. The name "Hexabot" is a trademark of Hexastack. You may not use this name in derivative works without express written permission. @@ -65,10 +65,7 @@ export class MessageService extends BaseService< success: true, }); } catch (e) { - this.logger.error( - 'MessageController subscribe : Websocket subscription', - e, - ); + this.logger.error('Websocket subscription', e); throw new InternalServerErrorException(e); } } diff --git a/api/src/chat/services/subscriber.service.ts b/api/src/chat/services/subscriber.service.ts index 0f6e4e89..dcc7bd73 100644 --- a/api/src/chat/services/subscriber.service.ts +++ b/api/src/chat/services/subscriber.service.ts @@ -72,9 +72,7 @@ export class SubscriberService extends BaseService< subscribe: Room.SUBSCRIBER, }); } catch (e) { - this.logger.error( - 'SubscriberController subscribe : Websocket subscription', - ); + this.logger.error('Websocket subscription'); throw new InternalServerErrorException(e); } } diff --git a/api/src/extensions/channels/web/base-web-channel.ts b/api/src/extensions/channels/web/base-web-channel.ts index 81b4ea4e..80024107 100644 --- a/api/src/extensions/channels/web/base-web-channel.ts +++ b/api/src/extensions/channels/web/base-web-channel.ts @@ -89,7 +89,7 @@ export default abstract class BaseWebChannelHandler< * @returns - */ init(): void { - this.logger.debug('Web Channel Handler : initialization ...'); + this.logger.debug('initialization ...'); } /** @@ -108,22 +108,17 @@ export default abstract class BaseWebChannelHandler< return; } - this.logger.debug( - 'Web Channel Handler : WS connected .. sending settings', - ); + this.logger.debug('WS connected .. sending settings'); try { const menu = await this.menuService.getTree(); return client.emit('settings', { menu, ...settings }); } catch (err) { - this.logger.warn('Web Channel Handler : Unable to retrieve menu ', err); + this.logger.warn('Unable to retrieve menu ', err); return client.emit('settings', settings); } } catch (err) { - this.logger.error( - 'Web Channel Handler : Unable to initiate websocket connection', - err, - ); + this.logger.error('Unable to initiate websocket connection', err); client.disconnect(); } } @@ -315,7 +310,7 @@ export default abstract class BaseWebChannelHandler< // Check if we have an origin header... if (!req.headers?.origin) { - this.logger.debug('Web Channel Handler : No origin ', req.headers); + this.logger.debug('No origin ', req.headers); throw new Error('CORS - No origin provided!'); } @@ -332,10 +327,7 @@ export default abstract class BaseWebChannelHandler< try { return new URL(origin.trim()).origin; } catch (error) { - this.logger.error( - `Web Channel Handler : Invalid URL in allowed domains: ${origin}`, - error, - ); + this.logger.error(`Invalid URL in allowed domains: ${origin}`, error); return null; } }) @@ -353,10 +345,7 @@ export default abstract class BaseWebChannelHandler< // For HTTP requests, set the Access-Control-Allow-Origin header to '', which the browser will // interpret as, 'no way Jose.' res.set('Access-Control-Allow-Origin', ''); - this.logger.debug( - 'Web Channel Handler : No origin found ', - req.headers.origin, - ); + this.logger.debug('No origin found ', req.headers.origin); throw new Error('CORS - Domain not allowed!'); } else { res.set('Access-Control-Allow-Origin', originUrl.origin); @@ -384,10 +373,7 @@ export default abstract class BaseWebChannelHandler< next: (profile: Subscriber) => void, ) { if (!req.session?.web?.profile?.id) { - this.logger.warn( - 'Web Channel Handler : No session ID to be found!', - req.session, - ); + this.logger.warn('No session ID to be found!', req.session); return res .status(403) .json({ err: 'Web Channel Handler : Unauthorized!' }); @@ -397,7 +383,7 @@ export default abstract class BaseWebChannelHandler< !Array.isArray(req.session.web.messageQueue) ) { this.logger.warn( - 'Web Channel Handler : Mixed channel request or invalid session data!', + 'Mixed channel request or invalid session data!', req.session, ); return res @@ -420,10 +406,7 @@ export default abstract class BaseWebChannelHandler< try { await this.validateCors(req, res); } catch (err) { - this.logger.warn( - 'Web Channel Handler : Attempt to access from an unauthorized origin', - err, - ); + this.logger.warn('Attempt to access from an unauthorized origin', err); throw new Error('Unauthorized, invalid origin !'); } } @@ -498,18 +481,14 @@ export default abstract class BaseWebChannelHandler< private getMessageQueue(req: Request, res: Response) { // Polling not authorized when using websockets if (this.isSocketRequest(req)) { - this.logger.warn( - 'Web Channel Handler : Polling not authorized when using websockets', - ); + this.logger.warn('Polling not authorized when using websockets'); return res .status(403) .json({ err: 'Polling not authorized when using websockets' }); } // Session must be active if (!(req.session && req.session.web && req.session.web.profile.id)) { - this.logger.warn( - 'Web Channel Handler : Must be connected to poll messages', - ); + this.logger.warn('Must be connected to poll messages'); return res .status(403) .json({ err: 'Polling not authorized : Must be connected' }); @@ -517,9 +496,7 @@ export default abstract class BaseWebChannelHandler< // Can only request polling once at a time if (req.session && req.session.web && req.session.web.polling) { - this.logger.warn( - 'Web Channel Handler : Poll rejected ... already requested', - ); + this.logger.warn('Poll rejected ... already requested'); return res .status(403) .json({ err: 'Poll rejected ... already requested' }); @@ -543,16 +520,14 @@ export default abstract class BaseWebChannelHandler< req.session.web.polling = false; return res.status(200).json(messages.map((msg) => ['message', msg])); } else { - this.logger.error( - 'Web Channel Handler : Polling failed .. no session data', - ); + this.logger.error('Polling failed .. no session data'); return res.status(500).json({ err: 'No session data' }); } } catch (err) { if (req.session.web) { req.session.web.polling = false; } - this.logger.error('Web Channel Handler : Polling failed', err); + this.logger.error('Polling failed', err); return res.status(500).json({ err: 'Polling failed' }); } }; @@ -569,11 +544,7 @@ export default abstract class BaseWebChannelHandler< req: Request | SocketRequest, res: Response | SocketResponse, ) { - this.logger.debug( - 'Web Channel Handler : subscribe (isSocket=' + - this.isSocketRequest(req) + - ')', - ); + this.logger.debug('subscribe (isSocket=' + this.isSocketRequest(req) + ')'); try { const profile = await this.getOrCreateSession(req); // Join socket room when using websocket @@ -581,10 +552,7 @@ export default abstract class BaseWebChannelHandler< try { await req.socket.join(profile.foreign_id); } catch (err) { - this.logger.error( - 'Web Channel Handler : Unable to subscribe via websocket', - err, - ); + this.logger.error('Unable to subscribe via websocket', err); } } // Fetch message history @@ -595,7 +563,7 @@ export default abstract class BaseWebChannelHandler< const messages = await this.fetchHistory(req, criteria); return res.status(200).json({ profile, messages }); } catch (err) { - this.logger.warn('Web Channel Handler : Unable to subscribe ', err); + this.logger.warn('Unable to subscribe ', err); return res.status(500).json({ err: 'Unable to subscribe' }); } } @@ -610,13 +578,13 @@ export default abstract class BaseWebChannelHandler< const { type, data } = req.body as Web.IncomingMessage; if (!req.session?.web?.profile?.id) { - this.logger.debug('Web Channel Handler : No session'); + this.logger.debug('No session'); return null; } // Check if any file is provided if (type !== 'file' || !('file' in data) || !data.file) { - this.logger.debug('Web Channel Handler : No files provided'); + this.logger.debug('No files provided'); return null; } @@ -636,10 +604,7 @@ export default abstract class BaseWebChannelHandler< createdBy: req.session?.web?.profile?.id, }); } catch (err) { - this.logger.error( - 'Web Channel Handler : Unable to store uploaded file', - err, - ); + this.logger.error('Unable to store uploaded file', err); throw new Error('Unable to upload file!'); } } @@ -671,10 +636,7 @@ export default abstract class BaseWebChannelHandler< (resolve, reject) => { upload(req as Request, res as Response, async (err?: any) => { if (err) { - this.logger.error( - 'Web Channel Handler : Unable to store uploaded file', - err, - ); + this.logger.error('Unable to store uploaded file', err); reject(new Error('Unable to upload file!')); } @@ -689,7 +651,7 @@ export default abstract class BaseWebChannelHandler< // Check if any file is provided if (!file) { - this.logger.debug('Web Channel Handler : No files provided'); + this.logger.debug('No files provided'); return null; } @@ -703,10 +665,7 @@ export default abstract class BaseWebChannelHandler< createdBy: req.session.web.profile?.id, }); } catch (err) { - this.logger.error( - 'Web Channel Handler : Unable to store uploaded file', - err, - ); + this.logger.error('Unable to store uploaded file', err); throw err; } } @@ -724,7 +683,7 @@ export default abstract class BaseWebChannelHandler< ): Promise { // Check if any file is provided if (!req.session.web) { - this.logger.debug('Web Channel Handler : No session provided'); + this.logger.debug('No session provided'); return null; } @@ -784,7 +743,7 @@ export default abstract class BaseWebChannelHandler< ): void { // @TODO: perform payload validation if (!req.body) { - this.logger.debug('Web Channel Handler : Empty body'); + this.logger.debug('Empty body'); res.status(400).json({ err: 'Web Channel Handler : Bad Request!' }); return; } else { @@ -814,10 +773,7 @@ export default abstract class BaseWebChannelHandler< }; } } catch (err) { - this.logger.warn( - 'Web Channel Handler : Unable to upload file ', - err, - ); + this.logger.warn('Unable to upload file ', err); return res .status(403) .json({ err: 'Web Channel Handler : File upload failed!' }); @@ -849,10 +805,7 @@ export default abstract class BaseWebChannelHandler< if (type) { this.eventEmitter.emit(`hook:chatbot:${type}`, event); } else { - this.logger.error( - 'Web Channel Handler : Webhook received unknown event ', - event, - ); + this.logger.error('Webhook received unknown event ', event); } res.status(200).json(event._adapter.raw); }); @@ -883,9 +836,7 @@ export default abstract class BaseWebChannelHandler< if (!this.isSocketRequest(req) && req.query._get) { switch (req.query._get) { case 'settings': - this.logger.debug( - 'Web Channel Handler : connected .. sending settings', - ); + this.logger.debug('connected .. sending settings'); try { const menu = await this.menuService.getTree(); return res.status(200).json({ @@ -894,19 +845,14 @@ export default abstract class BaseWebChannelHandler< ...settings, }); } catch (err) { - this.logger.warn( - 'Web Channel Handler : Unable to retrieve menu ', - err, - ); + this.logger.warn('Unable to retrieve menu ', err); return res.status(500).json({ err: 'Unable to retrieve menu' }); } case 'polling': // Handle polling when user is not connected via websocket return this.getMessageQueue(req, res as Response); default: - this.logger.error( - 'Web Channel Handler : Webhook received unknown command', - ); + this.logger.error('Webhook received unknown command'); return res .status(500) .json({ err: 'Webhook received unknown command' }); @@ -923,7 +869,7 @@ export default abstract class BaseWebChannelHandler< return this._handleEvent(req, res); } } catch (err) { - this.logger.warn('Web Channel Handler : Request check failed', err); + this.logger.warn('Request check failed', err); return res .status(403) .json({ err: 'Web Channel Handler : Unauthorized!' }); @@ -1131,9 +1077,7 @@ export default abstract class BaseWebChannelHandler< // Items count min check if (!data.length) { - this.logger.error( - 'Web Channel Handler : Unsufficient content count (must be >= 0 for list)', - ); + this.logger.error('Unsufficient content count (must be >= 0 for list)'); throw new Error('Unsufficient content count (list >= 0)'); } @@ -1181,7 +1125,7 @@ export default abstract class BaseWebChannelHandler< // Items count min check if (data.length === 0) { this.logger.error( - 'Web Channel Handler : Unsufficient content count (must be > 0 for carousel)', + 'Unsufficient content count (must be > 0 for carousel)', ); throw new Error('Unsufficient content count (carousel > 0)'); } @@ -1293,10 +1237,7 @@ export default abstract class BaseWebChannelHandler< await this.sendTypingIndicator(subscriber, timeout); return next(); } catch (err) { - this.logger.error( - 'Web Channel Handler : Failed in sending typing indicator ', - err, - ); + this.logger.error('Failed in sending typing indicator ', err); } } From 495cc060946024544f9f80155d98317514ffe571 Mon Sep 17 00:00:00 2001 From: abdou6666 Date: Mon, 27 Jan 2025 17:11:47 +0100 Subject: [PATCH 5/8] fix: refactor code --- api/src/logger/logger.service.ts | 97 ++++++++++++++++---------------- 1 file changed, 48 insertions(+), 49 deletions(-) diff --git a/api/src/logger/logger.service.ts b/api/src/logger/logger.service.ts index cf6bc358..c7e5847c 100644 --- a/api/src/logger/logger.service.ts +++ b/api/src/logger/logger.service.ts @@ -6,83 +6,82 @@ * 2. All derivative works must include clear attribution to the original creator and software, Hexastack and Hexabot, in a prominent location (e.g., in the software's "About" section, documentation, and README file). */ -import { ConsoleLogger, Inject, Injectable, Scope } from '@nestjs/common'; +import { + ConsoleLogger, + Inject, + Injectable, + LogLevel, + Scope, +} from '@nestjs/common'; import { INQUIRER } from '@nestjs/core'; -type TLog = 'log' | 'warn' | 'error' | 'verbose' | 'debug'; - @Injectable({ scope: Scope.TRANSIENT }) export class LoggerService extends ConsoleLogger { + private logLevels: LogLevel[] = []; + constructor(@Inject(INQUIRER) private parentClass: object) { super(parentClass.constructor.name); + this.initLogLevels(); } - log(message: any, ...args: any[]) { + log(message: string, ...args: any[]) { + if (!this.isLevelEnabled('log')) { + return; + } super.log(message); this.logArguments('log', args); } - error(message: any, ...args: any[]) { + error(message: string, ...args: any[]) { + if (!this.isLevelEnabled('error')) { + return; + } super.error(message); this.logArguments('error', args); } - warn(message: any, ...args: any[]) { + warn(message: string, ...args: any[]) { + if (!this.isLevelEnabled('warn')) { + return; + } super.warn(message); this.logArguments('warn', args); } - debug(message: any, ...args: any[]) { + debug(message: string, ...args: any[]) { + if (!this.isLevelEnabled('debug')) { + return; + } super.debug(message); this.logArguments('debug', args); } - verbose(message: any, ...args: any[]) { + verbose(message: string, ...args: any[]) { + if (!this.isLevelEnabled('verbose')) { + return; + } super.verbose(message); this.logArguments('verbose', args); } - private safeStringifyReplacer() { - const seen = new WeakSet(); - return (key: string, value: any) => { - if (typeof value === 'object' && value !== null) { - if (seen.has(value)) return '[Circular Reference]'; - seen.add(value); - } - return value; - }; - } - - private handleError(type: TLog, error: Error) { - const errorInfo = { - message: error.message, - name: error.name, - stack: error.stack, - }; - super[type](JSON.stringify(errorInfo, this.safeStringifyReplacer())); - } - - private logArguments(type: TLog, args: any[]) { - const isDevMode = process.env.NODE_ENV?.includes('dev'); - if (isDevMode) { - args.forEach((arg) => { - if (arg instanceof Error) { - this.handleError(type, arg); - } else if (typeof arg === 'object' && arg !== null) { - super[type](JSON.stringify(arg, this.safeStringifyReplacer())); - } else { - super[type](arg); - } - }); - } else { - // In production we do not stringify the arguments for performance reasons, except for errors - args.forEach((arg) => { - if (arg instanceof Error) { - this.handleError(type, arg); - } else { - super[type](arg); - } - }); + fatal(message: string, ...args: any[]) { + if (!this.isLevelEnabled('fatal')) { + return; } + super.fatal(message); + this.logArguments('fatal', args); + } + + private logArguments(type: LogLevel, args: any[]) { + args.forEach((arg) => { + super[type](arg); + }); + } + + private initLogLevels() { + process.env.NODE_ENV?.includes('dev') + ? this.logLevels.push('log', 'debug', 'error', 'verbose', 'fatal', 'warn') + : this.logLevels.push('log', 'warn', 'error'); + super.setLogLevels(this.logLevels); } } From e8cbe253d3d4ae02bdf5c11c36dbc4d9ab21545e Mon Sep 17 00:00:00 2001 From: abdou6666 Date: Tue, 28 Jan 2025 11:50:20 +0100 Subject: [PATCH 6/8] fix: apply feedback --- api/src/logger/logger.service.ts | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/api/src/logger/logger.service.ts b/api/src/logger/logger.service.ts index c7e5847c..93c04ddd 100644 --- a/api/src/logger/logger.service.ts +++ b/api/src/logger/logger.service.ts @@ -20,8 +20,11 @@ export class LoggerService extends ConsoleLogger { private logLevels: LogLevel[] = []; constructor(@Inject(INQUIRER) private parentClass: object) { - super(parentClass.constructor.name); - this.initLogLevels(); + super(parentClass.constructor.name, { + logLevels: process.env.NODE_ENV?.includes('dev') + ? ['log', 'debug', 'error', 'verbose', 'fatal', 'warn'] + : ['log', 'warn', 'error'], + }); } log(message: string, ...args: any[]) { @@ -77,11 +80,4 @@ export class LoggerService extends ConsoleLogger { super[type](arg); }); } - - private initLogLevels() { - process.env.NODE_ENV?.includes('dev') - ? this.logLevels.push('log', 'debug', 'error', 'verbose', 'fatal', 'warn') - : this.logLevels.push('log', 'warn', 'error'); - super.setLogLevels(this.logLevels); - } } From 3756235a76ee280e49c52df005642ad85eb292ca Mon Sep 17 00:00:00 2001 From: abdou6666 Date: Tue, 28 Jan 2025 17:58:16 +0100 Subject: [PATCH 7/8] fix: apply feedback --- api/src/logger/logger.service.ts | 41 +++++++------------------------- 1 file changed, 8 insertions(+), 33 deletions(-) diff --git a/api/src/logger/logger.service.ts b/api/src/logger/logger.service.ts index 93c04ddd..c14ebbf1 100644 --- a/api/src/logger/logger.service.ts +++ b/api/src/logger/logger.service.ts @@ -17,8 +17,6 @@ import { INQUIRER } from '@nestjs/core'; @Injectable({ scope: Scope.TRANSIENT }) export class LoggerService extends ConsoleLogger { - private logLevels: LogLevel[] = []; - constructor(@Inject(INQUIRER) private parentClass: object) { super(parentClass.constructor.name, { logLevels: process.env.NODE_ENV?.includes('dev') @@ -28,54 +26,31 @@ export class LoggerService extends ConsoleLogger { } log(message: string, ...args: any[]) { - if (!this.isLevelEnabled('log')) { - return; - } - super.log(message); - this.logArguments('log', args); + this.logArguments('log', message, args); } error(message: string, ...args: any[]) { - if (!this.isLevelEnabled('error')) { - return; - } - super.error(message); - this.logArguments('error', args); + this.logArguments('error', message, args); } warn(message: string, ...args: any[]) { - if (!this.isLevelEnabled('warn')) { - return; - } - super.warn(message); - this.logArguments('warn', args); + this.logArguments('warn', message, args); } debug(message: string, ...args: any[]) { - if (!this.isLevelEnabled('debug')) { - return; - } - super.debug(message); - this.logArguments('debug', args); + this.logArguments('debug', message, args); } verbose(message: string, ...args: any[]) { - if (!this.isLevelEnabled('verbose')) { - return; - } - super.verbose(message); - this.logArguments('verbose', args); + this.logArguments('verbose', message, args); } fatal(message: string, ...args: any[]) { - if (!this.isLevelEnabled('fatal')) { - return; - } - super.fatal(message); - this.logArguments('fatal', args); + this.logArguments('fatal', message, args); } - private logArguments(type: LogLevel, args: any[]) { + private logArguments(type: LogLevel, message: string, args: any[]) { + super[type](message); args.forEach((arg) => { super[type](arg); }); From 1f817758442b1618ca7755816d0b9784a7b63dfe Mon Sep 17 00:00:00 2001 From: abdou6666 Date: Tue, 28 Jan 2025 18:49:46 +0100 Subject: [PATCH 8/8] fix: logger import --- api/src/helper/lib/base-helper.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/api/src/helper/lib/base-helper.ts b/api/src/helper/lib/base-helper.ts index 1c46f0ac..e7dffd1f 100644 --- a/api/src/helper/lib/base-helper.ts +++ b/api/src/helper/lib/base-helper.ts @@ -8,8 +8,9 @@ import path from 'path'; -import { LoggerService, OnModuleInit } from '@nestjs/common'; +import { OnModuleInit } from '@nestjs/common'; +import { LoggerService } from '@/logger/logger.service'; import { SettingService } from '@/setting/services/setting.service'; import { Extension } from '@/utils/generics/extension'; import { HyphenToUnderscore } from '@/utils/types/extension';