Skip to content

Commit 7488f34

Browse files
committed
feat: implement logzero functionality directly
The logzero package provides a nice simple default logger setup. However it's currently unmaintained and so we don't really want to depend on it. This commit implements the main functionality we used from it so that we can remove the dependency. Signed-off-by: James McCorrie <james.mccorrie@lowrisc.org>
1 parent 8f5dff8 commit 7488f34

File tree

4 files changed

+403
-350
lines changed

4 files changed

+403
-350
lines changed

pyproject.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ dependencies = [
2222
"gitpython>=3.1.45",
2323
"hjson>=3.1.0",
2424
"jinja2>=3.1.6",
25-
"logzero>=1.7.0",
2625
"psutil>=7.2.2",
2726
"pydantic>=2.9.2",
2827
"pyyaml>=6.0.2",

scripts/license_check.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,12 @@
55

66
"""Check all files for license header."""
77

8+
import logging
89
import subprocess
910
import sys
1011
from pathlib import Path
1112

12-
from logzero import logger
13+
logger = logging.getLogger(__name__)
1314

1415
LICENSE = (
1516
"Copyright lowRISC contributors (OpenTitan project).",
@@ -36,7 +37,7 @@
3637

3738

3839
def check_header(*, text: str, trailing_newline_optional: bool = False) -> bool:
39-
"""Check header complies with license requirmeents."""
40+
"""Check header complies with license requirements."""
4041
lines = text.splitlines()
4142

4243
try:
@@ -92,6 +93,6 @@ def check_header(*, text: str, trailing_newline_optional: bool = False) -> bool:
9293
failed.append(path)
9394

9495
for path in failed:
95-
logger.error(f"failed: {path}")
96+
logger.error("failed: %s", path)
9697

9798
sys.exit(min(len(failed), 255))

src/dvsim/logging.py

Lines changed: 53 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,44 @@
55
"""Logging config."""
66

77
import logging
8+
import sys
89
from pathlib import Path
910
from typing import cast
1011

11-
from logzero import DEFAULT_FORMAT, setup_logger
12-
1312
__all__ = (
1413
"configure_logging",
1514
"LOG_LEVELS",
1615
)
1716

1817

1918
LOG_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

2248
class 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

6183
def _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

Comments
 (0)