diff --git a/src/api/admin/admin.service.ts b/src/api/admin/admin.service.ts index 4f7fb72..d1e6d18 100644 --- a/src/api/admin/admin.service.ts +++ b/src/api/admin/admin.service.ts @@ -3,6 +3,7 @@ import { HttpStatus, NotFoundException, BadRequestException, + Logger, } from '@nestjs/common'; import { PrismaPromise } from '@prisma/client'; @@ -21,6 +22,8 @@ import { WinningUpdateRequestDto } from './dto/winnings.dto'; */ @Injectable() export class AdminService { + private readonly logger = new Logger(AdminService.name); + /** * Constructs the admin winning service with the given dependencies. * @param prisma the prisma service. @@ -279,7 +282,7 @@ export class AdminService { ) { throw error; } - console.error('Updating winnings failed', error); + this.logger.error('Updating winnings failed', error); const message = 'Updating winnings failed. ' + error; result.error = { code: HttpStatus.INTERNAL_SERVER_ERROR, @@ -477,7 +480,7 @@ export class AdminService { createdAt: item.created_at, })); } catch (error) { - console.error('Getting winnings audit failed', error); + this.logger.error('Getting winnings audit failed', error); const message = 'Searching winnings failed. ' + error; result.error = { code: HttpStatus.INTERNAL_SERVER_ERROR, @@ -536,7 +539,7 @@ export class AdminService { externalTransactionDetails: {}, })); } catch (error) { - console.error('Getting winnings audit failed', error); + this.logger.error('Getting winnings audit failed', error); const message = 'Searching winnings failed. ' + error; result.error = { code: HttpStatus.INTERNAL_SERVER_ERROR, diff --git a/src/api/health-check/healthCheck.controller.ts b/src/api/health-check/healthCheck.controller.ts index cc6b35c..8c5f9bc 100644 --- a/src/api/health-check/healthCheck.controller.ts +++ b/src/api/health-check/healthCheck.controller.ts @@ -1,4 +1,10 @@ -import { Controller, Get, Version, VERSION_NEUTRAL } from '@nestjs/common'; +import { + Controller, + Get, + Logger, + Version, + VERSION_NEUTRAL, +} from '@nestjs/common'; import { ApiOperation, ApiProperty, ApiTags } from '@nestjs/swagger'; import { Public } from 'src/core/auth/decorators'; import { PrismaService } from 'src/shared/global/prisma.service'; @@ -26,6 +32,8 @@ export class GetHealthCheckResponseDto { @ApiTags('Healthcheck') @Controller() export class HealthCheckController { + private readonly logger = new Logger(HealthCheckController.name); + constructor(private readonly prisma: PrismaService) {} @Public() @@ -45,7 +53,7 @@ export class HealthCheckController { response.status = HealthCheckStatus.healthy; response.database = 'connected'; } catch (error) { - console.error('Health check failed', error); + this.logger.error('Health check failed', error); response.status = HealthCheckStatus.unhealthy; } diff --git a/src/api/repository/winnings.repo.ts b/src/api/repository/winnings.repo.ts index 818970d..edcf647 100644 --- a/src/api/repository/winnings.repo.ts +++ b/src/api/repository/winnings.repo.ts @@ -1,4 +1,4 @@ -import { HttpStatus, Injectable } from '@nestjs/common'; +import { HttpStatus, Injectable, Logger } from '@nestjs/common'; import { payment_status, Prisma, @@ -20,6 +20,8 @@ const ONE_DAY = 24 * 60 * 60 * 1000; @Injectable() export class WinningsRepository { + private readonly logger = new Logger(WinningsRepository.name); + constructor(private readonly prisma: PrismaService) {} private generateFilterDate(date: DateFilterType) { @@ -243,7 +245,7 @@ export class WinningsRepository { }; // response.data = winnings as any } catch (error) { - console.error('Searching winnings failed', error); + this.logger.error('Searching winnings failed', error); const message = 'Searching winnings failed. ' + error; result.error = { code: HttpStatus.INTERNAL_SERVER_ERROR, diff --git a/src/api/wallet/wallet.service.ts b/src/api/wallet/wallet.service.ts index 63d623e..10b928f 100644 --- a/src/api/wallet/wallet.service.ts +++ b/src/api/wallet/wallet.service.ts @@ -1,4 +1,4 @@ -import { Injectable, HttpStatus } from '@nestjs/common'; +import { Injectable, HttpStatus, Logger } from '@nestjs/common'; import { PrismaService } from 'src/shared/global/prisma.service'; @@ -13,6 +13,8 @@ import { PaymentMethodRepository } from '../repository/paymentMethod.repo'; */ @Injectable() export class WalletService { + private readonly logger = new Logger(WalletService.name); + /** * Constructs the admin winning service with the given dependencies. * @param prisma the prisma service. @@ -60,16 +62,16 @@ export class WalletService { ], }, withdrawalMethod: { - isSetupComplete: hasVerifiedPaymentMethod, + isSetupComplete: Boolean(hasVerifiedPaymentMethod), }, taxForm: { - isSetupComplete: hasActiveTaxForm, + isSetupComplete: Boolean(hasActiveTaxForm), }, }; result.data = winningTotals; } catch (error) { - console.error('Getting winnings audit failed', error); + this.logger.error('Getting winnings audit failed', error); const message = 'Searching winnings failed. ' + error; result.error = { code: HttpStatus.INTERNAL_SERVER_ERROR, diff --git a/src/api/webhooks/trolley/handlers/recipient-account.handler.ts b/src/api/webhooks/trolley/handlers/recipient-account.handler.ts index 1c95361..c829af5 100644 --- a/src/api/webhooks/trolley/handlers/recipient-account.handler.ts +++ b/src/api/webhooks/trolley/handlers/recipient-account.handler.ts @@ -1,4 +1,4 @@ -import { Injectable } from '@nestjs/common'; +import { Injectable, Logger } from '@nestjs/common'; import { WebhookEvent } from '../../webhooks.decorators'; import { PrismaService } from 'src/shared/global/prisma.service'; import { @@ -11,6 +11,8 @@ import { PaymentsService } from 'src/shared/payments'; @Injectable() export class RecipientAccountHandler { + private readonly logger = new Logger(RecipientAccountHandler.name); + constructor( private readonly prisma: PrismaService, private readonly paymentsService: PaymentsService, @@ -32,7 +34,7 @@ export class RecipientAccountHandler { }); if (!recipient) { - console.error( + this.logger.error( `Recipient not found for recipientId '${recipientId}' while updating user payment method!`, ); return; @@ -84,7 +86,7 @@ export class RecipientAccountHandler { }); if (!recipient) { - console.error( + this.logger.error( `Recipient not found for recipientId '${recipientId}' while updating user payment method!`, ); return; @@ -155,7 +157,7 @@ export class RecipientAccountHandler { }); if (!recipientPaymentMethod) { - console.info( + this.logger.log( `Recipient payment method not found for recipient account id '${payload.id}' while deleting trolley payment method!`, ); return; diff --git a/src/api/webhooks/trolley/handlers/tax-form.handler.ts b/src/api/webhooks/trolley/handlers/tax-form.handler.ts index e50907c..2c58e99 100644 --- a/src/api/webhooks/trolley/handlers/tax-form.handler.ts +++ b/src/api/webhooks/trolley/handlers/tax-form.handler.ts @@ -1,4 +1,4 @@ -import { Injectable } from '@nestjs/common'; +import { Injectable, Logger } from '@nestjs/common'; import { WebhookEvent } from '../../webhooks.decorators'; import { PrismaService } from 'src/shared/global/prisma.service'; import { tax_form_status, trolley_recipient } from '@prisma/client'; @@ -12,6 +12,8 @@ import { @Injectable() export class TaxFormHandler { + private readonly logger = new Logger(TaxFormHandler.name); + constructor( private readonly prisma: PrismaService, private readonly paymentsService: PaymentsService, @@ -95,7 +97,7 @@ export class TaxFormHandler { const recipient = await this.getDbRecipientById(taxFormData.recipientId); if (!recipient) { - console.error( + this.logger.error( `Recipient not found for recipientId '${taxFormData.recipientId}' in taxForm with id '${taxFormData.taxFormId}'`, ); return; diff --git a/src/api/webhooks/webhooks.controller.ts b/src/api/webhooks/webhooks.controller.ts index 01f83bc..95e2bbd 100644 --- a/src/api/webhooks/webhooks.controller.ts +++ b/src/api/webhooks/webhooks.controller.ts @@ -4,6 +4,7 @@ import { Req, RawBodyRequest, ForbiddenException, + Logger, } from '@nestjs/common'; import { ApiTags } from '@nestjs/swagger'; import { TrolleyHeaders, TrolleyService } from './trolley/trolley.service'; @@ -13,6 +14,8 @@ import { Public } from 'src/core/auth/decorators'; @ApiTags('Webhooks') @Controller('webhooks') export class WebhooksController { + private readonly logger = new Logger(WebhooksController.name); + constructor(private readonly trolleyService: TrolleyService) {} /** @@ -34,12 +37,16 @@ export class WebhooksController { request.rawBody?.toString('utf-8') ?? '', ) ) { + this.logger.warn( + 'Received request with missing or invalid signature!', + request.headers, + ); throw new ForbiddenException('Missing or invalid signature!'); } // do not proceed any further if event has already been processed if (!(await this.trolleyService.validateUnique(request.headers))) { - console.info( + this.logger.warn( `Webhook event '${request.headers[TrolleyHeaders.id]}' has already been processed!`, ); return; diff --git a/src/api/winnings/winnings.service.ts b/src/api/winnings/winnings.service.ts index 8872def..0f2e89c 100644 --- a/src/api/winnings/winnings.service.ts +++ b/src/api/winnings/winnings.service.ts @@ -1,4 +1,4 @@ -import { Injectable, HttpStatus } from '@nestjs/common'; +import { Injectable, HttpStatus, Logger } from '@nestjs/common'; import { Prisma, payment, payment_status } from '@prisma/client'; import { PrismaService } from 'src/shared/global/prisma.service'; @@ -15,6 +15,8 @@ import { PaymentMethodRepository } from '../repository/paymentMethod.repo'; */ @Injectable() export class WinningsService { + private readonly logger = new Logger(WinningsService.name); + /** * Constructs the admin winning service with the given dependencies. * @param prisma the prisma service. @@ -42,6 +44,8 @@ export class WinningsService { const originId = await this.originRepo.getOriginIdByName(body.origin, tx); if (!originId) { + this.logger.warn('Invalid origin provided', { originId }); + result.error = { code: HttpStatus.BAD_REQUEST, message: 'Origin name does not exist', diff --git a/src/core/auth/middleware/tokenValidator.middleware.ts b/src/core/auth/middleware/tokenValidator.middleware.ts index 12b6633..680a36c 100644 --- a/src/core/auth/middleware/tokenValidator.middleware.ts +++ b/src/core/auth/middleware/tokenValidator.middleware.ts @@ -1,11 +1,14 @@ import { Injectable, + Logger, NestMiddleware, UnauthorizedException, } from '@nestjs/common'; import * as jwt from 'jsonwebtoken'; import { ENV_CONFIG } from 'src/config'; +const logger = new Logger(`Auth/TokenValidatorMiddleware`); + @Injectable() export class TokenValidatorMiddleware implements NestMiddleware { use(req: any, res: Response, next: (error?: any) => void) { @@ -19,7 +22,7 @@ export class TokenValidatorMiddleware implements NestMiddleware { try { decoded = jwt.verify(idToken, ENV_CONFIG.AUTH0_CERT); } catch (error) { - console.error('Error verifying JWT', error); + logger.error('Error verifying JWT', error); throw new UnauthorizedException('Invalid or expired JWT!'); } diff --git a/src/main.ts b/src/main.ts index c2dec0b..c346675 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,7 +1,7 @@ import cors from 'cors'; import { NestFactory } from '@nestjs/core'; import { NestExpressApplication } from '@nestjs/platform-express'; -import { ValidationPipe } from '@nestjs/common'; +import { Logger, ValidationPipe } from '@nestjs/common'; import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger'; import { ApiModule } from './api/api.module'; import { AppModule } from './app.module'; @@ -18,6 +18,8 @@ async function bootstrap() { rawBody: true, }); + const logger = new Logger('bootstrap()'); + // Global prefix for all routes app.setGlobalPrefix(ENV_CONFIG.API_BASE); @@ -70,12 +72,12 @@ async function bootstrap() { // Add an event handler to log uncaught promise rejections and prevent the server from crashing process.on('unhandledRejection', (reason, promise) => { - console.error('Unhandled Rejection at:', promise, 'reason:', reason); + logger.error('Unhandled Rejection at:', promise, 'reason:', reason); }); // Add an event handler to log uncaught errors and prevent the server from crashing process.on('uncaughtException', (error: Error) => { - console.error( + logger.error( `Unhandled Error at: ${error}\n` + `Exception origin: ${error.stack}`, ); }); diff --git a/src/shared/global/prisma.service.ts b/src/shared/global/prisma.service.ts index ae6347c..2ada861 100644 --- a/src/shared/global/prisma.service.ts +++ b/src/shared/global/prisma.service.ts @@ -1,4 +1,4 @@ -import { Injectable, OnModuleInit } from '@nestjs/common'; +import { Injectable, Logger, OnModuleInit } from '@nestjs/common'; import { Prisma, PrismaClient } from '@prisma/client'; @Injectable() @@ -9,6 +9,8 @@ export class PrismaService > implements OnModuleInit { + private readonly logger = new Logger(PrismaService.name); + constructor() { super({ log: [ @@ -18,9 +20,43 @@ export class PrismaService { level: 'error', emit: 'event' }, ], }); + + // Setup logging for Prisma queries and errors + this.$on('query' as never, (e: Prisma.QueryEvent) => { + const queryTime = e.duration; + + // log slow queries (> 500ms) + if (queryTime > 500) { + this.logger.warn( + `Slow query detected! Duration: ${queryTime}ms | Query: ${e.query}`, + ); + } + }); + + this.$on('info' as never, (e: Prisma.LogEvent) => { + this.logger.log(`Prisma Info: ${e.message}`); + }); + + this.$on('warn' as never, (e: Prisma.LogEvent) => { + this.logger.warn(`Prisma Warning: ${e.message}`); + }); + + this.$on('error' as never, (e: Prisma.LogEvent) => { + this.logger.error(`Prisma Error: ${e.message}`, e.target); + }); } async onModuleInit() { - await this.$connect(); + this.logger.log('Initializing Prisma connection'); + try { + await this.$connect(); + this.logger.log('Prisma connected successfully'); + } catch (error) { + this.logger.error( + `Failed to connect to the database: ${error.message}`, + error.stack, + ); + throw error; + } } } diff --git a/src/shared/topcoder/members.service.ts b/src/shared/topcoder/members.service.ts index deaafe0..35acc68 100644 --- a/src/shared/topcoder/members.service.ts +++ b/src/shared/topcoder/members.service.ts @@ -1,5 +1,5 @@ import { chunk } from 'lodash'; -import { Injectable } from '@nestjs/common'; +import { Injectable, Logger } from '@nestjs/common'; import { MEMBER_FIELDS } from './member.types'; import { TopcoderM2MService } from './topcoder-m2m.service'; import { ENV_CONFIG } from 'src/config'; @@ -8,6 +8,8 @@ const { TOPCODER_API_BASE_URL } = ENV_CONFIG; @Injectable() export class TopcoderMembersService { + private readonly logger = new Logger(TopcoderMembersService.name); + constructor(private readonly m2MService: TopcoderM2MService) {} /** * Retrieves a mapping of user IDs to their corresponding handles from the Topcoder API. @@ -39,7 +41,11 @@ export class TopcoderMembersService { data.map(({ handle, userId }) => [userId, handle] as string[]), ) as { [userId: string]: string }; } catch (e) { - console.error('Failed to fetch tc members handles!', e?.message ?? e, e); + this.logger.error( + 'Failed to fetch tc members handles!', + e?.message ?? e, + e, + ); return {}; } } @@ -65,7 +71,7 @@ export class TopcoderMembersService { try { m2mToken = await this.m2MService.getToken(); } catch (e) { - console.error( + this.logger.error( 'Failed to fetch m2m token for fetching member details!', e.message ?? e, ); @@ -78,7 +84,7 @@ export class TopcoderMembersService { }).then((r) => r.json()); return response; } catch (e) { - console.error( + this.logger.error( `Failed to fetch tc member info for user '${handle}'! Error: `, e?.message ?? e, e, diff --git a/src/shared/topcoder/topcoder-m2m.service.ts b/src/shared/topcoder/topcoder-m2m.service.ts index 19078a8..6e9f691 100644 --- a/src/shared/topcoder/topcoder-m2m.service.ts +++ b/src/shared/topcoder/topcoder-m2m.service.ts @@ -1,8 +1,10 @@ -import { Injectable } from '@nestjs/common'; +import { Injectable, Logger } from '@nestjs/common'; import { ENV_CONFIG } from 'src/config'; @Injectable() export class TopcoderM2MService { + private readonly logger = new Logger(TopcoderM2MService.name); + /** * Retrieves a Machine-to-Machine (M2M) token from the Auth0 service. * @@ -38,8 +40,7 @@ export class TopcoderM2MService { return m2mToken; } catch (error) { - // eslint-disable-next-line no-console - console.error('Failed fetching TC M2M Token!', error); + this.logger.error('Failed fetching TC M2M Token!', error); return undefined; } }