diff --git a/core/__pycache__/__init__.cpython-314.pyc b/core/__pycache__/__init__.cpython-314.pyc new file mode 100644 index 0000000..9469fe0 Binary files /dev/null and b/core/__pycache__/__init__.cpython-314.pyc differ diff --git a/core/__pycache__/config.cpython-314.pyc b/core/__pycache__/config.cpython-314.pyc new file mode 100644 index 0000000..bccd736 Binary files /dev/null and b/core/__pycache__/config.cpython-314.pyc differ diff --git a/core/logger.py b/core/logger.py index 5f8999f..c6d2fba 100644 --- a/core/logger.py +++ b/core/logger.py @@ -1,65 +1,93 @@ import json import logging +import sys import traceback +from datetime import datetime from logging.handlers import RotatingFileHandler from typing import Optional class JSONFormatter(logging.Formatter): - """Custom formatter that outputs log records as JSON strings.""" + """ + Custom logging formatter that outputs log records as structured JSON strings. + Extracts dynamic context fields seamlessly. + """ def format(self, record: logging.LogRecord) -> str: + # Standardize timestamp to ISO-8601 format log_data = { - "timestamp": self.formatTime(record, self.datefmt), + "timestamp": datetime.fromtimestamp(record.created).isoformat(), "level": record.levelname, - "name": record.name, + "module": record.name, "message": record.getMessage(), } + + # Dynamically append any extra payload context keys passed by the developer + # Filtering out python's default internal LogRecord attributes + DEFAULT_ATTRS = { + "name", "msg", "args", "levelname", "levelno", "pathname", "filename", + "module", "exc_info", "exc_text", "stack_info", "lineno", "funcName", + "created", "msecs", "relativeCreated", "thread", "threadName", + "processName", "process" + } + + for key, value in record.__dict__.items(): + if key not in DEFAULT_ATTRS: + log_data[key] = value + + # Format exception tracebacks elegantly if an error runtime event occurs if record.exc_info: log_data["exception"] = "".join(traceback.format_exception(*record.exc_info)) + return json.dumps(log_data) -def setup( +def get_logger( + name: str = "execra", log_level: str = "INFO", log_file: Optional[str] = None, max_bytes: int = 10 * 1024 * 1024, backup_count: int = 5, - json_format: bool = False, -) -> None: - """Configure root logging for the application. - - Supports rotating file logs and structured JSON logging. + json_format: bool = True, # Defaulting to True to meet Feature Request #232 spec +) -> logging.Logger: + """ + Configures and returns a thread-safe, module-isolated logger instance. + Prevents duplicate handler bubbling and standardizes output tracking format. """ level = getattr(logging, log_level.upper(), logging.INFO) - root = logging.getLogger() + logger = logging.getLogger(name) + + # Prevent re-adding handlers if logger is already configured + if logger.hasHandlers(): + return logger - # Clear existing handlers to prevent duplicates during reconfiguration - for h in list(root.handlers): - root.removeHandler(h) + logger.setLevel(level) + logger.propagate = False # Isolate stream formats cleanly - # Configure formatter + # Resolve Formatter Selection if json_format: formatter = JSONFormatter() else: - formatter = logging.Formatter("[%(asctime)s] %(levelname)s %(name)s: %(message)s") + formatter = logging.Formatter( + "[%(asctime)s] %(levelname)s %(name)s: %(message)s", + datefmt="%Y-%m-%dT%H:%M:%S" + ) - # Add Stream Handler - stream_handler = logging.StreamHandler() + # 1. Standard System Output (Console Stream) + stream_handler = logging.StreamHandler(sys.stdout) stream_handler.setFormatter(formatter) - root.addHandler(stream_handler) + logger.addHandler(stream_handler) - # Add Rotating File Handler + # 2. Add File Persistence (Rotating File Engine) if log_file: file_handler = RotatingFileHandler( log_file, maxBytes=max_bytes, backupCount=backup_count, encoding="utf-8" ) file_handler.setFormatter(formatter) - root.addHandler(file_handler) - - root.setLevel(level) + logger.addHandler(file_handler) + return logger -setup() -logger = logging.getLogger("execra") \ No newline at end of file +# Default initialization hook matching execution environment requirements +logger = get_logger("execra") \ No newline at end of file