Source code for secop_ophyd.logs
import logging
from logging.handlers import TimedRotatingFileHandler
from pathlib import Path
from typing import Dict, Optional
# Default configuration
DEFAULT_LOG_LEVEL = logging.INFO
DEFAULT_LOG_FORMAT = "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
DEFAULT_LOG_DIR = ".secop-ophyd"
DEFAULT_LOG_FILENAME = "secop-ophyd.log"
DEFAULT_ROTATION_WHEN = "H" # Rotate every hour
DEFAULT_ROTATION_INTERVAL = 1 # Every 1 hour
DEFAULT_BACKUP_COUNT = 48 # Keep logs for 48 hours
# Create a dictionary of log level names to their values
LOG_LEVELS = {
"DEBUG": logging.DEBUG,
"INFO": logging.INFO,
"WARNING": logging.WARNING,
"ERROR": logging.ERROR,
"CRITICAL": logging.CRITICAL,
}
log_file_handlers: Dict[str, TimedRotatingFileHandler] = {}
console_handler: logging.StreamHandler | None = None
[docs]
def setup_logging(
name: str = "secop_ophyd",
level: int = DEFAULT_LOG_LEVEL,
log_dir: Optional[str] = None,
) -> logging.Logger:
"""
Set up and configure a logger with timed rotating file handler.
Parameters:
----------
name : str
Name of the logger
level : int
Logging level (default: INFO)
log_format : str
Format string for log messages
log_dir : Optional[str]
Directory to store log files (default: .secop-ophyd in current directory)
log_file : Optional[str]
Log file name (default: secop-ophyd.log)
when : str
Type of interval - can be 'S' (seconds), 'M' (minutes), 'H' (hours),
'D' (days), 'W0'-'W6' (weekday, 0=Monday), 'midnight'
interval : int
How many units between rotations (default: 1 hour)
backup_count : int
Number of backup files to keep (default: 48 - for 48 hours of logs)
console : bool
Whether to also log to console (default: False)
Returns:
-------
logging.Logger
Configured logger instance
"""
log_format: str = DEFAULT_LOG_FORMAT
log_file: Optional[str] = None
when: str = DEFAULT_ROTATION_WHEN
interval: int = DEFAULT_ROTATION_INTERVAL
backup_count: int = DEFAULT_BACKUP_COUNT
# Create logger
logger = logging.getLogger(name)
logger.setLevel(level)
# If handlers already exist, don't add more
if logger.handlers:
return logger
# Create formatter
formatter = logging.Formatter(log_format)
# Set up log directory
if log_dir is None:
log_dir = DEFAULT_LOG_DIR
log_path = Path(log_dir)
log_path.mkdir(parents=True, exist_ok=True)
# Set up log file path
if log_file is None:
log_file = DEFAULT_LOG_FILENAME
log_file_path = log_path / log_file
# Use resolved string path as key to ensure consistency
handler_key = str(log_file_path.resolve())
if handler_key not in log_file_handlers:
# Create timed rotating file handler
file_handler = TimedRotatingFileHandler(
log_file_path,
when=when,
interval=interval,
backupCount=backup_count,
encoding="utf-8",
utc=True, # Use UTC time for consistency
)
file_handler.setFormatter(formatter)
log_file_handlers[handler_key] = file_handler
is_new_handler = True
else:
# Use existing handler (don't overwrite formatter)
file_handler = log_file_handlers[handler_key]
is_new_handler = False
# Add console handler if requested
# global console_handler
# if not console_handler:
# console_handler = logging.StreamHandler(sys.stdout)
# console_handler.setFormatter(formatter)
# logger.addHandler(console_handler)
# else:
# logger.addHandler(console_handler)
logger.addHandler(file_handler)
# Log initialization message
if is_new_handler:
logger.info(f"Logger '{name}' initialized with new log file: {log_file_path}")
else:
logger.info(f"Logger '{name}' added to shared log file: {log_file_path}")
return logger
[docs]
def get_logger(name: str) -> logging.Logger:
"""
Get a logger by name. If the logger doesn't have handlers, it will be created
with default settings.
Parameters:
----------
name : str
Name of the logger
Returns:
-------
logging.Logger
Logger instance
"""
logger = logging.getLogger(name)
# If the logger doesn't have handlers, set it up with defaults
if not logger.handlers:
return setup_logging(name)
return logger