Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/pip/_vendor/rich/logging.py: 27%

78 statements  

« prev     ^ index     » next       coverage.py v7.4.3, created at 2024-02-26 06:33 +0000

1import logging 

2from datetime import datetime 

3from logging import Handler, LogRecord 

4from pathlib import Path 

5from types import ModuleType 

6from typing import ClassVar, Iterable, List, Optional, Type, Union 

7 

8from pip._vendor.rich._null_file import NullFile 

9 

10from . import get_console 

11from ._log_render import FormatTimeCallable, LogRender 

12from .console import Console, ConsoleRenderable 

13from .highlighter import Highlighter, ReprHighlighter 

14from .text import Text 

15from .traceback import Traceback 

16 

17 

18class RichHandler(Handler): 

19 """A logging handler that renders output with Rich. The time / level / message and file are displayed in columns. 

20 The level is color coded, and the message is syntax highlighted. 

21 

22 Note: 

23 Be careful when enabling console markup in log messages if you have configured logging for libraries not 

24 under your control. If a dependency writes messages containing square brackets, it may not produce the intended output. 

25 

26 Args: 

27 level (Union[int, str], optional): Log level. Defaults to logging.NOTSET. 

28 console (:class:`~rich.console.Console`, optional): Optional console instance to write logs. 

29 Default will use a global console instance writing to stdout. 

30 show_time (bool, optional): Show a column for the time. Defaults to True. 

31 omit_repeated_times (bool, optional): Omit repetition of the same time. Defaults to True. 

32 show_level (bool, optional): Show a column for the level. Defaults to True. 

33 show_path (bool, optional): Show the path to the original log call. Defaults to True. 

34 enable_link_path (bool, optional): Enable terminal link of path column to file. Defaults to True. 

35 highlighter (Highlighter, optional): Highlighter to style log messages, or None to use ReprHighlighter. Defaults to None. 

36 markup (bool, optional): Enable console markup in log messages. Defaults to False. 

37 rich_tracebacks (bool, optional): Enable rich tracebacks with syntax highlighting and formatting. Defaults to False. 

38 tracebacks_width (Optional[int], optional): Number of characters used to render tracebacks, or None for full width. Defaults to None. 

39 tracebacks_extra_lines (int, optional): Additional lines of code to render tracebacks, or None for full width. Defaults to None. 

40 tracebacks_theme (str, optional): Override pygments theme used in traceback. 

41 tracebacks_word_wrap (bool, optional): Enable word wrapping of long tracebacks lines. Defaults to True. 

42 tracebacks_show_locals (bool, optional): Enable display of locals in tracebacks. Defaults to False. 

43 tracebacks_suppress (Sequence[Union[str, ModuleType]]): Optional sequence of modules or paths to exclude from traceback. 

44 locals_max_length (int, optional): Maximum length of containers before abbreviating, or None for no abbreviation. 

45 Defaults to 10. 

46 locals_max_string (int, optional): Maximum length of string before truncating, or None to disable. Defaults to 80. 

47 log_time_format (Union[str, TimeFormatterCallable], optional): If ``log_time`` is enabled, either string for strftime or callable that formats the time. Defaults to "[%x %X] ". 

48 keywords (List[str], optional): List of words to highlight instead of ``RichHandler.KEYWORDS``. 

49 """ 

50 

51 KEYWORDS: ClassVar[Optional[List[str]]] = [ 

52 "GET", 

53 "POST", 

54 "HEAD", 

55 "PUT", 

56 "DELETE", 

57 "OPTIONS", 

58 "TRACE", 

59 "PATCH", 

60 ] 

61 HIGHLIGHTER_CLASS: ClassVar[Type[Highlighter]] = ReprHighlighter 

62 

