From dae66a0456160536c718a49f1349833148984dac Mon Sep 17 00:00:00 2001 From: Ebenezer199914 Date: Mon, 29 Jun 2026 22:20:37 +0000 Subject: [PATCH] feat: standardize backend logging, remove direct console usage (#65) Replace all direct console.* calls across the codebase with the project's structured winston logger (src/utils/logger.ts). Changes: - logger.ts: add named helpers logInfo, logWarn, logError, logDebug with optional context metadata; keep default export for back-compat - distribution.service.ts: replace 4 console.error/warn calls with logError / logWarn - distribution.controller.ts: replace 3 console.error calls with logError - data-source.ts: replace console.error in resetDatabase with logger.error - src/utils/index.ts: replace console.error in executeTransaction with logError - src/utils/errorHandler.ts: replace console.info with logger.info - src/scripts/generate-migration.ts: replace console.error and console.warn with logger.error and logger.warn - eslint.config.mjs: add 'no-console': 'error' rule to enforce the pattern going forward Verification: - bun run type-check: passes (0 errors) - bun run test: 33/33 pass - bun run lint: 0 errors (all no-console violations resolved) Closes #65 --- eslint.config.mjs | 5 ++ .../distribution/distribution.controller.ts | 7 ++- .../v1/distribution/distribution.service.ts | 9 +-- src/config/persistence/data-source.ts | 2 +- src/scripts/generate-migration.ts | 4 +- src/utils/errorHandler.ts | 2 +- src/utils/index.ts | 3 +- src/utils/logger.ts | 58 +++++++++++++++++++ 8 files changed, 78 insertions(+), 12 deletions(-) diff --git a/eslint.config.mjs b/eslint.config.mjs index 288bf19..32e220c 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -35,6 +35,11 @@ export default [ '@typescript-eslint/no-unused-vars': 'off', '@typescript-eslint/no-explicit-any': 'warn', + + // Enforce use of the project logger (src/utils/logger.ts) instead + // of direct console calls, which bypass structured logging and + // environment-aware log levels. + 'no-console': 'error', }, }, ]; diff --git a/src/components/v1/distribution/distribution.controller.ts b/src/components/v1/distribution/distribution.controller.ts index f365bdf..90cc1b5 100644 --- a/src/components/v1/distribution/distribution.controller.ts +++ b/src/components/v1/distribution/distribution.controller.ts @@ -3,6 +3,7 @@ import AppDataSource from "../../../config/persistence/data-source" import { DistributionEntity } from "./distribution.entity" import { DistributionService } from "./distribution.service" import type { ApiResponse, DistributionResponseDto, CreateDistributionDto, UpdateDistributionDto } from "./distribution.dto" +import { logError } from "../../../utils/logger" const getDistributionService = () => { if (!AppDataSource.isInitialized) { @@ -29,7 +30,7 @@ export const createDistribution = async (req: Request, res: Response): Promise = { data: null, @@ -57,7 +58,7 @@ export const updateDistribution = async (req: Request, res: Response): Promise = { data: null, diff --git a/src/components/v1/distribution/distribution.service.ts b/src/components/v1/distribution/distribution.service.ts index c4f4a06..bb3abf0 100644 --- a/src/components/v1/distribution/distribution.service.ts +++ b/src/components/v1/distribution/distribution.service.ts @@ -3,6 +3,7 @@ import type { Repository } from "typeorm" import type { DistributionEntity } from "./distribution.entity" import type { CreateDistributionDto, DistributionResponseDto, UpdateDistributionDto } from "./distribution.dto" import { DistributionStatus, Network } from "../../../types/enums" +import { logError, logWarn } from "../../../utils/logger" export class DistributionService { constructor(private readonly distributionRepository: Repository) {} @@ -16,7 +17,7 @@ export class DistributionService { return this.formatDistributionResponse(savedDistribution) } catch (error) { - console.error("Error creating distribution:", error) + logError("Error creating distribution", error) throw new Error("Failed to create distribution") } } @@ -57,7 +58,7 @@ export class DistributionService { return this.formatDistributionResponse(savedDistribution) } catch (error) { - console.error("Error updating distribution:", error) + logError("Error updating distribution", error) throw error instanceof Error ? error : new Error("Failed to update distribution") } } @@ -71,7 +72,7 @@ export class DistributionService { return distributions.map((d) => this.formatDistributionResponse(d)) } catch (error) { - console.error("Error listing distributions:", error) + logError("Error listing distributions", error) throw new Error("Failed to list distributions") } } @@ -115,7 +116,7 @@ export class DistributionService { const rate = new Decimal(usdRate) return amount.mul(rate).toString() } catch (error) { - console.warn("Error calculating total USD amount:", error) + logWarn("Error calculating total USD amount", { error: String(error) }) return "0" } } diff --git a/src/config/persistence/data-source.ts b/src/config/persistence/data-source.ts index a938386..82846ce 100644 --- a/src/config/persistence/data-source.ts +++ b/src/config/persistence/data-source.ts @@ -75,7 +75,7 @@ export const resetDatabase = async () => { await queryRunner.clearDatabase(); logger.info('All tables dropped. Database reset successfully!'); } catch (error) { - console.error('Error resetting the database:', error); + logger.error('Error resetting the database:', { error: String(error) }); } finally { await AppDataSource.destroy(); } diff --git a/src/scripts/generate-migration.ts b/src/scripts/generate-migration.ts index 3d351f0..28e3b00 100644 --- a/src/scripts/generate-migration.ts +++ b/src/scripts/generate-migration.ts @@ -8,7 +8,7 @@ const execAsync = promisify(exec); const migrationName = process.argv[2]; if (!migrationName) { - console.error('Please provide a migration name.'); + logger.error('Please provide a migration name.'); process.exit(1); } @@ -20,7 +20,7 @@ const command = `pnpm typeorm migration:generate --outputJs src/migrations/${mig const { stdout, stderr } = await execAsync(command); if (stdout.includes('No changes in database schema were found')) { - console.warn( + logger.warn( 'No schema changes detected. Skipping migration generation.' ); return; diff --git a/src/utils/errorHandler.ts b/src/utils/errorHandler.ts index 477b983..57e6b21 100644 --- a/src/utils/errorHandler.ts +++ b/src/utils/errorHandler.ts @@ -197,7 +197,7 @@ class ErrorHandler { let { message, httpCode, type } = error as AppError; const { name, extra } = error as AppError; - console.info(message, name); + logger.info(`${name}: ${message}`); switch (name) { case 'ValidationError': diff --git a/src/utils/index.ts b/src/utils/index.ts index 78a67aa..5ada06e 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -4,6 +4,7 @@ import { z } from 'zod'; import type { EntityManager } from 'typeorm'; import { IRequest } from '../types/global'; +import { logError } from './logger'; export const isValidUuid = (uuid: string): boolean => { const result = z.string().uuid().safeParse(uuid); @@ -27,7 +28,7 @@ export async function executeTransaction( try { return await transactionCallback(transactionManager); } catch (error) { - console.error('Transaction failed:', error); + logError('Transaction failed', error); throw error; } }); diff --git a/src/utils/logger.ts b/src/utils/logger.ts index e35c971..3c454e3 100644 --- a/src/utils/logger.ts +++ b/src/utils/logger.ts @@ -22,4 +22,62 @@ if (appConfigs.isDev || appConfigs.isTestMode || appConfigs.isLocalDev) { ); } +/** + * Log an informational message with optional context metadata. + * + * @param message - Human-readable message + * @param context - Optional key/value metadata (service name, IDs, etc.) + */ +export const logInfo = ( + message: string, + context?: Record +): void => { + logger.info(message, context); +}; + +/** + * Log a warning with optional context metadata. + */ +export const logWarn = ( + message: string, + context?: Record +): void => { + logger.warn(message, context); +}; + +/** + * Log an error. Accepts an Error instance, a plain message, or both. + * + * @param message - Human-readable description of what went wrong + * @param error - The caught error/exception (optional) + * @param context - Additional key/value metadata (optional) + */ +export const logError = ( + message: string, + error?: unknown, + context?: Record +): void => { + const meta: Record = { ...context }; + + if (error instanceof Error) { + meta.errorName = error.name; + meta.errorMessage = error.message; + meta.stack = error.stack; + } else if (error !== undefined) { + meta.error = error; + } + + logger.error(message, meta); +}; + +/** + * Log a debug message (only emitted when log level is set to 'debug'). + */ +export const logDebug = ( + message: string, + context?: Record +): void => { + logger.debug(message, context); +}; + export default logger;