Logo OnlineCBT
PYTHON Tutorial
PYTHON · LESSON 31

Logging, Assertions and Debugging

Diagnose behaviour with structured logs, assertions and a debugger.

Level: Beginner to Advanced
Duration: ~60 Mins Deep-Dive
Updated: 26 Jul 2026

Lesson 31: Logging, Assertions and Debugging

1. Introduction to Production System Observability

In enterprise application engineering, code is executed in remote environments (like AWS cloud instances, Docker containers, or background background workers) without an interactive user watching the terminal output. When logical anomalies or system failures occur in production, relying on basic print() statements is completely inadequate: standard output messages are transient, unformatted, lack severity classification, and cannot be filtered or directed to persistent disk logs easily.

To build maintainable, observable, and enterprise-grade software, developers must master three core diagnostic toolsets:

  • Structured Logging (`logging` module): Recording timestamped, classified diagnostic messages across execution environments.
  • Defensive Assertions (`assert` statement): Validating internal code invariants and runtime preconditions during development.
  • Interactive Debugging (`breakpoint()` and `pdb`): Inspecting memory stack frames, stepping through line-by-line execution, and analyzing variable mutation states live.

2. Python's Standard `logging` Module

Python provides a robust built-in event logging framework via the standard logging module. It categorizes runtime log events by severity levels, allowing developers to filter out verbose debug messages in production while preserving critical alerts.

1. The 5 Standard Logging Severity Levels

  • DEBUG
  • 10
  • logging.debug()
  • Detailed low-level diagnostic information used strictly during local development and troubleshooting.
  • INFO
  • 20
  • logging.info()
  • Confirmation that system processes are operating as expected (e.g., job started, database connected).
  • WARNING
  • 30
  • logging.warning()
  • Indicates an unexpected event occurred or a potential future problem was detected (e.g., 'disk space low'), but execution continues.
  • ERROR
  • 40
  • logging.error()
  • A serious problem occurred, preventing a specific function or sub-task from completing execution.
  • CRITICAL
  • 50
  • logging.critical()
  • A fatal error occurred that may cause the entire application to shut down immediately.
Level Name Numeric Weight Method Call Syntax When to Use / Production Purpose
Default Severity Threshold Rule: By default, Python's root logger threshold is set to WARNING (Numeric weight 30). Any log calls with a lower severity level (e.g., INFO or DEBUG) will be silently ignored unless the threshold configuration is explicitly modified!
BASIC_LOGGING_DEMO.PY
import logging

# Configuring global logging threshold and formatting output schema
logging.basicConfig(
    level=logging.DEBUG,  # Enables DEBUG, INFO, WARNING, ERROR, CRITICAL
    format="%(asctime)s | [%(levelname)s] | %(name)s | %(message)s",
    datefmt="%Y-%m-%d %H:%M:%S"
)

logger = logging.getLogger("EnterpriseEngine")

print("=== Triggering Log Events Across All 5 Severity Levels ===")
logger.debug("Database query parameters: host=127.0.0.1, port=5432")
logger.info("Server initialized and listening on port 8080.")
logger.warning("Cache miss threshold reached (82% misses). Performance degraded.")
logger.error("Failed to serialize transaction payload to JSON!")
logger.critical("Database connection pool exhausted! System shutting down.")
OUTPUT
=== Triggering Log Events Across All 5 Severity Levels ===
2026-07-26 19:05:00 | [DEBUG] | EnterpriseEngine | Database query parameters: host=127.0.0.1, port=5432
2026-07-26 19:05:00 | [INFO] | EnterpriseEngine | Server initialized and listening on port 8080.
2026-07-26 19:05:00 | [WARNING] | EnterpriseEngine | Cache miss threshold reached (82% misses). Performance degraded.
2026-07-26 19:05:00 | [ERROR] | EnterpriseEngine | Failed to serialize transaction payload to JSON!
2026-07-26 19:05:00 | [CRITICAL] | EnterpriseEngine | Database connection pool exhausted! System shutting down.

2. File Logging via FileHandlers and Exception Logging

In enterprise setups, logs are streamed simultaneously to the terminal console and written to persistent storage log files on disk. Furthermore, passing exc_info=True or calling logger.exception() captures the full exception traceback automatically!

