Loading...
Development

Below is the new section you can insert right after Section 14.6 – Debugging with pdb (or before the Mini-Project).

Below is the new section you can insert right after Section 14.6 – Debugging with pdb (or before the Mini-Project).
It follows the same clean, professional style and teaches production-grade logging with real-world examples, best practices, and integration with the Task Manager.


14.7 Debugging with logging (Production-Ready Debugging)

“Print statements are the assembly language of debugging.”
“Logging is the C of debugging.”Modern Python Wisdom


Why logging > print()?

print()logging
Always outputsConfigurable levels
No timestampsBuilt-in metadata
Hard to disableDisable per module
No file outputWrite to files, rotate
No structureJSON, structured logs

1. Basic Setup

# logger.py
import logging

# Create a logger
logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG)  # Capture everything

# Create handlers
console_handler = logging.StreamHandler()
file_handler = logging.FileHandler("app.log")

# Set levels
console_handler.setLevel(logging.INFO)
file_handler.setLevel(logging.DEBUG)

# Create formatter
formatter = logging.Formatter(
    "%(asctime)s - %(name)s - %(levelname)s - %(filename)s:%(lineno)d - %(message)s"
)
console_handler.setFormatter(formatter)
file_handler.setFormatter(formatter)

# Add handlers to logger
logger.addHandler(console_handler)
logger.addHandler(file_handler)

2. Log Levels (Use Wisely!)

LevelWhen to Use
DEBUGDetailed info, variable values
INFONormal operation milestones
WARNINGUnexpected but handled
ERRORSerious problem, function failed
CRITICALApp will crash or corrupt
logger.debug("User data: %s", user_dict)     # Safe: no string formatting issues
logger.info("User %s logged in", username)
logger.warning("Deprecated API used")
logger.error("Failed to save task", exc_info=True)
logger.critical("Database connection lost!")

3. Best Practice: Never interpolate sensitive data

# BAD
logger.debug(f"User password: {password}")

# GOOD
logger.debug("Processing login for user: %s", username)

4. Real Example: Log-Enabled Task Manager

# task_manager.py (updated with logging)
import json
from dataclasses import dataclass
from pathlib import Path
from typing import List
import logging

# Configure root logger (once at app start)
logging.basicConfig(
    level=logging.DEBUG,
    format="%(asctime)s [%(levelname)s] %(name)s:%(lineno)d - %(message)s",
    handlers=[
        logging.FileHandler("task_manager.log"),
        logging.StreamHandler()
    ]
)

logger = logging.getLogger(__name__)

@dataclass
class Task:
    title: str
    done: bool = False

class TaskManager:
    def __init__(self, file_path: str = "tasks.json"):
        self.file = Path(file_path)
        self.tasks: List[Task] = []
        logger.info("Initializing TaskManager with file: %s", self.file)
        self.load()

    def load(self):
        if self.file.exists():
            try:
                data = json.loads(self.file.read_text())
                self.tasks = [Task(**t) for t in data]
                logger.info("Loaded %d tasks from disk", len(self.tasks))
            except json.JSONDecodeError as e:
                logger.error("Corrupted JSON file: %s", e, exc_info=True)
                self.tasks = []
        else:
            logger.warning("No tasks file found. Starting fresh.")

    def save(self):
        try:
            data = [t.__dict__ for t in self.tasks]
            self.file.write_text(json.dumps(data, indent=2))
            logger.debug("Saved %d tasks to %s", len(self.tasks), self.file)
        except OSError as e:
            logger.critical("Failed to save tasks!", exc_info=True)

    def add(self, title: str):
        if not title.strip():
            logger.warning("Attempted to add empty task title")
            raise ValueError("Title cannot be empty")
        self.tasks.append(Task(title.strip()))
        logger.info("Added new task: %s", title.strip())
        self.save()

    def complete(self, index: int):
        if 1 <= index <= len(self.tasks):
            task = self.tasks[index-1]
            task.done = True
            logger.info("Marked task as done: %s", task.title)
            self.save()
        else:
            logger.error("Invalid task index: %d (valid: 1-%d)", index, len(self.tasks))

    def list(self):
        logger.debug("Listing %d tasks", len(self.tasks))
        for i, task in enumerate(self.tasks, 1):
            status = "DONE" if task.done else "PENDING"
            print(f"{i}. [{status}] {task.title}")

