Python

Verified against ChatGPT · 2026-08-01

Replace print() debugging with structured logging that survives production

A prompt for migrating print-based debugging to the logging module with correct levels, structured context fields, and no logger-configuration surprises at import time — instead of a find-and-replace of print with logging.info that keeps every other bad habit.

Claude CodeChatGPT (GPT-5.1)GitHub Copilot Chat4 fillable variables

The prompt

Ready to copy — highlighted parts are example details you can swap.

Migrate the print() calls in this code to proper structured logging. This is not a find-and-replace of print with logging.info — every call needs the right level, and the logger needs to be configured the way a real service actually configures logging.

CODE
A batch job that prints progress every 1000 rows, prints the full row dict on error, and prints a final summary.

RUNTIME CONTEXT
A Celery worker running inside a Docker container; stdout/stderr are captured by the container runtime and shipped to a log aggregator.

CONTEXTUAL FIELDS NEEDED
job_id, batch_size, and the row's primary key when logging a per-row failure.

LOG AGGREGATION TARGET
Logs are shipped to a system expecting JSON lines with a "message" and "level" key at minimum.

REQUIREMENTS
1. Get the logger with logger = logging.getLogger(__name__) at module level, never the root logger directly and never a logger configured inside a function that runs on every call — logging configuration (handlers, formatters, levels) belongs in one place at application startup, not scattered across modules.
2. Choose the right level per call based on what it actually reports: DEBUG for detail only useful while actively debugging, INFO for normal operational events worth keeping, WARNING for something recoverable but worth a human's attention, ERROR for a failure that needs investigation, CRITICAL only for something that threatens the whole process. A print() that was really tracking "did we get here" during development is DEBUG, not INFO — don't upgrade its importance just because it's becoming a real log call.
3. Attach job_id, batch_size, and the row's primary key when logging a per-row failure. as structured fields (via logging's extra= parameter, or a structured logging library if Logs are shipped to a system expecting JSON lines with a "message" and "level" key at minimum. expects JSON lines), not interpolated into the message string — a field baked into the string can't be filtered or aggregated on later, where a structured field can.
4. Use logger.exception(...) inside an except block when logging a caught error, so the traceback is captured automatically — don't manually format str(exc) and lose the stack trace that would have told someone where it actually happened.
5. Never log a secret, credential, or full request/response body containing personal data — if A batch job that prints progress every 1000 rows, prints the full row dict on error, and prints a final summary. currently prints something like that, flag it explicitly and log a redacted or truncated version instead, don't just move the same leak into the log aggregation pipeline.
6. Use %s-style lazy formatting (logger.info("processed %s items", count)) rather than an f-string in the log call, so string formatting doesn't run at all when the message would be filtered out by the configured level.
7. Where a print() was actually the only visible signal of progress on a long-running job, don't just downgrade it to a DEBUG log and let it disappear from default output — decide explicitly whether A Celery worker running inside a Docker container; stdout/stderr are captured by the container runtime and shipped to a log aggregator. needs an INFO-level heartbeat instead (a periodic "processed N of M" line) so the operational visibility the print() gave for free isn't quietly lost in the migration.

OUTPUT FORMAT
1. The migrated code.
2. A table: each original print() call, the level it became, and why that level.
3. Anything flagged under the secrets/PII rule, and what changed.
4. One sentence on whether any operational visibility the original print() calls provided was preserved at an appropriate level, or explicitly and deliberately dropped.

Customize

Optional — swap in your own details for the highlighted parts above.

Why this works

Requiring logging.getLogger(__name__) at module level rather than the root logger addresses how Python's logging module actually propagates: loggers are organized in a dotted hierarchy matching module paths, so getting one per module by name is what lets a real deployment set different levels for different subsystems later (turning DEBUG on for one noisy module without drowning every other module's logs in the same verbosity), where logging directly against the root logger collapses that hierarchy and makes per-module filtering impossible after the fact. Forcing a deliberate level choice per call, rather than a blanket print-to-info replacement, matters because a print statement's implicit importance was always "someone was looking at the terminal when this ran" — that tells you nothing about whether the same information deserves to be kept in production logs at INFO, permanently, forever, at whatever volume this code runs; most print-debugging call sites are actually DEBUG-level noise that should be filterable out by default, and treating all of them as INFO just relocates print-spam into the log aggregator instead of fixing it. The lazy %s-formatting requirement is a real, measurable performance detail specific to how the logging module works: logger.debug(f"...") evaluates the f-string and does the formatting work every single time that line executes, even when the configured level means the message will be immediately discarded, while logger.debug("...%s", value) only performs the formatting if the message will actually be emitted — at high call volume in a hot loop, this is the difference between debug logging being nearly free when disabled and debug logging silently costing real CPU time in production regardless of whether anyone will ever read the output. The secrets/PII flag exists because migrating a print() statement to a logger call changes its blast radius: a print() output disappears with the terminal session, but a structured log line typically gets durably shipped, indexed, and retained by {{log_aggregation_target}} for weeks or months, so a value that was a minor risk sitting in a developer's terminal becomes a durable, searchable, possibly-compliance-relevant record the moment it's migrated into the logging pipeline unexamined.

Verified against

ChatGPT GPT-5.1 · 2026-08-01

Changelog

  • 2026-08-01 Initial publish, verified against ChatGPT (GPT-5.1) on Python 3.12 stdlib logging.

Need this built into your business?

If a prompt isn't enough — custom software, built and maintained for you — that's Scult's day job.

EXPLORE CUSTOM SOFTWARE
All Python prompts

Check your AI visibility

One URL in, a 0–100 score and the exact fixes out.

RUN THE CHECK

Browse all the tools

15 tools across six categories
13 of them never send your data anywhere

Free · No signup · No trial clock

SEE THE DIRECTORY