LOGGING_TO_FILE_AND_EXCEPTIONS.PY
import logging
from pathlib import Path

log_file_path = Path.cwd() / "application_audit.log"

# Setup custom logger object
logger = logging.getLogger("FileAuditLogger")
logger.setLevel(logging.INFO)

# Create File Handler for persistent file storage
file_handler = logging.FileHandler(log_file_path, mode="a", encoding="utf-8")
file_formatter = logging.Formatter("%(asctime)s [%(levelname)s] %(message)s")
file_handler.setFormatter(file_formatter)

# Attach handler to logger (preventing duplicate handlers)
if not logger.handlers:
    logger.addHandler(file_handler)

def execute_risky_operation(val_a: float, val_b: float):
    logger.info(f"Executing division task: ({val_a} / {val_b})")
    try:
        result = val_a / val_b
        logger.info(f"Operation completed cleanly. Result: {result:.2f}")
        return result
    except ZeroDivisionError:
        # logger.exception automatically appends full traceback info!
        logger.exception("CRITICAL_FAILURE: Division by zero encountered inside operation block!")
        return None

# Testing file logging with traceback capturing
execute_risky_operation(100.0, 2.0)
execute_risky_operation(50.0, 0.0)

print(f"Audit log saved to: {log_file_path.name}")
print("=== Read Back File Contents ===")
with open(log_file_path, mode="r", encoding="utf-8") as f:
    print(f.read().strip())
OUTPUT
Audit log saved to: application_audit.log
=== Read Back File Contents ===
2026-07-26 19:05:05 [INFO] Executing division task: (100.0 / 2.0)
2026-07-26 19:05:05 [INFO] Operation completed cleanly. Result: 50.00
2026-07-26 19:05:05 [INFO] Executing division task: (50.0 / 0.0)
2026-07-26 19:05:05 [ERROR] CRITICAL_FAILURE: Division by zero encountered inside operation block!
Traceback (most recent call last):
  File "main.py", line 18, in execute_risky_operation
    result = val_a / val_b
ZeroDivisionError: division by zero

3. Defensive Programming with Assertions (`assert`)

An Assertion is a internal sanity check statement used during development to verify that internal code invariants (preconditions, postconditions, or internal assumptions) hold true. If an assertion evaluates to False, Python raises an AssertionError exception immediately.

# Assertion Syntax Structure:
assert boolean_condition, "Optional error diagnostic message string"
    
The Golden Rule of Assertions: NEVER use assertions for user input validation or data sanitization in production code! Assertions can be globally disabled at runtime by passing the optimize flag to Python (python -O app.py). Use standard if-raise conditionals or Guard Clauses for domain input checks, reserving assert strictly for internal developer debugging!
ASSERTIONS_DEMO.PY
def calculate_discounted_price(original_price: float, discount_pct: float) -> float:
    """Calculates discounted net price with defensive internal assertion checks."""
    # Internal developer invariant assertions (Catching developer programming bugs)
    assert original_price >= 0.0, f"Invariant Broken: Price cannot be negative! Got: {original_price}"
    assert 0.0 <= discount_pct <= 1.0, f"Invariant Broken: Discount rate must be between 0.0 and 1.0! Got: {discount_pct}"
    
    final_price = original_price * (1.0 - discount_pct)
    
    # Post-condition assertion
    assert final_price <= original_price, "Invariant Broken: Final price exceeded original base price!"
    return round(final_price, 2)

# Valid Invocation
valid_price = calculate_discounted_price(100.0, 0.20)
print(f"Calculated Discounted Price: ${valid_price:.2f}")

# Invalid Invocation (Triggers AssertionError)
try:
    bad_price = calculate_discounted_price(100.0, 1.50)  # Invalid 150% discount!
except AssertionError as assert_err:
    print(f"ASSERTION CAUGHT -> {assert_err}")
OUTPUT
Calculated Discounted Price: $80.00
ASSERTION CAUGHT -> Invariant Broken: Discount rate must be between 0.0 and 1.0! Got: 1.5

4. Interactive Debugging with `breakpoint()` and `pdb`

