DEV Community

Cover image for Python Logging vs Print - Why Logging Is Better for Debugging
Developer Service
Developer Service

Posted on • Originally published at developer-service.blog

Python Logging vs Print - Why Logging Is Better for Debugging

We’ve all been there: you’re writing Python code, something isn’t behaving as expected, and your first instinct is to scatter a few print() statements around. A quick print("Reached here") to see if the function runs, or print("x:", x) to check a variable’s value. It usually does the job, at least, until it doesn’t.

That was also my approach. print() was my sidekick, always there to give me a quick peek under the hood.

But here’s the thing: print() is fine when you’re tinkering, not when you’re building. As your code grows up, you need more than quick checks, you need context, structure, and persistence.

That’s where Python’s logging module comes in. Logging isn’t just a fancier print(); it’s a complete toolkit that transforms debugging. It tells you what your program is doing, when it’s doing it, and - most importantly - why things go wrong.


Why print() Isn’t Enough

At first, print() seems really usefull. Drop a line in your code, run it, and voilà, you see what’s happening. But as soon as your project grows beyond a small script, the cracks start to show.

Here’s where print() falls short:

  • No levels of importance: Every message looks identical. Whether it’s a harmless check or a serious error, print("something went wrong") doesn’t tell you how worried you should be.

  • No timestamps or context: When did the error happen? In which part of the code? print() won’t say. All you get is raw text, which quickly becomes useless once things get complicated.

  • No persistence: The moment your program finishes, your precious debug trail disappears. If something breaks overnight, you wake up with zero evidence of what happened.

  • Hard to clean up: Nothing clutters a codebase faster than dozens of stray print() statements. Commenting them out, deleting them, or forgetting one in production is a headache we’ve all endured.

  • Not scalable: print() might be fine when you’re coding alone, but in production systems or team projects, it simply doesn’t cut it. Logs are a shared language; print() is just noise.

print() is like a flashlight, it helps in a dark room, but it won’t light up the whole path when you’re navigating a forest. For that, you need something built for the job: logging.


What Logging Brings to the Table

Switching from print() to logging, it's like stepping out of the dark and into a control room. Suddenly, I wasn’t just seeing raw messages, I was getting organized, meaningful information.

Here’s why logging makes such a difference:

  • Log levels: Not every message deserves the same attention. With logging, I can mark something as DEBUG when I’m tracing the flow, use INFO for normal events, raise a WARNING for potential issues, and reserve ERROR and CRITICAL for real problems. It’s like going from flat text to a prioritized alert system.

  • Configurability: Tired of drowning in debug messages? With logging, I don’t have to delete anything, I just change the log level. One setting controls how much detail I see, whether I want everything during development or only warnings in production.

  • Context: Logs aren’t just messages; they come with timestamps, module names, even line numbers. That means when something breaks, I don’t just see what happened, I know when and where.

  • Persistence: Unlike print(), logs can stick around. They can be written to files, rotated daily, or shipped off to monitoring tools. If my script crashes at 2 AM, I wake up to a breadcrumb trail instead of an empty console.

  • Professionalism: At the end of the day, logging is the standard. Teams, frameworks, and production systems rely on it because it scales. It’s the difference between duct tape debugging and engineering with discipline.

Logging doesn’t just make debugging easier, it gives your code a voice, one that can whisper the little details or sound the alarm when things go wrong.


Getting Started with Logging

The good news? Making the jump from print() to logging is surprisingly easy. You don’t need to overhaul your whole project, you can start small with just a few lines of setup.

Here’s the simplest way to get going:

import logging

logging.basicConfig(level=logging.DEBUG)

logging.debug("This is a debug message")
logging.info("The application is running smoothly")
logging.warning("Something looks off, but still running")
logging.error("Something went wrong")
logging.critical("We’re on fire!")
Enter fullscreen mode Exit fullscreen mode

Run that, and you’ll see output with log levels automatically attached, something like:

DEBUG:root:This is a debug message
INFO:root:The application is running smoothly
WARNING:root:Something looks off, but still running
ERROR:root:Something went wrong
CRITICAL:root:We’re on fire!
Enter fullscreen mode Exit fullscreen mode

Notice how much more context you get compared to plain print() statements.

With print(), all you see is the text. With logging, you instantly know it’s an error, not just another random message. And with one configuration tweak, you could include timestamps, file names, and even line numbers.

It’s a tiny shift in how you write your code, but it pays off the first time you have to trace an issue.


Customizing Logs

The real power comes from customization. By tweaking just a few settings, I could turn plain log messages into something far more useful.

Changing Formats

Want to know not just what happened, but also when and where? That’s as simple as adjusting the format:

import logging