63 def __init__( 

64 self, 

65 level: Union[int, str] = logging.NOTSET, 

66 console: Optional[Console] = None, 

67 *, 

68 show_time: bool = True, 

69 omit_repeated_times: bool = True, 

70 show_level: bool = True, 

71 show_path: bool = True, 

72 enable_link_path: bool = True, 

73 highlighter: Optional[Highlighter] = None, 

74 markup: bool = False, 

75 rich_tracebacks: bool = False, 

76 tracebacks_width: Optional[int] = None, 

77 tracebacks_extra_lines: int = 3, 

78 tracebacks_theme: Optional[str] = None, 

79 tracebacks_word_wrap: bool = True, 

80 tracebacks_show_locals: bool = False, 

81 tracebacks_suppress: Iterable[Union[str, ModuleType]] = (), 

82 locals_max_length: int = 10, 

83 locals_max_string: int = 80, 

84 log_time_format: Union[str, FormatTimeCallable] = "[%x %X]", 

85 keywords: Optional[List[str]] = None, 

86 ) -> None: 

87 super().__init__(level=level) 

88 self.console = console or get_console() 

89 self.highlighter = highlighter or self.HIGHLIGHTER_CLASS() 

90 self._log_render = LogRender( 

91 show_time=show_time, 

92 show_level=show_level, 

93 show_path=show_path, 

94 time_format=log_time_format, 

95 omit_repeated_times=omit_repeated_times, 

96 level_width=None, 

97 ) 

98 self.enable_link_path = enable_link_path 

99 self.markup = markup 

100 self.rich_tracebacks = rich_tracebacks 

101 self.tracebacks_width = tracebacks_width 

102 self.tracebacks_extra_lines = tracebacks_extra_lines 

103 self.tracebacks_theme = tracebacks_theme 

104 self.tracebacks_word_wrap = tracebacks_word_wrap 

105 self.tracebacks_show_locals = tracebacks_show_locals 

106 self.tracebacks_suppress = tracebacks_suppress 

107 self.locals_max_length = locals_max_length 

108 self.locals_max_string = locals_max_string 

109 self.keywords = keywords 

110 

111 def get_level_text(self, record: LogRecord) -> Text: 

112 """Get the level name from the record. 

113 

114 Args: 

115 record (LogRecord): LogRecord instance. 

116 

117 Returns: 

118 Text: A tuple of the style and level name. 

119 """ 

120 level_name = record.levelname 

121 level_text = Text.styled( 

122 level_name.ljust(8), f"logging.level.{level_name.lower()}" 

123 ) 

124 return level_text 

125 

126 def emit(self, record: LogRecord) -> None: 

127 """Invoked by logging.""" 

128 message = self.format(record) 

129 traceback = None 

130 if ( 

131 self.rich_tracebacks 

132 and record.exc_info 

133 and record.exc_info != (None, None, None) 

134 ): 

135 exc_type, exc_value, exc_traceback = record.exc_info 

136 assert exc_type is not None 

137 assert exc_value is not None 

138 traceback = Traceback.from_exception( 

139 exc_type, 

140 exc_value, 

141 exc_traceback, 

142 width=self.tracebacks_width, 

143 extra_lines=self.tracebacks_extra_lines, 

144 theme=self.tracebacks_theme, 

145 word_wrap=self.tracebacks_word_wrap, 

146 show_locals=self.tracebacks_show_locals, 

147 locals_max_length=self.locals_max_length, 

148 locals_max_string=self.locals_max_string, 

149 suppress=self.tracebacks_suppress, 

150 ) 

151 message = record.getMessage() 

152 if self.formatter: 

153 record.message = record.getMessage() 

154 formatter = self.formatter 

155 if hasattr(formatter, "usesTime") and formatter.usesTime(): 

156 record.asctime = formatter.formatTime(record, formatter.datefmt) 

157 message = formatter.formatMessage(record) 

158 

159 message_renderable = self.render_message(record, message) 

160 log_renderable = self.render( 

161 record=record, traceback=traceback, message_renderable=message_renderable 

162 ) 

163 if isinstance(self.console.file, NullFile): 

164 # Handles pythonw, where stdout/stderr are null, and we return NullFile 

165 # instance from Console.file. In this case, we still want to make a log record 

166 # even though we won't be writing anything to a file. 

167 self.handleError(record) 

168 else: 

169 try: 

170 self.console.print(log_renderable) 

171 except Exception: 

172 self.handleError(record) 

173 

174 def render_message(self, record: LogRecord, message: str) -> "ConsoleRenderable": 