When tracking complex bugs, stepping through code interactively is significantly more productive than adding multiple temporary print() statements. Python 3.7+ provides the built-in breakpoint() function, which invokes Python's interactive debugger tool: `pdb` (Python Debugger).

1. Essential `pdb` Interactive Command Cheat Sheet

  • n
  • next
  • Executes the current line and steps forward to the next line in the current function.
  • s
  • step
  • Steps INTO the function call on the current line to inspect internal scope.
  • c
  • continue
  • Resumes normal execution until the next breakpoint() or script completion.
  • p variable
  • print
  • Evaluates and prints the current value of variable in active stack memory.
  • l
  • list
  • Displays source code context lines surrounding the currently active line position.
  • q
  • quit
  • Aborts debugger execution and kills the Python process immediately.
`pdb` Command Full Name Equivalent Interactive Execution Behavior Description
DEBUGGING_PDB_SIMULATION.PY
def process_inventory_batch(items: list):
    """
    Demonstrates programmatic breakpoint invocation simulation.
    In terminal execution, calling breakpoint() pauses execution and opens pdb shell.
    """
    total_value = 0.0
    for idx, item in enumerate(items, start=1):
        # Simulating conditional breakpoint during developer debugging
        if item.get("price", 0) < 0:
            print(f"[DEBUGGER] Anomaly detected at item index #{idx}! Pre-breakpoint state logged.")
            # Real code: breakpoint()  <-- Opens interactive (Pdb) prompt
        
        total_value += item.get("price", 0) * item.get("quantity", 1)
        
    return total_value

sample_batch = [
    {"name": "Mouse", "price": 25.0, "quantity": 4},
    {"name": "Corrupted_Item", "price": -100.0, "quantity": 1},  # Invalid price!
    {"name": "Monitor", "price": 300.0, "quantity": 2}
]

batch_total = process_inventory_batch(sample_batch)
print(f"Processed Batch Net Total: ${batch_total:.2f}")
OUTPUT
[DEBUGGER] Anomaly detected at item index #2! Pre-breakpoint state logged.
Processed Batch Net Total: $600.00

5. Integrating Logging & Diagnostics with `match-case` Pattern Matching

Building centralized diagnostic routing engines involves combining logging calls, custom exception handling, and assert precondition checks with match-case structural pattern matching.

MATCH_CASE_DIAGNOSTIC_DISPATCHER.PY
import logging

logging.basicConfig(level=logging.INFO, format="[%(levelname)s] %(message)s")
logger = logging.getLogger("DiagnosticDispatcher")

def dispatch_diagnostic_event(event_payload: tuple | dict):
    """
    Routes diagnostic payloads to logging & assertions using match-case.
    Demonstrates evaluating complex diagnostic schemas dynamically.
    """
    match event_payload:
        case ("AUDIT_OK", service, duration_ms) if duration_ms <= 200:
            logger.info(f"Service '{service}' healthy -> Response time: {duration_ms}ms")
            return "STATUS_NORMAL"
            
        case ("AUDIT_OK", service, duration_ms) if duration_ms > 200:
            logger.warning(f"Service '{service}' LATENCY ALERT -> Response time: {duration_ms}ms (>200ms threshold)")
            return "STATUS_LATENCY_WARN"
            
        case {"status": "CRITICAL_FAILURE", "module": mod_name, "code": err_code}:
            logger.critical(f"MODULE '{mod_name}' CRASHED! Code: {err_code}. Escalating to On-Call Engineer...")
            return "STATUS_ESCALATED"
            
        case ("TEST_ASSERT", test_val) if isinstance(test_val, int):
            try:
                assert test_val > 0, f"Diagnostic Assertion Failed: {test_val} is not positive!"
                logger.info("Diagnostic Assertion Passed successfully.")
                return "STATUS_ASSERT_PASS"
            except AssertionError as err:
                logger.error(f"DIAGNOSTIC_FAIL: {err}")
                return "STATUS_ASSERT_FAIL"
            
        case _:
            logger.error(f"Unrecognized diagnostic payload schema -> {event_payload}")
            return "STATUS_UNKNOWN"

