Create a centralized logger and instrument every operation in the Prompt Vault — adds, searches, deletes, and errors
Day 49 of 80
In a multi-file app, you don't want to configure logging in every single module. Instead, you create one function — get_logger(name) — that any module can call to get a properly configured logger. The configuration (where to write, what format, what level) lives in one place.
Python's logging system is hierarchical. All named loggers descend from the root logger. When you call logging.getLogger("prompt_vault.models"), you get a logger that belongs to the prompt_vault namespace. The name appears in every log line, so you can see at a glance which module wrote each message:
This file has one job: give any module a ready-to-use, properly configured logger. The if not logger.handlers check prevents duplicate handlers if get_logger is called multiple times with the same name.
import logging
from pathlib import Path
def get_logger(name):
"""Return a configured logger for the given module name.
Usage in any module:
from logger import get_logger
logger = get_logger(__name__)
"""
logger = logging.getLogger(name)
# Guard: don't add handlers if they already exist
if not logger.handlers:
logger.setLevel(logging.DEBUG)
# Ensure the logs/ directory exists
log_dir = Path("logs")
log_dir.mkdir(exist_ok=True)
# File handler: DEBUG and above → logs/vault.log
fh = logging.FileHandler(log_dir / "vault.log")
fh.setLevel(logging.DEBUG)
fh.setFormatter(logging.Formatter(
"%(asctime)s [%(levelname)s] %(name)s: %(message)s",
datefmt="%Y-%m-%d %H:%M:%S"
))
# Console handler: WARNING and above → terminal
ch = logging.StreamHandler()
ch.setLevel(logging.WARNING)
ch.setFormatter(logging.Formatter(
"[%(levelname)s] %(message)s"
))
logger.addHandler(fh)
logger.addHandler(ch)
return logger
get_logger(__name__) will log to the same logs/vault.log file. The Path("logs").mkdir(exist_ok=True) creates the logs directory automatically — no manual setup needed. The if not logger.handlers guard is critical: without it, importing a module multiple times (which Python does in test runners) would add duplicate handlers and print each message twice.Now update vault.py (or wherever your PromptLibrary class lives) to import the logger and log every meaningful operation.
import json
from pathlib import Path
from models import Prompt
from exceptions import StorageError
from logger import get_logger
logger = get_logger(__name__) # module-level logger
class PromptLibrary:
def __init__(self, filepath="prompts.json"):
self.filepath = Path(filepath)
self.prompts = self._load()
def _load(self):
if not self.filepath.exists():
logger.info(f"No file at {self.filepath}, starting empty")
return []
try:
with self.filepath.open() as f:
data = json.load(f)
logger.info(f"Loaded {len(data)} prompts from {self.filepath}")
return [Prompt(**p) for p in data]
except json.JSONDecodeError as e:
logger.error(f"Corrupt JSON in {self.filepath}: {e}")
raise StorageError(f"Cannot read {self.filepath}: corrupt JSON")
def add(self, platform, shot, prompt):
p = Prompt(platform, shot, prompt) # raises InvalidPlatformError/EmptyFieldError if invalid
self.prompts.append(p)
self._save()
logger.info(f"Added prompt: [{p.platform}] {p.shot}")
return p
def delete(self, index):
if 0 <= index < len(self.prompts):
removed = self.prompts.pop(index)
self._save()
logger.info(f"Deleted prompt #{index}: [{removed.platform}] {removed.shot}")
return removed
else:
logger.warning(f"Delete failed: index {index} out of range ({len(self.prompts)} prompts)")
return None
def search(self, query):
q = query.lower()
results = [
p for p in self.prompts
if q in p.shot.lower() or q in p.prompt.lower()
]
logger.debug(f"Search '{query}': {len(results)} results")
return results
def _save(self):
try:
with self.filepath.open("w") as f:
json.dump([vars(p) for p in self.prompts], f, indent=2)
logger.debug(f"Saved {len(self.prompts)} prompts to {self.filepath}")
except OSError as e:
logger.error(f"Failed to save {self.filepath}: {e}")
raise StorageError(f"Cannot write {self.filepath}")
Add some prompts, do some searches, try to delete an out-of-range index. Then open logs/vault.log and read the timeline.
That log tells a complete story: startup, two adds, a search, a failed delete (with the reason), and a successful delete. If something breaks in production, you open this file and read what happened — in order, with exact timestamps.
Every serious Python application has a log file. When a bug gets reported — "it failed on Tuesday around 2pm" — you open the log, go to Tuesday 2pm, and read exactly what the app was doing. Without logging, you're debugging blind. With logging, you have a complete record. You've just given your Prompt Vault that superpower.
logger.py with the get_logger(name) factory functionlogs/ directory is created automatically on first runvault.py with logger calls in _load, add, delete, search, and _savelogs/vault.log to read the outputDay 50 is the Week 10 Review — and a milestone. You've reached Day 50 of 80: the halfway point of the entire course. You'll review what you built this week and look ahead at Week 11, where you'll replace JSON storage with a real SQLite database.