From 15e4e9e12dcfc471df485b26f18067f2f4ba7e82 Mon Sep 17 00:00:00 2001 From: Hrithik-ui753 Date: Tue, 26 May 2026 16:47:55 +0530 Subject: [PATCH] feat(core): implement centralized structured json log manager module (#232) --- core/__pycache__/__init__.cpython-314.pyc | Bin 0 -> 131 bytes core/__pycache__/config.cpython-314.pyc | Bin 0 -> 8088 bytes core/logger.py | 76 +++++++++++++++------- 3 files changed, 52 insertions(+), 24 deletions(-) create mode 100644 core/__pycache__/__init__.cpython-314.pyc create mode 100644 core/__pycache__/config.cpython-314.pyc diff --git a/core/__pycache__/__init__.cpython-314.pyc b/core/__pycache__/__init__.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9469fe0b8821886a48b2cff1e080eff0019eb44b GIT binary patch literal 131 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 gSpFMUWEq$^;vBIz{K>?RD(JHNf7}^H761SM literal 0 HcmV?d00001 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