# Testing the diagnostic dispatcher
print("Result 1:", dispatch_diagnostic_event(("AUDIT_OK", "AuthService", 45)))
print("Result 2:", dispatch_diagnostic_event(("AUDIT_OK", "PaymentGateway", 450)))
print("Result 3:", dispatch_diagnostic_event({"status": "CRITICAL_FAILURE", "module": "DB_Cluster", "code": 500}))
print("Result 4:", dispatch_diagnostic_event(("TEST_ASSERT", -5)))
OUTPUT
[INFO] Service 'AuthService' healthy -> Response time: 45ms
Result 1: STATUS_NORMAL
[WARNING] Service 'PaymentGateway' LATENCY ALERT -> Response time: 450ms (>200ms threshold)
Result 2: STATUS_LATENCY_WARN
[CRITICAL] MODULE 'DB_Cluster' CRASHED! Code: 500. Escalating to On-Call Engineer...
Result 3: STATUS_ESCALATED
[ERROR] DIAGNOSTIC_FAIL: Diagnostic Assertion Failed: -5 is not positive!
Result 4: STATUS_ASSERT_FAIL

6. Frequently Asked Interview Questions with Answers

Q1: Why is using Python's `logging` module preferred over standard `print()` statements in enterprise applications?
Answer: logging provides severity categorization (DEBUG, INFO, WARNING, ERROR, CRITICAL), configurable output destinations (simultaneous console streams and persistent log files via Handlers), timestamp formatting, automatic exception traceback capturing (logger.exception()), and threshold filtering without needing to delete or modify code when deploying to production.
Q2: Why should `assert` statements NEVER be used for production user input validation?
Answer: Assertions can be globally disabled by running Python with optimization flags (e.g., python -O script.py), causing CPython to ignore all assert statements entirely. If security or input checks rely on assertions, those checks will be bypassed completely in optimized environments. Input validation must always use standard if-raise conditionals or Guard Clauses.
Q3: How do you capture a full Exception Traceback automatically inside a log file?
Answer: By invoking logger.exception("Error message") inside an except block. Alternatively, pass exc_info=True into any logging call (e.g., logger.error("Failed", exc_info=True)).
Q4: What is the function of `breakpoint()` in Python 3.7+?
Answer: breakpoint() is a built-in wrapper that pauses execution and opens the interactive Python Debugger (pdb) at that exact line position, allowing developers to inspect variables, step line-by-line, and debug runtime memory state.
Q5: What are the primary commands used inside the `pdb` interactive shell?
Answer: n (next line), s (step into function), c (continue execution), p var (print variable value), l (list source code lines), and q (quit debugger).

7. Homework & Practical Assignments

Task 1: Structured Logger & Assertion Benchmark

Create a script named logger_benchmark_task.py inside your lesson_31 folder:

  • Import logging and Path from pathlib.
  • Configure logging to write messages to system_monitor.log at DEBUG level with timestamp formatting.
  • Define function process_transaction(account_id: str, amount: float):
    • Add defensive developer assertions verifying amount > 0.
    • Log INFO message on start, DEBUG payload parameters, and WARNING if amount > 10,000.
  • Invoke function with valid and high-value transactions, then display file contents.

Task 2: Fault-Tolerant File Processor with Exception Logging

Create a script named log_file_processor_task.py:

  • Define function load_and_parse_json(filename: str):
    • Use a try-except block to attempt loading a target JSON file.
    • Log ERROR with logger.exception() if FileNotFoundError or json.JSONDecodeError occurs.
    • Use a match-case dispatcher to process the loaded dictionary payload or route error messages.
  • Test with a missing file and verify traceback capture in log output.

Task 3: Master Review Capstone Project — Enterprise Observability & System Diagnostics OS (`observability_engine_os.py`)

Create a script named observability_engine_os.py inside your lesson_31 folder. This assignment tests and integrates **ALL concepts learned across Lessons 1 through 31**.

