refactor: adopt Python logging module with named loggers

Replace all print() calls with structured logging using the Python
logging module. This provides:

- Named loggers per subsystem: commander_ai_lab.api, .batch, .sim,
  .coach, .deckgen, .collection, .scanner, .ml, .cache, .pplx
- Dual output: console (StreamHandler) + rotating file (RotatingFileHandler)
  at logs/commander-ai-lab.log (25 MB x 5 backups = 125 MB max)
- Configurable via env vars: CAL_LOG_DIR, CAL_LOG_MAX_MB, CAL_LOG_BACKUP_COUNT
- --verbose / -v flag for DEBUG-level output
- overnight-run.py: separate rotating log at logs/overnight-run.log (10 MB x 5)
- Sub-module loggers in ml/, scanner/, deck_sources/, src/ all propagate
  to the root commander_ai_lab logger
- Stripped redundant [TAG] prefixes from messages (logger name carries context)
- Format: '2026-03-13 14:00:00 INFO  [commander_ai_lab.batch] message'

Files changed: lab_api.py, overnight-run.py, and 11 sub-module .py files
176 total print() calls converted (101 in lab_api.py, 76 in sub-modules)
This commit is contained in:
KoalaTrapLord committed 2026-03-13 18:09:52 +00:00
1 parent 8d2e6199db
commit a1fd290d5f
13 files changed
+390 -257

No files matched your search

