Skip to content

Commit dc96b58

Browse files
Logging: 4-letter level abbreviations with color on console
WARN prints in yellow, ERRO and CRIT in red (abbreviation only, not the full message). File handler uses plain abbreviations without ANSI codes. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent e0213be commit dc96b58

1 file changed

Lines changed: 52 additions & 13 deletions

File tree

src/hevelius-runner.py

Lines changed: 52 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
# 4. Handles status updates
2727

2828
import argparse
29+
import copy
2930
import logging
3031
import sys
3132
import time
@@ -36,7 +37,7 @@
3637

3738
import yaml
3839

39-
from console_color import init_windows_console
40+
from console_color import init_windows_console, color_segment, RED, YELLOW
4041
from config_manager import ConfigManager
4142
from api_client import APIClient, resolve_scope_id_from_identifier
4243
from task_manager import TaskManager
@@ -49,24 +50,62 @@
4950
from cmd_doctor import cmd_doctor
5051
from cmd_projects import cmd_projects
5152

53+
_LEVEL_ABBREV = {
54+
"DEBUG": "DEBG",
55+
"INFO": "INFO",
56+
"WARNING": "WARN",
57+
"ERROR": "ERRO",
58+
"CRITICAL": "CRIT",
59+
}
60+
61+
_LEVEL_COLOR = {
62+
"WARNING": YELLOW,
63+
"ERROR": RED,
64+
"CRITICAL": RED,
65+
}
66+
67+
68+
class _ShortLevelFormatter(logging.Formatter):
69+
"""Formats the log level as a fixed 4-letter abbreviation."""
70+
71+
def format(self, record: logging.LogRecord) -> str:
72+
record = copy.copy(record)
73+
record.levelname = _LEVEL_ABBREV.get(record.levelname, record.levelname[:4].upper())
74+
return super().format(record)
75+
76+
77+
class _ColoredShortLevelFormatter(logging.Formatter):
78+
"""Like _ShortLevelFormatter but colorizes WARN/ERRO/CRIT on color-capable streams."""
79+
80+
def __init__(self, fmt: str, stream) -> None:
81+
super().__init__(fmt)
82+
self._stream = stream
83+
84+
def format(self, record: logging.LogRecord) -> str:
85+
record = copy.copy(record)
86+
color = _LEVEL_COLOR.get(record.levelname)
87+
abbrev = _LEVEL_ABBREV.get(record.levelname, record.levelname[:4].upper())
88+
record.levelname = color_segment(color, abbrev, self._stream) if color else abbrev
89+
return super().format(record)
90+
91+
5292
def setup_logging():
5393
"""Configure logging for the application."""
5494
log_dir = Path("logs")
5595
log_dir.mkdir(exist_ok=True)
5696

57-
# Full logging: format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
58-
logging.basicConfig(
59-
level=logging.INFO,
60-
format='%(levelname)s - %(message)s',
61-
handlers=[
62-
RotatingFileHandler(
63-
log_dir / 'observatory.log',
64-
maxBytes=1024*1024,
65-
backupCount=5
66-
),
67-
logging.StreamHandler(sys.stdout)
68-
]
97+
fmt = "%(levelname)s %(message)s"
98+
stdout_handler = logging.StreamHandler(sys.stdout)
99+
stdout_handler.setFormatter(_ColoredShortLevelFormatter(fmt, sys.stdout))
100+
101+
file_handler = RotatingFileHandler(
102+
log_dir / 'observatory.log',
103+
maxBytes=1024*1024,
104+
backupCount=5,
69105
)
106+
file_handler.setFormatter(_ShortLevelFormatter(fmt))
107+
108+
logging.basicConfig(level=logging.INFO, handlers=[file_handler, stdout_handler])
70109

71110

72111
class ObservatoryAutomation:

0 commit comments

Comments
 (0)