Advanced Python Automation for Network Engineers
Logging Instead of Print: Structured, Secret-Safe Output
What you'll learn
- Why print() stops being sufficient once a tool has multiple modules
- The four logging levels this module actually uses and when each applies
- How to configure logging once and get context automatically in every message
- Why a token or password must never appear in a log line
Listen to a guided explanation of this lesson.
Spoken narration isn't supported in this browser.
print() doesn't scale past one file
Every capstone's print() calls worked because there was one file, run once, watched by one person. Once code is split across modules (Lesson 2) and possibly run concurrently (Lesson 7), print() has no levels, no timestamps, and no way to tell which module a message came from.
import logging
def configure_logging(level=logging.INFO):
logging.basicConfig(
level=level,
format="%(asctime)s %(levelname)s %(name)s: %(message)s",
)
def get_logger(name):
return logging.getLogger(name)configure_logging() is called once, at startup. get_logger(__name__) gives each module its own named logger -- %(name)s in the format string is what makes the source of each message visible.
Real output, from real code
from network_automation.logging_config import get_logger
logger = get_logger(__name__)
def check_device(connection, device):
hostname = device.hostname
try:
actual_vlans = get_state_with_retry(connection, hostname)
except DeviceUnreachableError as e:
logger.warning(f"{hostname}: unreachable ({e})")
return AutomationResult(hostname=hostname, status=Status.UNREACHABLE, detail=str(e))
# ...
logger.info(f"{hostname}: already compliant")
return AutomationResult(hostname=hostname, status=Status.ALREADY_COMPLIANT, detail="no change needed")logger.info() for the normal case, logger.warning() for a device-level failure that isn't a bug — the same expected-failure-vs-real-bug distinction from Lesson 3, now reflected in which level gets used.
2026-08-26 13:00:20,139 INFO network_automation.automation: SW1: already compliant
2026-08-26 13:00:20,447 INFO network_automation.automation: SW2: missing VLANs [10]
2026-08-26 13:00:21,573 INFO network_automation.automation: SW3: missing VLANs [30]
2026-08-26 13:00:28,612 WARNING network_automation.automation: SW4: unreachable (SW4 timed out)
2026-08-26 13:00:28,919 WARNING network_automation.automation: SW5: authentication failed (SW5 rejected the credentials)Real, verified output. Every line carries a timestamp, a level, and network_automation.automation as the source — %(name)s reflecting get_logger(__name__) exactly.
What must never appear in a log line
A token in a log file is a leaked credential
It's tempting to log a request for debugging, headers and all. Don't. A log file is often kept longer, read by more people, and stored less carefully than the code itself — logging an Authorization header is functionally the same mistake as hardcoding the credential in source, the rule every module since Netmiko Fundamentals has held. This module's own logger.info()/logger.warning() calls above log hostnames and statuses only — never a token, never a password.
Key takeaways
- print() lacks levels, timestamps, and a source module — all real problems once code is split across files (Lesson 2) or run concurrently (Lesson 7).
- configure_logging() runs once at startup; get_logger(__name__) gives each module its own named logger.
- logger.info() for the normal/expected case, logger.warning() for an operational failure — verified directly against all 5 devices' real output.
- A token or password must never appear in a log line — the same no-hardcoded-credentials rule since Netmiko Fundamentals, applied to logging output specifically.