From 7af375f8c0e553a8bbb5eaff45b6b763c3a0745d Mon Sep 17 00:00:00 2001 From: Alexis Bruneteau Date: Fri, 7 Nov 2025 03:06:38 +0100 Subject: [PATCH] feat: Add comprehensive logging to backend for debugging blockchain and startup Add structured logging throughout the backend: - logging_config.py: Centralized logging configuration with colored output - main.py: Enhanced startup logging showing initialization progress - init_blockchain.py: Detailed blockchain initialization logging - services.py: Election creation logging Logging features: - Emoji prefixes for different log levels (INFO, DEBUG, ERROR, etc.) - Color-coded output for better visibility - Timestamp and module information - Exception stack traces on errors - Separate loggers for different modules This helps debug: - Backend startup sequence - Database initialization - Blockchain election recording - Service operations - Configuration issues --- e-voting-system/backend/init_blockchain.py | 131 ++++++++++++++------- e-voting-system/backend/logging_config.py | 93 +++++++++++++++ e-voting-system/backend/main.py | 26 +++- e-voting-system/backend/services.py | 18 ++- 4 files changed, 219 insertions(+), 49 deletions(-) create mode 100644 e-voting-system/backend/logging_config.py diff --git a/e-voting-system/backend/init_blockchain.py b/e-voting-system/backend/init_blockchain.py index 9de928f..c30c763 100644 --- a/e-voting-system/backend/init_blockchain.py +++ b/e-voting-system/backend/init_blockchain.py @@ -5,12 +5,15 @@ This module ensures that when the backend starts, all elections in the database are recorded to the blockchain if they aren't already. """ +import logging from sqlalchemy.orm import Session from . import models from .blockchain_elections import elections_blockchain, record_election_to_blockchain from datetime import datetime import json +logger = logging.getLogger(__name__) + def initialize_elections_blockchain(db: Session) -> None: """ @@ -22,55 +25,93 @@ def initialize_elections_blockchain(db: Session) -> None: Args: db: Database session """ - # Get all elections from database - elections = db.query(models.Election).all() + logger.info("-" * 60) + logger.info("Blockchain Initialization Started") + logger.info("-" * 60) - if not elections: - print("No elections to record to blockchain") - return + try: + # Get all elections from database + elections = db.query(models.Election).all() + logger.info(f"Found {len(elections)} elections in database") - # Check which elections are already on blockchain - existing_election_ids = {block.election_id for block in elections_blockchain.blocks} + if not elections: + logger.warning("No elections to record to blockchain") + return - # Record each election that isn't already on blockchain - for election in elections: - if election.id in existing_election_ids: - print(f"Election {election.id} already on blockchain, skipping") - continue + # Check which elections are already on blockchain + existing_election_ids = {block.election_id for block in elections_blockchain.blocks} + logger.info(f"Blockchain currently has {len(existing_election_ids)} elections") - try: - # Get candidates for this election - candidates = db.query(models.Candidate).filter( - models.Candidate.election_id == election.id - ).all() + # Record each election that isn't already on blockchain + recorded_count = 0 + skipped_count = 0 - candidates_data = [ - { - "id": c.id, - "name": c.name, - "description": c.description or "", - "order": c.order or 0 - } - for c in candidates - ] + for election in elections: + if election.id in existing_election_ids: + logger.debug(f" ⊘ Election {election.id} ({election.name}) already on blockchain") + skipped_count += 1 + continue - # Record to blockchain - record_election_to_blockchain( - election_id=election.id, - election_name=election.name, - election_description=election.description or "", - candidates=candidates_data, - start_date=election.start_date.isoformat(), - end_date=election.end_date.isoformat(), - is_active=election.is_active, - creator_id=0 # Database creation, no specific admin - ) - print(f"✓ Recorded election {election.id} ({election.name}) to blockchain") - except Exception as e: - print(f"✗ Failed to record election {election.id} to blockchain: {e}") + try: + # Get candidates for this election + candidates = db.query(models.Candidate).filter( + models.Candidate.election_id == election.id + ).all() - # Verify blockchain integrity - if elections_blockchain.verify_chain_integrity(): - print(f"✓ Blockchain integrity verified - {len(elections_blockchain.blocks)} blocks") - else: - print("✗ Blockchain integrity check failed!") + logger.debug(f" Recording election {election.id} with {len(candidates)} candidates") + + candidates_data = [ + { + "id": c.id, + "name": c.name, + "description": c.description or "", + "order": c.order or 0 + } + for c in candidates + ] + + # Record to blockchain + block = record_election_to_blockchain( + election_id=election.id, + election_name=election.name, + election_description=election.description or "", + candidates=candidates_data, + start_date=election.start_date.isoformat(), + end_date=election.end_date.isoformat(), + is_active=election.is_active, + creator_id=0 # Database creation, no specific admin + ) + logger.info( + f" ✓ Recorded election {election.id} ({election.name})\n" + f" Block #{block.index}, Hash: {block.block_hash[:16]}..., " + f"Candidates: {block.candidates_count}" + ) + recorded_count += 1 + + except Exception as e: + logger.error( + f" ✗ Failed to record election {election.id} ({election.name}): {e}", + exc_info=True + ) + + logger.info(f"Recording summary: {recorded_count} new, {skipped_count} skipped") + + # Verify blockchain integrity + logger.info(f"Verifying blockchain integrity ({len(elections_blockchain.blocks)} blocks)...") + if elections_blockchain.verify_chain_integrity(): + logger.info(f"✓ Blockchain integrity verified successfully") + else: + logger.error("✗ Blockchain integrity check FAILED - possible corruption!") + + logger.info("-" * 60) + logger.info(f"Blockchain Initialization Complete") + logger.info(f" Total blocks: {len(elections_blockchain.blocks)}") + logger.info(f" Chain valid: {elections_blockchain.verify_chain_integrity()}") + logger.info("-" * 60) + + except Exception as e: + logger.error( + f"Blockchain initialization failed with error: {e}", + exc_info=True + ) + raise diff --git a/e-voting-system/backend/logging_config.py b/e-voting-system/backend/logging_config.py new file mode 100644 index 0000000..f533144 --- /dev/null +++ b/e-voting-system/backend/logging_config.py @@ -0,0 +1,93 @@ +""" +Logging configuration for E-Voting Backend. + +Provides structured logging with appropriate levels for different modules. +""" + +import logging +import sys +from datetime import datetime + +# Create custom formatter with emojis and colors +class ColoredFormatter(logging.Formatter): + """Custom formatter with colors and emojis for better visibility""" + + # ANSI color codes + COLORS = { + 'DEBUG': '\033[36m', # Cyan + 'INFO': '\033[32m', # Green + 'WARNING': '\033[33m', # Yellow + 'ERROR': '\033[31m', # Red + 'CRITICAL': '\033[35m', # Magenta + } + RESET = '\033[0m' + + # Emoji prefixes + EMOJIS = { + 'DEBUG': '🔍', + 'INFO': 'ℹ️ ', + 'WARNING': '⚠️ ', + 'ERROR': '❌', + 'CRITICAL': '🔥', + } + + def format(self, record): + # Add color and emoji + levelname = record.levelname + emoji = self.EMOJIS.get(levelname, '') + color = self.COLORS.get(levelname, '') + + # Format message + timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S') + formatted = f"{color}{emoji} {timestamp} - {record.name} - {levelname} - {record.getMessage()}{self.RESET}" + + # Add exception info if present + if record.exc_info: + formatted += f"\n{self.format_exception(record.exc_info)}" + + return formatted + + def format_exception(self, exc_info): + """Format exception info""" + import traceback + return '\n'.join(traceback.format_exception(*exc_info)) + + +def setup_logging(level=logging.INFO): + """ + Setup logging for the entire application. + + Args: + level: Logging level (default: logging.INFO) + """ + # Remove existing handlers + root_logger = logging.getLogger() + for handler in root_logger.handlers[:]: + root_logger.removeHandler(handler) + + # Create console handler with colored formatter + console_handler = logging.StreamHandler(sys.stdout) + console_handler.setLevel(level) + formatter = ColoredFormatter() + console_handler.setFormatter(formatter) + + # Add handler to root logger + root_logger.addHandler(console_handler) + root_logger.setLevel(level) + + # Set specific loggers + logging.getLogger('backend').setLevel(level) + logging.getLogger('backend.blockchain_elections').setLevel(logging.DEBUG) + logging.getLogger('backend.init_blockchain').setLevel(logging.INFO) + logging.getLogger('backend.services').setLevel(logging.INFO) + logging.getLogger('backend.main').setLevel(logging.INFO) + + # Suppress verbose third-party logging + logging.getLogger('sqlalchemy.engine').setLevel(logging.WARNING) + logging.getLogger('sqlalchemy.pool').setLevel(logging.WARNING) + logging.getLogger('uvicorn').setLevel(logging.INFO) + logging.getLogger('uvicorn.access').setLevel(logging.WARNING) + + +# Setup logging on import +setup_logging() diff --git a/e-voting-system/backend/main.py b/e-voting-system/backend/main.py index 6bdcf53..438f375 100644 --- a/e-voting-system/backend/main.py +++ b/e-voting-system/backend/main.py @@ -2,23 +2,45 @@ Application FastAPI principale. """ +import logging from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware from .config import settings from .database import init_db, get_db from .routes import router from .init_blockchain import initialize_elections_blockchain +from .logging_config import setup_logging + +# Setup logging for the entire application +setup_logging(level=logging.INFO) +logger = logging.getLogger(__name__) + +logger.info("=" * 70) +logger.info("🚀 Starting E-Voting Backend") +logger.info("=" * 70) # Initialiser la base de données -init_db() +logger.info("📦 Initializing database...") +try: + init_db() + logger.info("✓ Database initialized successfully") +except Exception as e: + logger.error(f"✗ Database initialization failed: {e}", exc_info=True) + raise # Initialiser la blockchain avec les élections existantes +logger.info("⛓️ Initializing blockchain...") try: db = next(get_db()) initialize_elections_blockchain(db) db.close() + logger.info("✓ Blockchain initialization completed") except Exception as e: - print(f"Warning: Failed to initialize blockchain on startup: {e}") + logger.error(f"⚠️ Blockchain initialization failed (non-fatal): {e}", exc_info=True) + +logger.info("=" * 70) +logger.info("✓ Backend initialization complete, starting FastAPI app") +logger.info("=" * 70) # Créer l'application FastAPI app = FastAPI( diff --git a/e-voting-system/backend/services.py b/e-voting-system/backend/services.py index 91ee4fa..11da7ae 100644 --- a/e-voting-system/backend/services.py +++ b/e-voting-system/backend/services.py @@ -2,6 +2,7 @@ Service de base de données - Opérations CRUD. """ +import logging from sqlalchemy.orm import Session from sqlalchemy import func from . import models, schemas @@ -9,6 +10,8 @@ from .auth import hash_password, verify_password from datetime import datetime from .blockchain_elections import record_election_to_blockchain +logger = logging.getLogger(__name__) + class VoterService: """Service pour gérer les électeurs""" @@ -109,11 +112,15 @@ class ElectionService: # Record to blockchain immediately after creation try: + logger.debug(f"Recording election {db_election.id} ({name}) to blockchain") + # Get candidates for this election to include in blockchain record candidates = db.query(models.Candidate).filter( models.Candidate.election_id == db_election.id ).all() + logger.debug(f" Found {len(candidates)} candidates for election {db_election.id}") + candidates_data = [ { "id": c.id, @@ -125,7 +132,7 @@ class ElectionService: ] # Record election to blockchain - record_election_to_blockchain( + block = record_election_to_blockchain( election_id=db_election.id, election_name=name, election_description=description, @@ -135,9 +142,16 @@ class ElectionService: is_active=is_active, creator_id=creator_id ) + logger.info( + f"✓ Election {db_election.id} recorded to blockchain " + f"(Block #{block.index}, Hash: {block.block_hash[:16]}...)" + ) except Exception as e: # Log error but don't fail election creation - print(f"Warning: Could not record election {db_election.id} to blockchain: {e}") + logger.error( + f"Warning: Could not record election {db_election.id} to blockchain: {e}", + exc_info=True + ) return db_election