Logging in SimPy: Debugging and Tracing Your Simulations
Print statements are fine for tiny simulations. Real simulations need proper logging.
Why Logging?
Print statements: - Can't be turned off easily - No timestamps - No severity levels - Hard to filter - Mix with output
Logging gives you control.
Basic Python Logging
import logging
import simpy
# Configure logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)
def customer(env, name, server):
logger.info(f"{name} arrives at {env.now}")
with server.request() as req:
yield req
logger.debug(f"{name} starts service at {env.now}")
yield env.timeout(5)
logger.info(f"{name} leaves at {env.now}")
Log Levels
Use them appropriately:
logger.debug("Detailed diagnostic info") # For debugging
logger.info("General operational events") # Normal operation
logger.warning("Something unexpected") # Potential issues
logger.error("Something went wrong") # Errors
logger.critical("System failure") # Critical failures
Simulation-Specific Logger
class SimulationLogger:
def __init__(self, name, level=logging.INFO):
self.logger = logging.getLogger(name)
self.logger.setLevel(level)
# Console handler
ch = logging.StreamHandler()
ch.setLevel(level)
# Format with simulation context
formatter = logging.Formatter(
'%(asctime)s | %(levelname)-8s | %(message)s',
datefmt='%H:%M:%S'
)
ch.setFormatter(formatter)
self.logger.addHandler(ch)
def event(self, env, entity, action, details=""):
"""Log a simulation event."""
self.logger.info(f"t={env.now:8.2f} | {entity:15} | {action:15} | {details}")
def debug(self, message):
self.logger.debug(message)
# Usage
log = SimulationLogger('simulation', level=logging.INFO)
def customer(env, name, server):
log.event(env, name, "ARRIVE")
with server.request() as req:
yield req
log.event(env, name, "START_SERVICE")
yield env.timeout(5)
log.event(env, name, "END_SERVICE")
log.event(env, name, "DEPART")
Logging to File
import logging
def setup_logging(log_file='simulation.log', level=logging.INFO):
"""Configure logging to both console and file."""
logger = logging.getLogger('simulation')
logger.setLevel(level)
# File handler
fh = logging.FileHandler(log_file, mode='w')
fh.setLevel(logging.DEBUG) # Capture everything in file
# Console handler
ch = logging.StreamHandler()
ch.setLevel(level)
# Formatters
detailed_fmt = logging.Formatter(
'%(asctime)s | %(levelname)-8s | %(name)s | %(message)s'
)
simple_fmt = logging.Formatter('%(levelname)s: %(message)s')
fh.setFormatter(detailed_fmt)
ch.setFormatter(simple_fmt)
logger.addHandler(fh)
logger.addHandler(ch)
return logger
logger = setup_logging()
Event Tracing
For detailed analysis:
class EventTracer:
def __init__(self, env):
self.env = env
self.events = []
def trace(self, entity, event_type, **kwargs):
"""Record an event with full context."""
record = {
'time': self.env.now,
'entity': entity,
'event': event_type,
**kwargs
}
self.events.append(record)
def to_dataframe(self):
import pandas as pd
return pd.DataFrame(self.events)
def save(self, filename):
df = self.to_dataframe()
df.to_csv(filename, index=False)
# Usage
tracer = EventTracer(env)
def customer(env, name, server, tracer):
tracer.trace(name, 'ARRIVE', queue_length=len(server.queue))
with server.request() as req:
yield req
tracer.trace(name, 'START_SERVICE', wait_time=env.now - arrival)
yield env.timeout(5)
tracer.trace(name, 'END_SERVICE')
# After simulation
tracer.save('event_log.csv')
Conditional Logging
Only log when interesting:
def customer(env, name, server, logger):
arrival = env.now
with server.request() as req:
yield req
wait = env.now - arrival
# Only log long waits
if wait > 10:
logger.warning(f"{name} waited {wait:.1f} (threshold exceeded)")
yield env.timeout(5)
Logging Resource State
def resource_monitor(env, resource, logger, interval=10):
"""Periodically log resource state."""
while True:
logger.info(
f"Resource state: "
f"in_use={resource.count}/{resource.capacity}, "
f"queue={len(resource.queue)}"
)
yield env.timeout(interval)
env.process(resource_monitor(env, server, logger))
Debug Mode
Toggle detailed logging:
import os
DEBUG = os.environ.get('SIM_DEBUG', 'false').lower() == 'true'
logger = logging.getLogger('simulation')
logger.setLevel(logging.DEBUG if DEBUG else logging.INFO)
# Run with: SIM_DEBUG=true python simulation.py
Structured Logging (JSON)
For machine parsing:
import json
import logging
class JSONFormatter(logging.Formatter):
def format(self, record):
log_record = {
'timestamp': self.formatTime(record),
'level': record.levelname,
'message': record.getMessage(),
}
if hasattr(record, 'sim_time'):
log_record['sim_time'] = record.sim_time
if hasattr(record, 'entity'):
log_record['entity'] = record.entity
return json.dumps(log_record)
# Setup
handler = logging.FileHandler('simulation.jsonl')
handler.setFormatter(JSONFormatter())
logger.addHandler(handler)
# Usage
logger.info("Customer arrived", extra={'sim_time': env.now, 'entity': 'C1'})
Complete Logging Setup
import logging
import sys
def setup_simulation_logging(
name='simulation',
console_level=logging.INFO,
file_level=logging.DEBUG,
log_file=None
):
"""Configure comprehensive logging for simulation."""
logger = logging.getLogger(name)
logger.setLevel(logging.DEBUG)
logger.handlers = [] # Clear existing handlers
# Console handler
console = logging.StreamHandler(sys.stdout)
console.setLevel(console_level)
console_fmt = logging.Formatter('%(levelname)s: %(message)s')
console.setFormatter(console_fmt)
logger.addHandler(console)
# File handler (if specified)
if log_file:
file_handler = logging.FileHandler(log_file, mode='w')
file_handler.setLevel(file_level)
file_fmt = logging.Formatter(
'%(asctime)s | %(levelname)-8s | %(message)s',
datefmt='%Y-%m-%d %H:%M:%S'
)
file_handler.setFormatter(file_fmt)
logger.addHandler(file_handler)
return logger
# Usage
logger = setup_simulation_logging(
console_level=logging.WARNING, # Only warnings to console
file_level=logging.DEBUG, # Everything to file
log_file='simulation_debug.log'
)
Summary
Logging best practices: - Use Python's logging module, not print - Use appropriate log levels - Log to file for detailed analysis - Use structured data for machine parsing - Make logging configurable
Debug efficiently. Log intelligently.
Next Steps
Discover the Power of Simulation
Want to become a go-to expert in simulation with Python? The Complete Simulation Bootcamp will show you how simulation can transform your career and your projects.
Explore the Bootcamp