Coverage for src / competitive_verifier / log.py: 100%
52 statements
« prev ^ index » next coverage.py v7.13.1, created at 2026-04-26 12:38 +0900
« prev ^ index » next coverage.py v7.13.1, created at 2026-04-26 12:38 +0900
1import pathlib
2import sys
3from contextlib import contextmanager
4from dataclasses import asdict, dataclass
5from logging import (
6 DEBUG,
7 ERROR,
8 INFO,
9 NOTSET,
10 WARNING,
11 Handler,
12 LogRecord,
13 basicConfig,
14)
15from typing import TextIO
17import colorlog
18from colorama import Fore, Style
20from competitive_verifier import github
23@dataclass
24class GitHubMessageParams:
25 title: str | None = None
26 file: str | pathlib.Path | None = None
27 col: int | None = None
28 end_column: int | None = None
29 line: int | None = None
30 end_line: int | None = None
33class GitHubActionsHandler(Handler):
34 def __init__(
35 self,
36 *,
37 stream: TextIO,
38 level: int | str = NOTSET,
39 ) -> None:
40 super().__init__(level)
41 self.stream = stream
43 def emit(self, record: LogRecord) -> None:
44 if record.levelno >= ERROR:
45 command = "error"
46 elif record.levelno >= WARNING:
47 command = "warning"
48 elif record.levelno >= INFO:
49 command = "notice"
50 else:
51 if record.levelno >= DEBUG:
52 other_name = (
53 ""
54 if record.name.startswith("competitive_verifier")
55 else f"{record.name}:{record.lineno}:"
56 )
57 github.debug(other_name + record.getMessage(), stream=self.stream)
58 return
60 if (gh := getattr(record, "github", None)) is None or not isinstance(
61 gh, GitHubMessageParams
62 ):
63 return
65 if not (gh.title or record.name.startswith("competitive_verifier")):
66 gh.title = record.name
68 github.message(command, record.getMessage(), stream=self.stream, **asdict(gh))
71def configure_stderr_logging(
72 default_level: int | None = None,
73) -> None: # pragma: no cover
74 colorlog_handler = colorlog.StreamHandler(sys.stderr)
75 colorlog_handler.setLevel(default_level or WARNING)
76 colorlog_handler.setFormatter(
77 colorlog.ColoredFormatter(
78 "%(log_color)s%(levelname)s%(reset)s:%(name)s:%(lineno)d:%(message)s"
79 )
80 )
82 handlers: list[Handler] = [colorlog_handler]
84 if github.env.is_in_github_actions():
85 handlers.append(GitHubActionsHandler(stream=sys.stderr))
87 basicConfig(
88 level=NOTSET,
89 handlers=handlers,
90 )
93def _console_group(category: str, *, title: str, file: TextIO | None):
94 print(
95 (
96 f"<------------- {Fore.CYAN}{category}:"
97 f"{Fore.YELLOW}{title}"
98 f"{Style.RESET_ALL} ------------->"
99 ),
100 file=file,
101 )
104@contextmanager
105def group(title: str, *, stream: TextIO | None = None):
106 if stream is None:
107 stream = sys.stderr
108 if github.env.is_in_github_actions():
109 try:
110 github.begin_group(title, stream=stream)
111 yield
112 finally:
113 github.end_group(stream=stream)
114 else:
115 try:
116 _console_group(" Start group", title=title, file=stream)
117 yield
118 finally:
119 _console_group("Finish group", title=title, file=stream)