175 """Render message text in to Text. 

176 

177 Args: 

178 record (LogRecord): logging Record. 

179 message (str): String containing log message. 

180 

181 Returns: 

182 ConsoleRenderable: Renderable to display log message. 

183 """ 

184 use_markup = getattr(record, "markup", self.markup) 

185 message_text = Text.from_markup(message) if use_markup else Text(message) 

186 

187 highlighter = getattr(record, "highlighter", self.highlighter) 

188 if highlighter: 

189 message_text = highlighter(message_text) 

190 

191 if self.keywords is None: 

192 self.keywords = self.KEYWORDS 

193 

194 if self.keywords: 

195 message_text.highlight_words(self.keywords, "logging.keyword") 

196 

197 return message_text 

198 

199 def render( 

200 self, 

201 *, 

202 record: LogRecord, 

203 traceback: Optional[Traceback], 

204 message_renderable: "ConsoleRenderable", 

205 ) -> "ConsoleRenderable": 

206 """Render log for display. 

207 

208 Args: 

209 record (LogRecord): logging Record. 

210 traceback (Optional[Traceback]): Traceback instance or None for no Traceback. 

211 message_renderable (ConsoleRenderable): Renderable (typically Text) containing log message contents. 

212 

213 Returns: 

214 ConsoleRenderable: Renderable to display log. 

215 """ 

216 path = Path(record.pathname).name 

217 level = self.get_level_text(record) 

218 time_format = None if self.formatter is None else self.formatter.datefmt 

219 log_time = datetime.fromtimestamp(record.created) 

220 

221 log_renderable = self._log_render( 

222 self.console, 

223 [message_renderable] if not traceback else [message_renderable, traceback], 

224 log_time=log_time, 

225 time_format=time_format, 

226 level=level, 

227 path=path, 

228 line_no=record.lineno, 

229 link_path=record.pathname if self.enable_link_path else None, 

230 ) 

231 return log_renderable 

232 

233 

234if __name__ == "__main__": # pragma: no cover 

235 from time import sleep 

236 

237 FORMAT = "%(message)s" 

238 # FORMAT = "%(asctime)-15s - %(levelname)s - %(message)s" 

239 logging.basicConfig( 

240 level="NOTSET", 

241 format=FORMAT, 

242 datefmt="[%X]", 

243 handlers=[RichHandler(rich_tracebacks=True, tracebacks_show_locals=True)], 

244 ) 

245 log = logging.getLogger("rich") 

246 

247 log.info("Server starting...") 

248 log.info("Listening on http://127.0.0.1:8080") 

249 sleep(1) 

250 

251 log.info("GET /index.html 200 1298") 

252 log.info("GET /imgs/backgrounds/back1.jpg 200 54386") 

253 log.info("GET /css/styles.css 200 54386") 

254 log.warning("GET /favicon.ico 404 242") 

255 sleep(1) 

256 

257 log.debug( 

258 "JSONRPC request\n--> %r\n<-- %r", 

259 { 

260 "version": "1.1", 

261 "method": "confirmFruitPurchase", 

262 "params": [["apple", "orange", "mangoes", "pomelo"], 1.123], 

263 "id": "194521489", 

264 }, 

265 {"version": "1.1", "result": True, "error": None, "id": "194521489"}, 

266 ) 

267 log.debug( 

268 "Loading configuration file /adasd/asdasd/qeqwe/qwrqwrqwr/sdgsdgsdg/werwerwer/dfgerert/ertertert/ertetert/werwerwer" 

269 ) 

270 log.error("Unable to find 'pomelo' in database!") 

271 log.info("POST /jsonrpc/ 200 65532") 

272 log.info("POST /admin/ 401 42234") 

273 log.warning("password was rejected for admin site.") 

274 

275 def divide() -> None: 

276 number = 1 

277 divisor = 0 

278 foos = ["foo"] * 100 

279 log.debug("in divide") 

280 try: 

281 number / divisor 

282 except: 

283 log.exception("An error of some kind occurred!") 

284 

285 divide() 

286 sleep(1) 

287 log.critical("Out of memory!") 

288 log.info("Server exited with code=-1") 

289 log.info("[bold]EXITING...[/bold]", extra=dict(markup=True))