5. Run & See Logs

python task_manager.py add "Buy milk"
python task_manager.py list

Console Output:

2025-04-05 10:00:01,234 [INFO] __main__:23 - Initializing TaskManager with file: tasks.json
2025-04-05 10:00:01,235 [WARNING] __main__:30 - No tasks file found. Starting fresh.
2025-04-05 10:00:01,236 [INFO] __main__:55 - Added new task: Buy milk
2025-04-05 10:00:01,236 [DEBUG] __main__:47 - Saved 1 tasks to tasks.json
1. [PENDING] Buy milk

task_manager.log file created automatically


6. Advanced: JSON Logging (for Log Aggregators)

pip install python-json-logger
from pythonjsonlogger import jsonlogger
import logging

logger = logging.getLogger()
handler = logging.StreamHandler()
formatter = jsonlogger.JsonFormatter(
    '%(asctime)s %(name)s %(levelname)s %(message)s %(filename)s %(lineno)d'
)
handler.setFormatter(formatter)
logger.addHandler(handler)

Output:

{
  "asctime": "2025-04-05 10:00:01,236",
  "name": "__main__",
  "levelname": "INFO",
  "message": "Added new task: Buy milk",
  "filename": "task_manager.py",
  "lineno": 55
}

Perfect for ELK, Datadog, Splunk


7. Logging in Libraries vs Apps

ContextLogger Name
App entrypointlogging.getLogger(__name__)
Library modulelogging.getLogger('myapp.utils')
Third-partyDon’t configure — let app do it

Never do this in a library:

logging.basicConfig(...)  # Only in main script!

8. Disable Noisy Logs (e.g. requests)

logging.getLogger("urllib3").setLevel(logging.WARNING)
logging.getLogger("requests").setLevel(logging.WARNING)

9. Rotate Logs (Don’t Fill Disk!)

from logging.handlers import RotatingFileHandler

handler = RotatingFileHandler("app.log", maxBytes=10_000_000, backupCount=5)
logger.addHandler(handler)

app.log, app.log.1, ..., app.log.5 (50 MB total)


10. Best Practices Summary

DoDon’t
Use logger = logging.getLogger(__name__)Use print() in production
Log context (user ID, task ID)Log passwords or tokens
Use structured logs in prodHardcode log paths
Configure once in mainCall basicConfig() multiple times
Use levels appropriatelyLog everything at DEBUG in prod

11. Quick Config for Different Environments

# config/logging_config.py
import logging.config

LOGGING_CONFIG = {
    "version": 1,
    "disable_existing_loggers": False,
    "formatters": {
        "standard": {
            "format": "%(asctime)s [%(levelname)s] %(name)s: %(message)s"
        }
    },
    "handlers": {
        "console": {
            "class": "logging.StreamHandler",
            "formatter": "standard",
            "level": "INFO"
        },
        "file": {
            "class": "logging.handlers.RotatingFileHandler",
            "filename": "app.log",
            "maxBytes": 10_000_000,
            "backupCount": 5,
            "formatter": "standard",
            "level": "DEBUG"
        }
    },
    "root": {
        "handlers": ["console", "file"],
        "level": "DEBUG"
    }
}

# In main.py
import logging.config
from config.logging_config import LOGGING_CONFIG

logging.config.dictConfig(LOGGING_CONFIG)
logger = logging.getLogger(__name__)

12. VS Code: View Logs Live

  1. Open app.log
  2. Right-click → "Follow"
  3. Or use Log Viewer extension

Summary: Debugging Toolbox

ToolUse Case
print()Quick REPL check
pdb / ipdbStep-through, inspect state
loggingProduction monitoring, audit trail
pytestAutomated verification
sentry.ioError tracking in prod

Now your app logs like a real product!

Next Step: Add user ID to logs in Task Manager:

logger.info("User %s added task: %s", user_id, title)

Insert this section into your notes after Section 14.6.
Your Python tutorial now covers:

  • Testing (pytest)
  • Interactive Debugging (pdb/ipdb)
  • Production Debugging (logging)

Happy Logging!