55"""Logging config."""
66
77import logging
8+ import sys
89from pathlib import Path
910from typing import cast
1011
11- from logzero import DEFAULT_FORMAT , setup_logger
12-
1312__all__ = (
1413 "configure_logging" ,
1514 "LOG_LEVELS" ,
1615)
1716
1817
1918LOG_LEVELS = ["DEBUG" , "VERBOSE" , "INFO" , "WARNING" , "ERROR" , "CRITICAL" ]
19+ DEFAULT_FORMAT = (
20+ "%(color)s[%(levelname)1.1s %(asctime)s %(module)s:%(lineno)d]%(end_color)s %(message)s"
21+ )
22+ _PLAIN_FORMAT = "[%(levelname)1.1s %(asctime)s %(module)s:%(lineno)d] %(message)s"
23+ _DATE_FORMAT = "%y%m%d %H:%M:%S"
24+
25+ _VERBOSE_LOG_LEVEL = 15
26+
27+ # ANSI color codes keyed by level
28+ _LEVEL_COLORS = {
29+ logging .DEBUG : "\033 [36m" , # Cyan
30+ _VERBOSE_LOG_LEVEL : "\033 [34m" , # Blue (VERBOSE)
31+ logging .INFO : "\033 [32m" , # Green
32+ logging .WARNING : "\033 [33m" , # Yellow
33+ logging .ERROR : "\033 [31m" , # Red
34+ logging .CRITICAL : "\033 [31;1m" , # Bold red
35+ }
36+ _RESET = "\033 [0m"
37+
38+
39+ class _ColorFormatter (logging .Formatter ):
40+ """Formatter that injects %(color)s / %(end_color)s into records."""
41+
42+ def format (self , record : logging .LogRecord ) -> str :
43+ record .color = _LEVEL_COLORS .get (record .levelno , "" )
44+ record .end_color = _RESET if record .color else ""
45+ return super ().format (record )
2046
2147
2248class DVSimLogger (logging .getLoggerClass ()):
@@ -27,7 +53,7 @@ class DVSimLogger(logging.getLoggerClass()):
2753 ERROR = logging .ERROR
2854 WARNING = logging .WARNING
2955 INFO = logging .INFO
30- VERBOSE = 15
56+ VERBOSE = _VERBOSE_LOG_LEVEL
3157 DEBUG = logging .DEBUG
3258
3359 def __init__ (self , name : str ) -> None :
@@ -49,20 +75,36 @@ def set_logfile(
4975 ) -> None :
5076 """Set a logfile to save the logs to."""
5177 fh = logging .FileHandler (filename = path , mode = mode )
52-
53- fh .setLevel (level or self .DEBUG )
54- fh .setFormatter (
55- logging .Formatter (DEFAULT_FORMAT ),
56- )
57-
78+ fh .setLevel (level if level is not None else self .DEBUG )
79+ fh .setFormatter (logging .Formatter (_PLAIN_FORMAT , datefmt = _DATE_FORMAT ))
5880 self .addHandler (fh )
5981
6082
6183def _build_logger () -> DVSimLogger :
6284 """Build a DVSim logger."""
6385 logging .setLoggerClass (DVSimLogger )
6486
65- return cast ("DVSimLogger" , setup_logger ("dvsim" ))
87+ logger = cast ("DVSimLogger" , logging .getLogger ("dvsim" ))
88+
89+ # Attach a stderr handler with colour formatting (mirrors logzero default)
90+ if not logger .handlers :
91+ sh = logging .StreamHandler (sys .stderr )
92+ sh .setFormatter (_ColorFormatter (DEFAULT_FORMAT , datefmt = _DATE_FORMAT ))
93+ logger .addHandler (sh )
94+
95+ # Prevent log records bubbling up to the root logger
96+ logger .propagate = False
97+
98+ # Log any unhandled exceptions
99+ _previous_excepthook = sys .excepthook
100+
101+ def _handle_exception (exc_type , exc_value , exc_tb ):
102+ logger .critical ("Unhandled exception" , exc_info = (exc_type , exc_value , exc_tb ))
103+ _previous_excepthook (exc_type , exc_value , exc_tb )
104+
105+ sys .excepthook = _handle_exception
106+
107+ return logger
66108
67109
68110# Logger to import
@@ -94,5 +136,5 @@ def configure_logging(*, verbose: bool, debug: bool, log_level: str | None, log_
94136 if log_file :
95137 log .set_logfile (
96138 path = Path (log_file ),
97- level = log_level ,
139+ level = new_log_level ,
98140 )
0 commit comments