⚡ IT Wisdom We don't have a monitoring problem. We have a "we ignored all 4,000 alerts" problem.
Skilled Practitioner
4249 XP 1751 to Expert
Units
Whetting Your Appetite — Why Python for Networking? Using the Python Interpreter An Informal Introduction to Python — Network Basics Control Flow — Making Decisions in Network Scripts Data Structures — Modeling Network Inventories Modules — Organizing Your Network Scripts Input and Output — Reading and Writing Network Data Errors and Exceptions — Building Reliable Network Scripts Classes — Modeling Network Devices as Objects Standard Library Part I — Tools Every Network Script Needs Standard Library Part II — Logging, Formatting, and Counting Virtual Environments and Packages — Isolating Your Dependencies Capstone — Build a Network Inventory Tool

logging — Replace print() in Real Scripts

print() is fine for learning. Production scripts use logging — it adds timestamps, severity levels, and lets you control what gets written to the screen versus a file without changing the code.

import logging

logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s %(levelname)s %(message)s",
    datefmt="%Y-%m-%d %H:%M:%S",
)

logging.debug("Connecting to sw-core-01")     # hidden at INFO level
logging.info("Processing 5 devices")
logging.warning("sw-access-02 is unreachable")
logging.error("Failed to write report.txt")

Output:

2025-04-15 09:30:00 INFO Processing 5 devices
2025-04-15 09:30:00 WARNING sw-access-02 is unreachable
2025-04-15 09:30:00 ERROR Failed to write report.txt

Level order (lowest to highest): DEBUG → INFO → WARNING → ERROR → CRITICAL. Setting level=logging.INFO suppresses DEBUG and shows everything above it.

Write to a file instead of the screen:

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

pprint — Readable Output for Nested Data

When you print a large dictionary or list of dicts, print() runs it together. pprint formats it with indentation so it is readable:

from pprint import pprint

inventory = [
    {"name": "sw-core-01", "ip": "10.1.1.1", "site": "HQ", "vlan": 10},
    {"name": "rtr-edge-01", "ip": "10.1.1.254", "site": "HQ", "vlan": 1},
]

pprint(inventory)

Output:

[{'ip': '10.1.1.1', 'name': 'sw-core-01', 'site': 'HQ', 'vlan': 10},
 {'ip': '10.1.1.254', 'name': 'rtr-edge-01', 'site': 'HQ', 'vlan': 1}]

pprint(data, width=40) forces narrower wrapping. pprint(data, depth=1) hides nested levels.


string.Template — Safe Variable Substitution

string.Template fills placeholders using $variable syntax. Unlike f-strings, it works with data you do not know at write time — useful for config templates:

from string import Template

cfg_template = Template("""
interface $interface
 description $description
 switchport access vlan $vlan
""")

output = cfg_template.substitute(
    interface="GigabitEthernet0/1",
    description="Uplink to core",
    vlan=10,
)
print(output)

substitute() raises KeyError if a placeholder is missing. safe_substitute() leaves missing placeholders as-is instead of crashing.


collections.Counter — Count Device Types in One Line

Counter takes any iterable and counts how many times each value appears:

from collections import Counter

device_types = ["switch", "router", "switch", "firewall", "switch", "router"]
counts = Counter(device_types)

print(counts)
# Counter({'switch': 3, 'router': 2, 'firewall': 1})

print(counts.most_common(2))
# [('switch', 3), ('router', 2)]

Use it on a list of values extracted from your inventory:

types = [d["type"] for d in inventory]
counts = Counter(types)

Summary

  • logging replaces print() in production scripts — adds timestamps, levels, and log file support
  • Level order: DEBUG < INFO < WARNING < ERROR < CRITICAL
  • pprint formats nested data structures for readable output
  • string.Template fills $placeholder values — safe_substitute() is forgiving on missing keys
  • Counter counts occurrences in an iterable — .most_common(n) returns the top n