2 from datetime
import datetime
3 from logging
import Handler
, LogRecord
4 from pathlib
import Path
5 from types
import ModuleType
6 from typing
import ClassVar
, Iterable
, List
, Optional
, Type
, Union
8 from pip
._vendor
.rich
._null
_file
import NullFile
10 from . import get_console
11 from ._log
_render
import FormatTimeCallable
, LogRender
12 from .console
import Console
, ConsoleRenderable
13 from .highlighter
import Highlighter
, ReprHighlighter
14 from .text
import Text
15 from .traceback
import Traceback
18 class 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.
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.
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.
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``.
51 KEYWORDS
: ClassVar
[Optional
[List
[str]]] = [
61 HIGHLIGHTER_CLASS
: ClassVar
[Type
[Highlighter
]] = ReprHighlighter
65 level
: Union
[int, str] = logging
.NOTSET
,
66 console
: Optional
[Console
] = None,
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,
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,
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(
92 show_level
=show_level
,
94 time_format
=log_time_format
,
95 omit_repeated_times
=omit_repeated_times
,
98 self
.enable_link_path
= enable_link_path
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
111 def get_level_text(self
, record
: LogRecord
) -> Text
:
112 """Get the level name from the record.
115 record (LogRecord): LogRecord instance.
118 Text: A tuple of the style and level name.
120 level_name
= record
.levelname
121 level_text
= Text
.styled(
122 level_name
.ljust(8), f
"logging.level.{level_name.lower()}"
126 def emit(self
, record
: LogRecord
) -> None:
127 """Invoked by logging."""
128 message
= self
.format(record
)
133 and record
.exc_info
!= (None, None, None)
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(
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
,
151 message
= record
.getMessage()
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
)
159 message_renderable
= self
.render_message(record
, message
)
160 log_renderable
= self
.render(
161 record
=record
, traceback
=traceback
, message_renderable
=message_renderable
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
)
170 self
.console
.print(log_renderable
)
172 self
.handleError(record
)
174 def render_message(self
, record
: LogRecord
, message
: str) -> "ConsoleRenderable":
175 """Render message text in to Text.
178 record (LogRecord): logging Record.
179 message (str): String containing log message.
182 ConsoleRenderable: Renderable to display log message.
184 use_markup
= getattr(record
, "markup", self
.markup
)
185 message_text
= Text
.from_markup(message
) if use_markup
else Text(message
)
187 highlighter
= getattr(record
, "highlighter", self
.highlighter
)
189 message_text
= highlighter(message_text
)
191 if self
.keywords
is None:
192 self
.keywords
= self
.KEYWORDS
195 message_text
.highlight_words(self
.keywords
, "logging.keyword")
203 traceback
: Optional
[Traceback
],
204 message_renderable
: "ConsoleRenderable",
205 ) -> "ConsoleRenderable":
206 """Render log for display.
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.
214 ConsoleRenderable: Renderable to display log.
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
)
221 log_renderable
= self
._log
_render
(
223 [message_renderable
] if not traceback
else [message_renderable
, traceback
],
225 time_format
=time_format
,
228 line_no
=record
.lineno
,
229 link_path
=record
.pathname
if self
.enable_link_path
else None,
231 return log_renderable
234 if __name__
== "__main__": # pragma: no cover
235 from time
import sleep
237 FORMAT
= "%(message)s"
238 # FORMAT = "%(asctime)-15s - %(levelname)s - %(message)s"
243 handlers
=[RichHandler(rich_tracebacks
=True, tracebacks_show_locals
=True)],
245 log
= logging
.getLogger("rich")
247 log
.info("Server starting...")
248 log
.info("Listening on http://127.0.0.1:8080")
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")
258 "JSONRPC request\n--> %r\n<-- %r",
261 "method": "confirmFruitPurchase",
262 "params": [["apple", "orange", "mangoes", "pomelo"], 1.123],
265 {"version": "1.1", "result": True, "error": None, "id": "194521489"}
,
268 "Loading configuration file /adasd/asdasd/qeqwe/qwrqwrqwr/sdgsdgsdg/werwerwer/dfgerert/ertertert/ertetert/werwerwer"
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.")
275 def divide() -> None:
279 log
.debug("in divide")
283 log
.exception("An error of some kind occurred!")
287 log
.critical("Out of memory!")
288 log
.info("Server exited with code=-1")
289 log
.info("[bold]EXITING...[/bold]", extra
=dict(markup
=True))