Logging Module Integration vs Print Statements
🏷️ Error Handling and Exceptions / Error Handling Best Practices
📋 Context Introduction
When building scripts and tools, engineers often start with print statements to see what's happening. While this works for small scripts, it quickly becomes problematic in larger systems. The logging module offers a structured, professional approach to tracking events, errors, and debugging information. This guide explains why logging is superior and how to transition from print statements to proper logging.
🎯 Why Print Statements Fall Short
Print statements are simple but have significant limitations in production environments:
- No control over output levels – You cannot easily suppress debug messages or highlight errors
- No timestamps – You lose critical context about when events occurred
- No destination flexibility – Output always goes to the console, not to files or external systems
- Difficult to disable – Removing or commenting out print statements is tedious and error-prone
- No structured formatting – Messages are plain text without consistent patterns
⚙️ The Logging Module Advantage
The logging module solves all these problems with built-in capabilities:
- Multiple severity levels – DEBUG, INFO, WARNING, ERROR, CRITICAL
- Timestamps automatically – Every log entry records when it happened
- Flexible output destinations – Console, files, network sockets, or all at once
- Runtime configuration – Change logging behavior without modifying code
- Structured formatting – Consistent message patterns across your entire application
📊 Comparison Table: Print vs Logging
| Feature | Print Statements | Logging Module |
|---|---|---|
| Message levels | None (all messages equal) | DEBUG, INFO, WARNING, ERROR, CRITICAL |
| Timestamps | Manual (you write it) | Automatic (built-in) |
| Output control | Always visible | Configurable per level |
| File output | Requires file handling code | Built-in file handlers |
| Performance impact | Always runs | Can be disabled at runtime |
| Production readiness | Low | High |
🛠️ Basic Logging Setup
To start using logging, you need a simple configuration:
- Import the logging module with import logging
- Configure the basic settings using logging.basicConfig()
- Set the desired log level (typically INFO or DEBUG for development)
- Define a format string that includes timestamp, level, and message
A minimal setup looks like this:
- logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
- Then use logging.info("Your message here") instead of print("Your message here")
🕵️ Logging Levels Explained
The logging module provides five standard levels, each serving a specific purpose:
- DEBUG – Detailed information for diagnosing problems (use during development only)
- INFO – Confirmation that things are working as expected
- WARNING – Something unexpected happened, but the application still works
- ERROR – The application couldn't perform a function due to a more serious issue
- CRITICAL – A serious error that may prevent the application from continuing
To use a specific level, call the corresponding function:
- logging.debug("Variable value is: 42") – Only visible when level is set to DEBUG
- logging.info("Server started successfully") – Visible at INFO level and above
- logging.warning("Disk space is low") – Visible at WARNING level and above
- logging.error("Failed to connect to database") – Visible at ERROR level and above
- logging.critical("System is shutting down") – Always visible regardless of level
📁 Writing Logs to a File
One of the most powerful features is directing logs to a file for later review:
- Add the filename parameter to basicConfig: logging.basicConfig(filename='app.log', level=logging.INFO)
- All log messages will now be written to app.log instead of the console
- You can still see messages in the console by adding a StreamHandler alongside the file handler
For more advanced file management:
- Use RotatingFileHandler to automatically create new log files when the current one reaches a certain size
- Use TimedRotatingFileHandler to create new log files daily, hourly, or at custom intervals
🎨 Formatting Log Messages
Customize how log messages appear with format strings:
- %(asctime)s – Adds timestamp in ISO 8601 format
- %(levelname)s – Shows the severity level (INFO, ERROR, etc.)
- %(message)s – The actual log message you provide
- %(name)s – Shows the logger name (useful for large applications)
- %(lineno)d – Shows the line number where the log call was made
Example format: '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
This produces output like: 2024-01-15 14:30:22 - root - INFO - Server started
🔄 Transitioning from Print to Logging
Here is a practical approach to convert existing code:
- Replace print("Starting process...") with logging.info("Starting process...")
- Replace print(f"Error: {error_message}") with logging.error(f"Error: {error_message}")
- Replace print(f"Debug: {variable}") with logging.debug(f"Debug: {variable}")
- Keep print statements only for user-facing interactive prompts that require immediate response
A common pattern for scripts:
- Set logging level to DEBUG during development for maximum visibility
- Change to INFO or WARNING when deploying to production
- Use ERROR and CRITICAL for conditions that require immediate attention
⚠️ Common Pitfalls to Avoid
When adopting logging, watch out for these mistakes:
- Forgetting to configure logging – Without basicConfig, logging messages may not appear
- Using print inside exception handlers – Always use logging.exception() or logging.error() instead
- Logging sensitive information – Avoid logging passwords, API keys, or personal data
- Over-logging at DEBUG level – Too much debug output can hide important messages
- Not using exception logging – Use logging.exception("Error occurred") inside except blocks to capture the full traceback
✅ Best Practices Summary
Follow these guidelines for effective logging:
- Set appropriate log levels – Use DEBUG for development, INFO for normal operations, ERROR for failures
- Include context in messages – Add relevant variable values or identifiers to help debugging
- Use structured logging – Consistent format across all modules makes log analysis easier
- Configure logging at application start – Set up once at the entry point of your script
- Rotate log files – Prevent single log files from growing too large
- Monitor logs regularly – Set up alerts for ERROR and CRITICAL messages
🚀 Next Steps
To deepen your understanding of logging:
- Explore logger objects instead of the root logger for better control in larger applications
- Learn about handlers (FileHandler, StreamHandler, SMTPHandler) for different output destinations
- Investigate log filters to selectively include or exclude messages
- Practice setting up logging in a multi-module project to see how loggers propagate
Remember: Good logging is an investment that pays off every time something goes wrong in production.
The logging module provides a structured way to record program events with severity levels, timestamps, and output control, while print statements simply display text to the console.
📝 Example 1: Basic Print Statement
This shows how a print statement outputs a simple message to the console.
print("Engine starting sequence initiated")
📤 Output: Engine starting sequence initiated
📝 Example 2: Basic Logging Module Setup
This shows how to configure the logging module and write a simple informational message.
import logging
logging.basicConfig(level=logging.INFO)
logging.info("Engine starting sequence initiated")
📤 Output: INFO:root:Engine starting sequence initiated
📝 Example 3: Logging with Different Severity Levels
This shows how the logging module categorizes messages by severity (debug, info, warning, error, critical).
import logging
logging.basicConfig(level=logging.DEBUG)
logging.debug("Checking fuel pressure")
logging.info("Fuel pressure normal")
logging.warning("Fuel level below 25%")
logging.error("Engine temperature critical")
logging.critical("Engine shutdown required")
📤 Output: DEBUG:root:Checking fuel pressure
INFO:root:Fuel pressure normal
WARNING:root:Fuel level below 25%
ERROR:root:Engine temperature critical
CRITICAL:root:Engine shutdown required
📝 Example 4: Logging with Timestamps
This shows how to add timestamps to log entries for tracking when events occurred.
import logging
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s - %(levelname)s - %(message)s"
)
logging.info("Sensor calibration started")
logging.warning("Sensor 3 reading out of range")
📤 Output: 2024-01-15 14:30:22,456 - INFO - Sensor calibration started
2024-01-15 14:30:22,457 - WARNING - Sensor 3 reading out of range
📝 Example 5: Logging to a File Instead of Console
This shows how to redirect log messages to a file for later review by engineers.
import logging
logging.basicConfig(
level=logging.INFO,
filename="engine_log.txt",
filemode="w",
format="%(asctime)s - %(levelname)s - %(message)s"
)
logging.info("Engine startup sequence complete")
logging.warning("Coolant temperature rising slowly")
logging.error("Coolant pump failure detected")
📤 Output: (No console output — all messages written to engine_log.txt)
📝 Example 6: Using Print vs Logging in a Loop
This shows how print statements clutter output while logging can filter by severity level.
import logging
logging.basicConfig(level=logging.WARNING)
sensor_readings = [95, 102, 98, 110, 97]
for reading in sensor_readings:
print(f"Sensor reading: {reading}")
logging.debug(f"Sensor reading: {reading}")
if reading > 100:
logging.warning(f"Sensor reading exceeds threshold: {reading}")
📤 Output: Sensor reading: 95
Sensor reading: 102
Sensor reading: 98
Sensor reading: 110
Sensor reading: 97
WARNING:root:Sensor reading exceeds threshold: 102
WARNING:root:Sensor reading exceeds threshold: 110
📝 Example 7: Logging with Exception Details
This shows how the logging module captures full error tracebacks for debugging by engineers.
import logging
logging.basicConfig(level=logging.ERROR)
try:
result = 10 / 0
except ZeroDivisionError as error:
logging.error("Division failed", exc_info=True)
📤 Output: ERROR:root:Division failed
Traceback (most recent call last):
File "
ZeroDivisionError: division by zero
Comparison Table: Print Statements vs Logging Module
| Feature | Print Statement | Logging Module |
|---|---|---|
| Severity levels | No | Yes (DEBUG, INFO, WARNING, ERROR, CRITICAL) |
| Timestamps | No | Yes (configurable format) |
| Output to file | No (console only) | Yes (file, console, network) |
| Filter by severity | No | Yes (set level to show only warnings+) |
| Exception traceback | Manual (try/except) | Automatic (exc_info=True) |
| Production use | Not recommended | Recommended for engineers |
📋 Context Introduction
When building scripts and tools, engineers often start with print statements to see what's happening. While this works for small scripts, it quickly becomes problematic in larger systems. The logging module offers a structured, professional approach to tracking events, errors, and debugging information. This guide explains why logging is superior and how to transition from print statements to proper logging.
🎯 Why Print Statements Fall Short
Print statements are simple but have significant limitations in production environments:
- No control over output levels – You cannot easily suppress debug messages or highlight errors
- No timestamps – You lose critical context about when events occurred
- No destination flexibility – Output always goes to the console, not to files or external systems
- Difficult to disable – Removing or commenting out print statements is tedious and error-prone
- No structured formatting – Messages are plain text without consistent patterns
⚙️ The Logging Module Advantage
The logging module solves all these problems with built-in capabilities:
- Multiple severity levels – DEBUG, INFO, WARNING, ERROR, CRITICAL
- Timestamps automatically – Every log entry records when it happened
- Flexible output destinations – Console, files, network sockets, or all at once
- Runtime configuration – Change logging behavior without modifying code
- Structured formatting – Consistent message patterns across your entire application
📊 Comparison Table: Print vs Logging
| Feature | Print Statements | Logging Module |
|---|---|---|
| Message levels | None (all messages equal) | DEBUG, INFO, WARNING, ERROR, CRITICAL |
| Timestamps | Manual (you write it) | Automatic (built-in) |
| Output control | Always visible | Configurable per level |
| File output | Requires file handling code | Built-in file handlers |
| Performance impact | Always runs | Can be disabled at runtime |
| Production readiness | Low | High |
🛠️ Basic Logging Setup
To start using logging, you need a simple configuration:
- Import the logging module with import logging
- Configure the basic settings using logging.basicConfig()
- Set the desired log level (typically INFO or DEBUG for development)
- Define a format string that includes timestamp, level, and message
A minimal setup looks like this:
- logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
- Then use logging.info("Your message here") instead of print("Your message here")
🕵️ Logging Levels Explained
The logging module provides five standard levels, each serving a specific purpose:
- DEBUG – Detailed information for diagnosing problems (use during development only)
- INFO – Confirmation that things are working as expected
- WARNING – Something unexpected happened, but the application still works
- ERROR – The application couldn't perform a function due to a more serious issue
- CRITICAL – A serious error that may prevent the application from continuing
To use a specific level, call the corresponding function:
- logging.debug("Variable value is: 42") – Only visible when level is set to DEBUG
- logging.info("Server started successfully") – Visible at INFO level and above
- logging.warning("Disk space is low") – Visible at WARNING level and above
- logging.error("Failed to connect to database") – Visible at ERROR level and above
- logging.critical("System is shutting down") – Always visible regardless of level
📁 Writing Logs to a File
One of the most powerful features is directing logs to a file for later review:
- Add the filename parameter to basicConfig: logging.basicConfig(filename='app.log', level=logging.INFO)
- All log messages will now be written to app.log instead of the console
- You can still see messages in the console by adding a StreamHandler alongside the file handler
For more advanced file management:
- Use RotatingFileHandler to automatically create new log files when the current one reaches a certain size
- Use TimedRotatingFileHandler to create new log files daily, hourly, or at custom intervals
🎨 Formatting Log Messages
Customize how log messages appear with format strings:
- %(asctime)s – Adds timestamp in ISO 8601 format
- %(levelname)s – Shows the severity level (INFO, ERROR, etc.)
- %(message)s – The actual log message you provide
- %(name)s – Shows the logger name (useful for large applications)
- %(lineno)d – Shows the line number where the log call was made
Example format: '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
This produces output like: 2024-01-15 14:30:22 - root - INFO - Server started
🔄 Transitioning from Print to Logging
Here is a practical approach to convert existing code:
- Replace print("Starting process...") with logging.info("Starting process...")
- Replace print(f"Error: {error_message}") with logging.error(f"Error: {error_message}")
- Replace print(f"Debug: {variable}") with logging.debug(f"Debug: {variable}")
- Keep print statements only for user-facing interactive prompts that require immediate response
A common pattern for scripts:
- Set logging level to DEBUG during development for maximum visibility
- Change to INFO or WARNING when deploying to production
- Use ERROR and CRITICAL for conditions that require immediate attention
⚠️ Common Pitfalls to Avoid
When adopting logging, watch out for these mistakes:
- Forgetting to configure logging – Without basicConfig, logging messages may not appear
- Using print inside exception handlers – Always use logging.exception() or logging.error() instead
- Logging sensitive information – Avoid logging passwords, API keys, or personal data
- Over-logging at DEBUG level – Too much debug output can hide important messages
- Not using exception logging – Use logging.exception("Error occurred") inside except blocks to capture the full traceback
✅ Best Practices Summary
Follow these guidelines for effective logging:
- Set appropriate log levels – Use DEBUG for development, INFO for normal operations, ERROR for failures
- Include context in messages – Add relevant variable values or identifiers to help debugging
- Use structured logging – Consistent format across all modules makes log analysis easier
- Configure logging at application start – Set up once at the entry point of your script
- Rotate log files – Prevent single log files from growing too large
- Monitor logs regularly – Set up alerts for ERROR and CRITICAL messages
🚀 Next Steps
To deepen your understanding of logging:
- Explore logger objects instead of the root logger for better control in larger applications
- Learn about handlers (FileHandler, StreamHandler, SMTPHandler) for different output destinations
- Investigate log filters to selectively include or exclude messages
- Practice setting up logging in a multi-module project to see how loggers propagate
Remember: Good logging is an investment that pays off every time something goes wrong in production.
Interactive Views
You are currently in 📚 All-in-One mode. Use the tabs at the top to switch to 📖 Theory Only or 💻 Code Only views.
The logging module provides a structured way to record program events with severity levels, timestamps, and output control, while print statements simply display text to the console.
📝 Example 1: Basic Print Statement
This shows how a print statement outputs a simple message to the console.
print("Engine starting sequence initiated")
📤 Output: Engine starting sequence initiated
📝 Example 2: Basic Logging Module Setup
This shows how to configure the logging module and write a simple informational message.
import logging
logging.basicConfig(level=logging.INFO)
logging.info("Engine starting sequence initiated")
📤 Output: INFO:root:Engine starting sequence initiated
📝 Example 3: Logging with Different Severity Levels
This shows how the logging module categorizes messages by severity (debug, info, warning, error, critical).
import logging
logging.basicConfig(level=logging.DEBUG)
logging.debug("Checking fuel pressure")
logging.info("Fuel pressure normal")
logging.warning("Fuel level below 25%")
logging.error("Engine temperature critical")
logging.critical("Engine shutdown required")
📤 Output: DEBUG:root:Checking fuel pressure
INFO:root:Fuel pressure normal
WARNING:root:Fuel level below 25%
ERROR:root:Engine temperature critical
CRITICAL:root:Engine shutdown required
📝 Example 4: Logging with Timestamps
This shows how to add timestamps to log entries for tracking when events occurred.
import logging
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s - %(levelname)s - %(message)s"
)
logging.info("Sensor calibration started")
logging.warning("Sensor 3 reading out of range")
📤 Output: 2024-01-15 14:30:22,456 - INFO - Sensor calibration started
2024-01-15 14:30:22,457 - WARNING - Sensor 3 reading out of range
📝 Example 5: Logging to a File Instead of Console
This shows how to redirect log messages to a file for later review by engineers.
import logging
logging.basicConfig(
level=logging.INFO,
filename="engine_log.txt",
filemode="w",
format="%(asctime)s - %(levelname)s - %(message)s"
)
logging.info("Engine startup sequence complete")
logging.warning("Coolant temperature rising slowly")
logging.error("Coolant pump failure detected")
📤 Output: (No console output — all messages written to engine_log.txt)
📝 Example 6: Using Print vs Logging in a Loop
This shows how print statements clutter output while logging can filter by severity level.
import logging
logging.basicConfig(level=logging.WARNING)
sensor_readings = [95, 102, 98, 110, 97]
for reading in sensor_readings:
print(f"Sensor reading: {reading}")
logging.debug(f"Sensor reading: {reading}")
if reading > 100:
logging.warning(f"Sensor reading exceeds threshold: {reading}")
📤 Output: Sensor reading: 95
Sensor reading: 102
Sensor reading: 98
Sensor reading: 110
Sensor reading: 97
WARNING:root:Sensor reading exceeds threshold: 102
WARNING:root:Sensor reading exceeds threshold: 110
📝 Example 7: Logging with Exception Details
This shows how the logging module captures full error tracebacks for debugging by engineers.
import logging
logging.basicConfig(level=logging.ERROR)
try:
result = 10 / 0
except ZeroDivisionError as error:
logging.error("Division failed", exc_info=True)
📤 Output: ERROR:root:Division failed
Traceback (most recent call last):
File "
ZeroDivisionError: division by zero
Comparison Table: Print Statements vs Logging Module
| Feature | Print Statement | Logging Module |
|---|---|---|
| Severity levels | No | Yes (DEBUG, INFO, WARNING, ERROR, CRITICAL) |
| Timestamps | No | Yes (configurable format) |
| Output to file | No (console only) | Yes (file, console, network) |
| Filter by severity | No | Yes (set level to show only warnings+) |
| Exception traceback | Manual (try/except) | Automatic (exc_info=True) |
| Production use | Not recommended | Recommended for engineers |