Project Architectural Requirements Specification:

  1. Environment, Modules & Entry Guard (Lessons 18-20):
    • Import standard library modules (sys, datetime, json, csv, logging, time) and Path from pathlib.
    • Wrap main execution inside an if __name__ == "__main__": guard block.
  2. Enterprise Logging & Diagnostics (Lesson 31):
    • Set up a multi-handler logger streaming INFO messages to console and DEBUG logs to vault_system.log.
    • Incorporate internal defensive developer assertions (assert) for state invariant checking.
    • Implement programmatic breakpoint() simulation flags for diagnostic inspection.
  3. Context Managers & Resource Safeguards (Lesson 30):
    • Build a custom context manager DiagnosticContext(job_id) that benchmarks execution time, logs acquisition on __enter__, and logs completion on __exit__.
  4. Fault Tolerance & Custom Exceptions (Lesson 29):
    • Define custom exception classes: DiagnosticError(Exception) and ThresholdExceededError(DiagnosticError).
    • Wrap all operations in try-except-else-finally blocks and log exceptions using logger.exception().
  5. CSV & JSON Persistence Layer (Lessons 27-28):
    • Maintain a diagnostic storage directory telemetry_vault/ using pathlib.Path.
    • Persist metric telemetry maps to JSON files (metrics.json) and export audit reports to CSV (audit_export.csv).
  6. Nested Collections, Comprehensions & Data Structures (Lessons 21-26):
    • Maintain an in-memory active database represented as nested dictionaries, sets, and NamedTuples.
    • Use List and Dictionary Comprehensions to clean, filter, and extract diagnostic logs dynamically.
  7. Advanced Function Parameters & Scope (Lessons 15-17):
    • Structure code into pure modular functions with type hints, docstrings, early return Guard Clauses, and *args / **kwargs logging.
  8. Indefinite & Definite Loops (Lessons 13-14):
    • Run the interactive CLI interface inside a continuous while True menu loop.
    • Iterate through output reports using for loops with enumerate() and .items().
  9. Pattern Matching CLI Dispatcher (Lesson 12):
    • Process user CLI command tokens inside a match-case block:
      • case ["RUN", job_name, *args] → Execute task inside DiagnosticContext, validate invariants using assert, and record metric telemetry in JSON.
      • case ["AUDIT", "LOGS"] → Filter and display log file entries using list comprehensions.
      • case ["EXPORT", "CSV"] → Export operational metrics to CSV safely.
      • case ["EXIT" | "QUIT"] → Terminate session safely using a sentinel flag.
      • case _ → Output command error message via logger.error().
  10. Conditionals, Formatting & Foundations (Lessons 1-11):
    • Sanitize all string inputs using .strip() and .upper().
    • Format tabular reports using f-strings with field width alignment specifiers (:<15, :>10) and clear visual borders.

8. File & Workspace Directory Structure

Standard Course Workspace Layout:

Ensure all exercise files are stored inside their corresponding lesson directories:

python_mastery_course/
│
├── lesson_01/ ... lesson_30/
│
└── lesson_31/
    ├── basic_logging_demo.py
    ├── logging_to_file_and_exceptions.py
    ├── assertions_demo.py
    ├── debugging_pdb_simulation.py
    ├── match_case_diagnostic_dispatcher.py
    ├── logger_benchmark_task.py       <-- (Task 1)
    ├── log_file_processor_task.py     <-- (Task 2)
    └── observability_engine_os.py     <-- (Task 3: Master Review Capstone)
        

9. What We Will Learn Next

Next Up: Lesson 32 — Classes, Objects and Constructors

Congratulations on completing the core persistent data, file systems, fault tolerance, and diagnostics modules of the course! It is time to enter Module 4: Object-Oriented Programming (OOP) in Depth!

In the next lesson, we will cover:

  • Introduction to Object-Oriented Programming paradigm (OOP).
  • Defining custom Classes (`class`) and instantiating Objects.
  • The Constructor initializer method: __init__() and instance attribute binding.
  • Understanding the role of the self parameter and instance memory space.
  • Defining Instance Methods and combining class instances with match-case pattern matching.

📝 Live Lesson Practice

HTML/CSS JavaScript Python C++ C PHP
⌨️ Practice Inputs (लाइव इनपुट भरें) (खाली होने पर RED, भरने पर GREEN underline)
💻 Code Editor (Monaco VS Code Engine)
👀 Live Preview
Address Contact

+91 7877547686

E-mail

onlinecbtportal@gmail.com

Helpline Number

+91 7877547686


Click To Download
Get it on Google Play

ऐप डाउनलोड करने
के लिए Google Play पर
उपलब्ध है

WhatsApp Chat