+5 -5
View File
@@ -240,12 +240,12 @@ def print_label_distribution(labels: np.ndarray) -> Dict[str, int]:
dist[action.value] = count
total = len(labels)
print(f"\n{'Action':<25} {'Count':>8} {'Pct':>8}")
print("─" * 43)
log.info(f"\n{'Action':<25} {'Count':>8} {'Pct':>8}")
log.info("─" * 43)
for action_name, count in sorted(dist.items(), key=lambda x: -x[1]):
pct = (count / total * 100) if total > 0 else 0
print(f" {action_name:<23} {count:>8} {pct:>7.1f}%")
print(f"{'─' * 43}")
print(f" {'TOTAL':<23} {total:>8}")
log.info(f" {action_name:<23} {count:>8} {pct:>7.1f}%")
log.info(f"{'─' * 43}")
log.info(f" {'TOTAL':<23} {total:>8}")
return dist
+8 -8
View File
@@ -154,17 +154,17 @@ def build_dataset(
# Print game outcome distribution
unique_outcomes, outcome_counts = np.unique(outcomes, return_counts=True)
print(f"\n{'Outcome':<25} {'Count':>8}")
print("─" * 35)
log.info(f"\n{'Outcome':<25} {'Count':>8}")
log.info("─" * 35)
for o, c in sorted(zip(unique_outcomes, outcome_counts), key=lambda x: -x[1]):
print(f" {str(o):<23} {c:>8}")
log.info(f" {str(o):<23} {c:>8}")
# Print playstyle distribution
unique_styles, style_counts = np.unique(playstyles, return_counts=True)
print(f"\n{'Playstyle':<25} {'Count':>8}")
print("─" * 35)
log.info(f"\n{'Playstyle':<25} {'Count':>8}")
log.info("─" * 35)
for s, c in sorted(zip(unique_styles, style_counts), key=lambda x: -x[1]):
print(f" {str(s):<23} {c:>8}")
log.info(f" {str(s):<23} {c:>8}")
return {
"states": states,
@@ -288,7 +288,7 @@ def main():
max_samples=args.max_samples,
)
if not dataset:
print("\nNo data to save. Run simulations with --ml-log first.")
log.info("\nNo data to save. Run simulations with --ml-log first.")
sys.exit(1)
if args.no_split:
@@ -300,7 +300,7 @@ def main():
save_dataset(val, f"{base}-val.npz")
save_dataset(test, f"{base}-test.npz")
print("\nDataset build complete.")
log.info("\nDataset build complete.")
if __name__ == "__main__":
+6 -2
View File
@@ -16,6 +16,10 @@ The network is designed for:
import os
import sys
from pathlib import Path
import logging
log = logging.getLogger("commander_ai_lab.ml.training")
# Conditional torch import — graceful failure if not installed
try:
@@ -25,7 +29,7 @@ try:
TORCH_AVAILABLE = True
except ImportError:
TORCH_AVAILABLE = False
print("[ML] PyTorch not installed. Install with: pip install torch")
log.warning("PyTorch not installed. Install with: pip install torch")
import numpy as np
@@ -204,7 +208,7 @@ def save_checkpoint(
"dropout": TRAINING_CONFIG.dropout,
},
}, path)
print(f"[ML] Checkpoint saved: {path}")
log.info(f"Checkpoint saved: {path}")
def load_checkpoint(path: str, device: str = "cpu"):
+26 -27
View File
@@ -49,8 +49,7 @@ from ml.training.policy_network import (
PolicyNetwork, save_checkpoint, load_checkpoint,
)
logging.basicConfig(level=logging.INFO, format="[%(name)s] %(message)s")
logger = logging.getLogger("ml.trainer")
logger = logging.getLogger("commander_ai_lab.ml.training")
# ══════════════════════════════════════════════════════════
@@ -502,40 +501,40 @@ def evaluate_model(
def print_evaluation(results: Dict):
"""Pretty-print evaluation results."""
print()
print("=" * 65)
print(" Test Set Evaluation")
print("=" * 65)
print(f" Overall Accuracy: {results['overall_accuracy']:.1%}")
print(f" Total Samples: {results['total_samples']:,}")
print()
print(f" {'Action':<20} {'Support':>8} {'Prec':>7} {'Recall':>7} {'F1':>7}")
print(" " + "-" * 55)
logger.info("")
logger.info("=" * 65)
logger.info(" Test Set Evaluation")
logger.info("=" * 65)
logger.info(f" Overall Accuracy: {results['overall_accuracy']:.1%}")
logger.info(f" Total Samples: {results['total_samples']:,}")
logger.info("")
logger.info(f" {'Action':<20} {'Support':>8} {'Prec':>7} {'Recall':>7} {'F1':>7}")
logger.info(" " + "-" * 55)
for action_name, m in results["per_class"].items():
print(
logger.info(
f" {action_name:<20} {m['support']:>8} "
f"{m['precision']:>7.3f} {m['recall']:>7.3f} {m['f1']:>7.3f}"
)
print(" " + "-" * 55)
logger.info(" " + "-" * 55)
# Confusion matrix
print()
print(" Confusion Matrix (rows=true, cols=predicted):")
logger.info("")
logger.info(" Confusion Matrix (rows=true, cols=predicted):")
header = " " + " " * 18
for c in range(NUM_ACTIONS):
short_name = IDX_TO_ACTION[c].value[:6]
header += f" {short_name:>6}"
print(header)
logger.info(header)
for r in range(NUM_ACTIONS):
row_name = IDX_TO_ACTION[r].value[:16]
row_str = f" {row_name:<18}"
for c in range(NUM_ACTIONS):
row_str += f" {results['confusion_matrix'][r][c]:>6}"
print(row_str)
print()
logger.info(row_str)
logger.info("")
# ══════════════════════════════════════════════════════════
@@ -605,8 +604,8 @@ def main():
args = parser.parse_args()
if not TORCH_AVAILABLE:
print("PyTorch not installed. Install with:")
print(" pip install torch torchvision --index-url https://download.pytorch.org/whl/cu121")
logger.info("PyTorch not installed. Install with:")
logger.info(" pip install torch torchvision --index-url https://download.pytorch.org/whl/cu121")
sys.exit(1)
device = args.device or get_best_device()
@@ -621,13 +620,13 @@ def main():
if args.eval_only:
# Evaluation only
if not os.path.exists(test_path):
print(f"Test data not found: {test_path}")
print("Run: python -m ml.scripts.ml_cli build")
logger.info(f"Test data not found: {test_path}")
logger.info("Run: python -m ml.scripts.ml_cli build")
sys.exit(1)
ckpt_path = args.checkpoint or os.path.join(ckpt_dir, "best_policy.pt")
if not os.path.exists(ckpt_path):
print(f"Checkpoint not found: {ckpt_path}")
logger.info(f"Checkpoint not found: {ckpt_path}")
sys.exit(1)
test_states, test_labels = load_npz_split(test_path)
@@ -641,15 +640,15 @@ def main():
eval_path = os.path.join(ckpt_dir, "eval_results.json")
with open(eval_path, "w") as f:
json.dump(results, f, indent=2)
print(f" Results saved: {eval_path}")
logger.info(f" Results saved: {eval_path}")
return
# Training mode
for path, label in [(train_path, "train"), (val_path, "val")]:
if not os.path.exists(path):
print(f"{label} data not found: {path}")
print("Build the dataset first:")
print(" python -m ml.scripts.ml_cli build --results-dir results/")
logger.info(f"{label} data not found: {path}")
logger.info("Build the dataset first:")
logger.info(" python -m ml.scripts.ml_cli build --results-dir results/")
sys.exit(1)
train_states, train_labels = load_npz_split(train_path)