tux.utils.logger
¶
Rich logging configuration for Tux.
This module sets up global logging configuration using loguru with Rich formatting. It should be imported and initialized at the start of the application.
Classes:
Name | Description |
---|---|
RichHandlerProtocol | Protocol for Rich handler. |
LoguruRichHandler | Enhanced Rich handler for loguru that splits long messages into two lines. |
Functions:
Name | Description |
---|---|
highlight | Create a highlighter function for the given style. |
setup_logging | Set up global logging configuration. |
Classes¶
LoguruRichHandler(*args: Any, **kwargs: Any)
¶
Bases: RichHandler
, RichHandlerProtocol
Enhanced Rich handler for loguru that splits long messages into two lines.
For messages that fit within the available space (i.e. between the prefix and the right-aligned source info), a single line is printed. If the message is too long, then:
- The first line prints as much of the message as possible.
- The second line starts with a continued prefix that is spaced to match the normal prefix and prints the remainder (with the source info right-aligned).
The normal prefix is:
█ [HH:MM:SS][LEVEL ]
and the continued prefix is:
█ [CONTINUED ]
Methods:
Name | Description |
---|---|
emit | Handle log record emission with custom formatting. |
Source code in tux/utils/logger.py
Functions¶
emit(record: LogRecord) -> None
¶
Handle log record emission with custom formatting.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
record | LogRecord | The log record to emit | required |
Notes
Formats log records with: - Colored level indicator - Timestamp - Level name - Source location - Message
Source code in tux/utils/logger.py
def emit(self, record: LogRecord) -> None:
"""Handle log record emission with custom formatting.
Parameters
----------
record : LogRecord
The log record to emit
Notes
-----
Formats log records with:
- Colored level indicator
- Timestamp
- Level name
- Source location
- Message
"""
try:
# Format the message
message = self.format(record)
# --- Level symbol and text ---
level_name = record.levelname.lower()
level_symbols = {
"debug": "[bold bright_black]█[/]", # Muted gray for debug
"info": "[bold bright_blue]█[/]", # Bright blue for info
"warning": "[bold #FFA500]█[/]", # Orange for warning
"error": "[bold #FF453A]█[/]", # Apple red for error
"critical": "[bold #FF453A on #800000]█[/]", # Red on dark red for critical
"success": "[bold #32CD32]█[/]", # Lime green for success
"trace": "[dim #808080]█[/]", # Gray for trace
}
# Get current time
now = datetime.now(UTC)
time_text = Text(now.strftime("%H:%M:%S"))
time_text.stylize("bold")
# Format level name
level_text = Text(f"[{level_name.upper():<8}]")
level_text.stylize(f"bold {level_name}")
# --- Constants ---
level_field_width = 4 # Adjust as needed
symbol = level_symbols.get(level_name, "[bright_black]█[/]")
# --- First prefix ---
first_prefix_markup = (
f"{symbol}"
+ f"[log.time][{datetime.fromtimestamp(record.created, tz=UTC).strftime('%H:%M:%S')}][/]"
+ "[log.bracket][[/]"
+ f"[logging.level.{level_name}]{record.levelname.upper()[:4].ljust(level_field_width)}[/]"
+ "[log.bracket]][/]"
+ " "
)
# --- Source info ---
# For example: "run @ main.py:215"
source_info = (
f"[dim]{record.funcName}[bright_black] @ [/bright_black]{record.filename}:{record.lineno}[/dim]"
)
# --- Continued prefix ---
continued_prefix_markup = (
f"{symbol} [log.bracket][[/]"
+ f"[logging.level.info]{'CONTINUED'.ljust(level_field_width)}[/]"
+ "[log.bracket]][/]"
+ " "
)
# Convert the formatted message to plain text and strip all whitespace
plain_message = Text.from_markup(message).plain.strip()
# Clean up task names in messages
if "discord-ext-tasks: " in plain_message:
# First remove the discord-ext-tasks prefix
plain_message = plain_message.replace("discord-ext-tasks: ", "")
# Then trim everything after the dots in task names
plain_message = re.sub(r"(\w+)\.\w+", r"\1", plain_message)
# Print first line with source info after log type
first_line = (first_prefix_markup + source_info + " " + plain_message).rstrip()
self.console.print(first_line, markup=True, highlight=False)
# If message is long, print continued lines
if len(plain_message) > 160: # Arbitrary threshold for line continuation
continued_message = plain_message[160:]
while continued_message:
chunk, continued_message = continued_message[:160], continued_message[160:]
line = (continued_prefix_markup + chunk).rstrip()
self.console.print(line, markup=True, highlight=False)
except Exception:
self.handleError(record)
Functions¶
highlight(style: str) -> dict[str, Callable[[Text], Text]]
¶
Create a highlighter function for the given style.
setup_logging() -> None
¶
Set up global logging configuration.
Source code in tux/utils/logger.py
def setup_logging() -> None:
"""Set up global logging configuration."""
console = Console(
force_terminal=True,
color_system="truecolor",
width=160,
theme=Theme(
{
"logging.level.success": "bold #32CD32", # Lime green
"logging.level.trace": "dim #808080", # Gray
"logging.level.debug": "bold bright_black", # Muted gray
"logging.level.info": "bold bright_blue", # Bright blue
"logging.level.warning": "bold #FFA500", # Orange
"logging.level.error": "bold #FF453A", # Apple red
"logging.level.critical": "bold #FF453A reverse", # Reversed apple red
"log.time": "bold bright_white", # Keep time bright white
"log.bracket": "bold bright_black", # Keep brackets muted
},
),
)
logger.configure(
handlers=[
{
"sink": LoguruRichHandler(
console=console,
show_time=False, # We display time ourselves.
show_path=False,
rich_tracebacks=True,
tracebacks_show_locals=True,
log_time_format="[%X]",
markup=True,
highlighter=None,
),
"format": "{message}",
"level": "DEBUG",
},
],
)