logging.basicConfig(
    level=logging.DEBUG,
    format="%(asctime)s - %(levelname)s - %(filename)s:%(lineno)d - %(message)s"
)

logging.warning("Something might be off here")
Enter fullscreen mode Exit fullscreen mode

Now every log tells me the timestamp, severity, file, and line number:

2025-09-18 08:34:06,157 - WARNING - example03.py:8 - Something might be off here
Enter fullscreen mode Exit fullscreen mode

Suddenly, my logs feel like a black box recorder for my code.

Writing Logs to a File

Printing to the console is fine for quick debugging, but what if your program runs overnight or on a server? You’ll want logs written to a file:

logging.basicConfig(
    filename="app.log",
    level=logging.INFO,
    format="%(asctime)s - %(levelname)s - %(message)s"
)

logging.info("Application started")
Enter fullscreen mode Exit fullscreen mode

Produces an app.log file with:

2025-09-18 08:35:58,314 - INFO - Application started
Enter fullscreen mode Exit fullscreen mode

This way, I can come back later and retrace exactly what happened.

Rotating Logs

Of course, one file can grow out of control if your app runs for weeks or months. That’s where rotating logs come in:

import logging
from logging.handlers import RotatingFileHandler

handler = RotatingFileHandler("app.log", maxBytes=2000, backupCount=5)
logging.basicConfig(
    handlers=[handler],
    level=logging.DEBUG,
    format="%(asctime)s - %(levelname)s - %(message)s"
)

logging.debug("This will be rotated once the file gets too big")
Enter fullscreen mode Exit fullscreen mode

Now, once app.log reaches ~2 KB, it rolls over to a new file, keeping a history without filling my disk.

Different Loggers for Different Modules

In bigger projects, it helps to have separate loggers so each part of the code can “speak for itself.”

import logging

logger = logging.getLogger("data_processor")
logger.setLevel(logging.DEBUG)

# Add a console handler
handler = logging.StreamHandler()
handler.setLevel(logging.DEBUG)
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
handler.setFormatter(formatter)
logger.addHandler(handler)

logger.info("Data pipeline started")
Enter fullscreen mode Exit fullscreen mode

Instead of everything lumping into “root,” I know exactly which module produced which message.

📘 Bonus for you: If you enjoy sharpening your Python skills with practical tips like this, I’ve put together a free e-book: Python One-Liners. It’s packed with clever, bite-sized snippets that will make you a more efficient coder. Grab your free copy here.


Best Practices

By this point, logging had become my go-to tool, but like any tool, it’s easy to misuse. Early on, I made a few mistakes that turned my logs into either a flood of noise or a potential security risk.

Here are some lessons I’ve learned the hard way:

Pick the Right Log Level

Not every message deserves a flashing red siren. Use DEBUG for detailed internal info, INFO for normal operations, WARNING for potential issues, ERROR for problems, and CRITICAL when the system is in serious trouble. Choosing wisely keeps your logs readable and meaningful.

Avoid Overlogging

I once logged every single iteration of a loop that processed thousands of items. The result? My actual error messages were buried under a mountain of noise. Log strategically: enough to trace what’s happening, but not so much that the important stuff gets lost.

Don’t Log Sensitive Data

Passwords, API tokens, personal user info, keep them out of your logs. It’s tempting to dump everything for debugging, but once logs end up in files, servers, or monitoring tools, that sensitive data can become a liability.

Use Structured Logs at Scale

When your application grows beyond a single script, plain text logs can get messy. Structured logs, make it easier to search, filter, and analyze logs with tools like Elasticsearch, Logstash, Kibana or cloud log aggregators. This is where logging evolves from a simple debug aid into a full observability solution.

Logging is most powerful when it’s thoughtful. Done right, it’s like having a flight recorder for your code, capturing just enough information to help you understand what happened, without drowning you in details or leaking secrets.


Conclusion

With logging, my code had structure, memory, and a voice that could tell me not just what went wrong, but when and where.

The shift from print to logging doesn’t have to be overwhelming. You don’t need to master handlers, formatters, and rotation right away. Just start small: replace a few print() calls with logging.info() or logging.error(). Add a timestamp. Save logs to a file. Bit by bit, you’ll build a habit that makes your projects easier to maintain and your debugging sessions far less painful.

And here’s the truth: the first time something breaks in production and your logs calmly explain what happened, you’ll wonder how you ever survived without them.

So go ahead, let print() retire to your quick experiments, and let logging take over when it really matters. Your future self will thank you.


Follow me on Twitter: https://twitter.com/DevAsService

Follow me on Instagram: https://www.instagram.com/devasservice/

Follow me on TikTok: https://www.tiktok.com/@devasservice

Follow me on YouTube: https://www.youtube.com/@DevAsService

Top comments (0)