| 5 | from typing import Optional |
| 6 | |
| 7 | class Logger: |
| 8 | def __init__(self, log_dir: str, name: str = "neuralforge"): |
| 9 | self.log_dir = log_dir |
| 10 | self.name = name |
| 11 | |
| 12 | os.makedirs(log_dir, exist_ok=True) |
| 13 | |
| 14 | timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") |
| 15 | log_file = os.path.join(log_dir, f"{name}_{timestamp}.log") |
| 16 | |
| 17 | self.logger = logging.getLogger(name) |
| 18 | self.logger.setLevel(logging.INFO) |
| 19 | |
| 20 | if self.logger.hasHandlers(): |
| 21 | self.logger.handlers.clear() |
| 22 | |
| 23 | file_handler = logging.FileHandler(log_file) |
| 24 | file_handler.setLevel(logging.INFO) |
| 25 | |
| 26 | console_handler = logging.StreamHandler(sys.stdout) |
| 27 | console_handler.setLevel(logging.INFO) |
| 28 | |
| 29 | formatter = logging.Formatter( |
| 30 | '%(asctime)s - %(name)s - %(levelname)s - %(message)s', |
| 31 | datefmt='%Y-%m-%d %H:%M:%S' |
| 32 | ) |
| 33 | |
| 34 | file_handler.setFormatter(formatter) |
| 35 | console_handler.setFormatter(formatter) |
| 36 | |
| 37 | self.logger.addHandler(file_handler) |
| 38 | self.logger.addHandler(console_handler) |
| 39 | |
| 40 | self.info(f"Logger initialized. Logging to: {log_file}") |
| 41 | |
| 42 | def info(self, message: str): |
| 43 | self.logger.info(message) |
| 44 | |
| 45 | def warning(self, message: str): |
| 46 | self.logger.warning(message) |
| 47 | |
| 48 | def error(self, message: str): |
| 49 | self.logger.error(message) |
| 50 | |
| 51 | def debug(self, message: str): |
| 52 | self.logger.debug(message) |
| 53 | |
| 54 | def log_metrics(self, metrics: dict, step: Optional[int] = None): |
| 55 | if step is not None: |
| 56 | message = f"Step {step}: " |
| 57 | else: |
| 58 | message = "Metrics: " |
| 59 | |
| 60 | metric_strs = [f"{k}={v:.4f}" if isinstance(v, float) else f"{k}={v}" |
| 61 | for k, v in metrics.items()] |
| 62 | message += ", ".join(metric_strs) |
| 63 | |
| 64 | self.info(message) |