Debugging & Logging
Checking access...
Print Debugging (Quick & Dirty)
# Simple but effective for small scriptsdef complex_function(x, y): print(f"DEBUG: x={x}, y={y}") # Quick inspection result = x * y + x / y print(f"DEBUG: result={result}") return resultPython’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 debuggerUsing breakpoint() (Python 3.7+)
# Same as pdb.set_trace() but configurable via PYTHONBREAKPOINT envdef process_data(data): result = [] for item in data: breakpoint() # Pause here result.append(item * 2) return resultPost-Mortem Debugging
# Debug after an exception occursdef buggy(): return 1 / 0
try: buggy()except: import pdb pdb.post_mortem() # Opens debugger at the crash pointBetter Debugging with IPython and ipdb
pip install ipdbimport 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 startlogging.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 handlerfile_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 handlerconsole_handler = logging.StreamHandler()console_handler.setLevel(logging.DEBUG)console_formatter = logging.Formatter( "%(levelname)s: %(message)s")console_handler.setFormatter(console_formatter)
# Add both handlerslogger.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 jsonimport 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 timefrom 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
@timerdef slow_function(): time.sleep(0.5)
# Using timeit for micro-benchmarksimport timeit
# Measure expressiontime = timeit.timeit('"-".join(str(n) for n in range(100))', number=10000)
# Measure functiondef test(): return sum(range(1000))
time = timeit.timeit(test, number=10000)Using cProfile
import cProfileimport pstats
def main(): total = 0 for i in range(1000): total += sum(range(i)) return total
# Profile and see resultscProfile.run('main()', sort='cumtime')
# Save to filecProfile.run('main()', 'profile_output')p = pstats.Stats('profile_output')p.sort_stats('time').print_stats(10) # Top 10 by timeLinting and Type Checking
# Lintingpip install pylintpylint mymodule.py
# Type checkingpip install mypymypy mymodule.py --strict
# Auto-formattingpip install blackblack mymodule.py
# Import sortingpip install isortisort mymodule.pyError Monitoring Patterns
import loggingfrom 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 wrapperKey Takeaways
breakpoint()(3.7+) opens pdb — use it for interactive debugging- pdb commands:
nnext,sstep into,ccontinue,pprint,qquit - Use
logginginstead ofprintfor 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
timeitfor micro-benchmarks,cProfilefor profilingpylintfor linting,mypyfor type checking,blackfor formatting- Monitor functions with a logging decorator for observability