Skip to main content

Skillber v1.0 is here!

Learn more

Debugging & Logging

Checking access...

# Simple but effective for small scripts
def complex_function(x, y):
print(f"DEBUG: x={x}, y={y}") # Quick inspection
result = x * y + x / y
print(f"DEBUG: result={result}")
return result

Python’s Built-in Debugger: pdb

import pdb
def buggy_function(n):
result = []
for i in range(n):
pdb.set_trace() # Breakpoint — execution pauses here
result.append(i * 2)
return result
# Interactive commands at breakpoint:
# n (next) — execute next line
# s (step) — step into function
# c (continue) — continue until next breakpoint
# p var — print variable
# l (list) — show current line context
# q (quit) — exit debugger

Using breakpoint() (Python 3.7+)

# Same as pdb.set_trace() but configurable via PYTHONBREAKPOINT env
def process_data(data):
result = []
for item in data:
breakpoint() # Pause here
result.append(item * 2)
return result

Post-Mortem Debugging

# Debug after an exception occurs
def buggy():
return 1 / 0
try:
buggy()
except:
import pdb
pdb.post_mortem() # Opens debugger at the crash point

Better Debugging with IPython and ipdb

Terminal window
pip install ipdb
import ipdb
# Better tab completion, syntax highlighting, etc.
ipdb.set_trace()

The traceback Module

import traceback
def deep_func():
return 1 / 0
def middle_func():
return deep_func()
try:
middle_func()
except:
# Print full traceback
traceback.print_exc()
# Get traceback as string
tb_str = traceback.format_exc()
print(f"Full traceback:\n{tb_str}")

Logging Best Practices

Basic Configuration

import logging
# Configure once at application start
logging.basicConfig(
level=logging.DEBUG,
format="%(asctime)s [%(levelname)s] %(name)s: %(message)s",
datefmt="%Y-%m-%d %H:%M:%S",
)
logger = logging.getLogger(__name__)
logger.debug("Detailed diagnostic info")
logger.info("Normal operation message")
logger.warning("Something unexpected but not an error")
logger.error("Operation failed")
logger.critical("System cannot continue")

Logging to File and Console

import logging
logger = logging.getLogger("myapp")
logger.setLevel(logging.DEBUG)
# File handler
file_handler = logging.FileHandler("app.log")
file_handler.setLevel(logging.INFO)
file_formatter = logging.Formatter(
"%(asctime)s [%(levelname)s] %(name)s: %(message)s"
)
file_handler.setFormatter(file_formatter)
# Console handler
console_handler = logging.StreamHandler()
console_handler.setLevel(logging.DEBUG)
console_formatter = logging.Formatter(
"%(levelname)s: %(message)s"
)
console_handler.setFormatter(console_formatter)
# Add both handlers
logger.addHandler(file_handler)
logger.addHandler(console_handler)
logger.info("This goes to both file and console")
logger.debug("This only goes to console")

Structured Logging (JSON)

import json
import logging
class JSONFormatter(logging.Formatter):
"""Format logs as JSON for machine readability."""
def format(self, record):
log_data = {
"timestamp": self.formatTime(record),
"level": record.levelname,
"logger": record.name,
"message": record.getMessage(),
}
if record.exc_info:
log_data["exception"] = self.formatException(record.exc_info)
return json.dumps(log_data)
handler = logging.StreamHandler()
handler.setFormatter(JSONFormatter())
logger = logging.getLogger("json_logger")
logger.addHandler(handler)
logger.setLevel(logging.DEBUG)
logger.error("Something went wrong", exc_info=True)
# {"timestamp": "2024-12-25 14:30:00", "level": "ERROR", ...}

Profiling Performance

import time
from functools import wraps
def timer(func):
"""Decorator to measure function execution time."""
@wraps(func)
def wrapper(*args, **kwargs):
start = time.perf_counter()
result = func(*args, **kwargs)
elapsed = time.perf_counter() - start
print(f"{func.__name__} took {elapsed:.4f}s")
return result
return wrapper
@timer
def slow_function():
time.sleep(0.5)
# Using timeit for micro-benchmarks
import timeit
# Measure expression
time = timeit.timeit('"-".join(str(n) for n in range(100))', number=10000)
# Measure function
def test():
return sum(range(1000))
time = timeit.timeit(test, number=10000)

Using cProfile

import cProfile
import pstats
def main():
total = 0
for i in range(1000):
total += sum(range(i))
return total
# Profile and see results
cProfile.run('main()', sort='cumtime')
# Save to file
cProfile.run('main()', 'profile_output')
p = pstats.Stats('profile_output')
p.sort_stats('time').print_stats(10) # Top 10 by time

Linting and Type Checking

Terminal window
# Linting
pip install pylint
pylint mymodule.py
# Type checking
pip install mypy
mypy mymodule.py --strict
# Auto-formatting
pip install black
black mymodule.py
# Import sorting
pip install isort
isort mymodule.py

Error Monitoring Patterns

import logging
from functools import wraps
logger = logging.getLogger(__name__)
def monitor(func):
"""Decorator that monitors function health."""
@wraps(func)
def wrapper(*args, **kwargs):
try:
result = func(*args, **kwargs)
logger.info(f"{func.__name__} succeeded")
return result
except Exception as e:
logger.error(f"{func.__name__} failed: {e}", exc_info=True)
raise
return wrapper

Key Takeaways

  • breakpoint() (3.7+) opens pdb — use it for interactive debugging
  • pdb commands: n next, s step into, c continue, p print, q quit
  • Use logging instead of print for production code
  • Configure log levels: DEBUG → INFO → WARNING → ERROR → CRITICAL
  • Use multiple handlers (file + console) with different levels
  • Structured logging (JSON) helps with log aggregation tools
  • timeit for micro-benchmarks, cProfile for profiling
  • pylint for linting, mypy for type checking, black for formatting
  • Monitor functions with a logging decorator for observability