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/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/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); } } 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'; diff --git a/api/src/logger/logger.service.ts b/api/src/logger/logger.service.ts index be15d874..c14ebbf1 100644 --- a/api/src/logger/logger.service.ts +++ b/api/src/logger/logger.service.ts @@ -1,11 +1,58 @@ /* - * 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, + LogLevel, + 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, { + logLevels: process.env.NODE_ENV?.includes('dev') + ? ['log', 'debug', 'error', 'verbose', 'fatal', 'warn'] + : ['log', 'warn', 'error'], + }); + } + + log(message: string, ...args: any[]) { + this.logArguments('log', message, args); + } + + error(message: string, ...args: any[]) { + this.logArguments('error', message, args); + } + + warn(message: string, ...args: any[]) { + this.logArguments('warn', message, args); + } + + debug(message: string, ...args: any[]) { + this.logArguments('debug', message, args); + } + + verbose(message: string, ...args: any[]) { + this.logArguments('verbose', message, args); + } + + fatal(message: string, ...args: any[]) { + this.logArguments('fatal', message, args); + } + + private logArguments(type: LogLevel, message: string, args: any[]) { + super[type](message); + args.forEach((arg) => { + super[type](arg); + }); + } +} 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/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); }); 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);