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
This commit is contained in:
Alexis Bruneteau 2025-11-07 03:06:38 +01:00
parent d4ce64f097
commit 7af375f8c0
4 changed files with 219 additions and 49 deletions

View File

@ -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. are recorded to the blockchain if they aren't already.
""" """
import logging
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from . import models from . import models
from .blockchain_elections import elections_blockchain, record_election_to_blockchain from .blockchain_elections import elections_blockchain, record_election_to_blockchain
from datetime import datetime from datetime import datetime
import json import json
logger = logging.getLogger(__name__)
def initialize_elections_blockchain(db: Session) -> None: def initialize_elections_blockchain(db: Session) -> None:
""" """
@ -22,55 +25,93 @@ def initialize_elections_blockchain(db: Session) -> None:
Args: Args:
db: Database session db: Database session
""" """
# Get all elections from database logger.info("-" * 60)
elections = db.query(models.Election).all() logger.info("Blockchain Initialization Started")
logger.info("-" * 60)
if not elections: try:
print("No elections to record to blockchain") # Get all elections from database
return elections = db.query(models.Election).all()
logger.info(f"Found {len(elections)} elections in database")
# Check which elections are already on blockchain if not elections:
existing_election_ids = {block.election_id for block in elections_blockchain.blocks} logger.warning("No elections to record to blockchain")
return
# Record each election that isn't already on blockchain # Check which elections are already on blockchain
for election in elections: existing_election_ids = {block.election_id for block in elections_blockchain.blocks}
if election.id in existing_election_ids: logger.info(f"Blockchain currently has {len(existing_election_ids)} elections")
print(f"Election {election.id} already on blockchain, skipping")
continue
try: # Record each election that isn't already on blockchain
# Get candidates for this election recorded_count = 0
candidates = db.query(models.Candidate).filter( skipped_count = 0
models.Candidate.election_id == election.id
).all()
candidates_data = [ for election in elections:
{ if election.id in existing_election_ids:
"id": c.id, logger.debug(f" ⊘ Election {election.id} ({election.name}) already on blockchain")
"name": c.name, skipped_count += 1
"description": c.description or "", continue
"order": c.order or 0
}
for c in candidates
]
# Record to blockchain try:
record_election_to_blockchain( # Get candidates for this election
election_id=election.id, candidates = db.query(models.Candidate).filter(
election_name=election.name, models.Candidate.election_id == election.id
election_description=election.description or "", ).all()
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}")
# Verify blockchain integrity logger.debug(f" Recording election {election.id} with {len(candidates)} candidates")
if elections_blockchain.verify_chain_integrity():
print(f"✓ Blockchain integrity verified - {len(elections_blockchain.blocks)} blocks") candidates_data = [
else: {
print("✗ Blockchain integrity check failed!") "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

View File

@ -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()

View File

@ -2,23 +2,45 @@
Application FastAPI principale. Application FastAPI principale.
""" """
import logging
from fastapi import FastAPI from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware from fastapi.middleware.cors import CORSMiddleware
from .config import settings from .config import settings
from .database import init_db, get_db from .database import init_db, get_db
from .routes import router from .routes import router
from .init_blockchain import initialize_elections_blockchain 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 # 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 # Initialiser la blockchain avec les élections existantes
logger.info("⛓️ Initializing blockchain...")
try: try:
db = next(get_db()) db = next(get_db())
initialize_elections_blockchain(db) initialize_elections_blockchain(db)
db.close() db.close()
logger.info("✓ Blockchain initialization completed")
except Exception as e: 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 # Créer l'application FastAPI
app = FastAPI( app = FastAPI(

View File

@ -2,6 +2,7 @@
Service de base de données - Opérations CRUD. Service de base de données - Opérations CRUD.
""" """
import logging
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from sqlalchemy import func from sqlalchemy import func
from . import models, schemas from . import models, schemas
@ -9,6 +10,8 @@ from .auth import hash_password, verify_password
from datetime import datetime from datetime import datetime
from .blockchain_elections import record_election_to_blockchain from .blockchain_elections import record_election_to_blockchain
logger = logging.getLogger(__name__)
class VoterService: class VoterService:
"""Service pour gérer les électeurs""" """Service pour gérer les électeurs"""
@ -109,11 +112,15 @@ class ElectionService:
# Record to blockchain immediately after creation # Record to blockchain immediately after creation
try: try:
logger.debug(f"Recording election {db_election.id} ({name}) to blockchain")
# Get candidates for this election to include in blockchain record # Get candidates for this election to include in blockchain record
candidates = db.query(models.Candidate).filter( candidates = db.query(models.Candidate).filter(
models.Candidate.election_id == db_election.id models.Candidate.election_id == db_election.id
).all() ).all()
logger.debug(f" Found {len(candidates)} candidates for election {db_election.id}")
candidates_data = [ candidates_data = [
{ {
"id": c.id, "id": c.id,
@ -125,7 +132,7 @@ class ElectionService:
] ]
# Record election to blockchain # Record election to blockchain
record_election_to_blockchain( block = record_election_to_blockchain(
election_id=db_election.id, election_id=db_election.id,
election_name=name, election_name=name,
election_description=description, election_description=description,
@ -135,9 +142,16 @@ class ElectionService:
is_active=is_active, is_active=is_active,
creator_id=creator_id 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: except Exception as e:
# Log error but don't fail election creation # 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 return db_election