Source code for research_helpers.log.formatters

"""Event rendering for a console or a file."""

from __future__ import annotations

from typing import TYPE_CHECKING, Any

from colorama import Fore, Style

if TYPE_CHECKING:
    from structlog.types import EventDict

__all__ = ['Renderer']

# a dotted module name longer than this is abbreviated to its first and last components
MAX_MODULE = 30
MIN_PARTS = 2

# 'HH:MM:SS' from an ISO timestamp
CLOCK = 8


[docs] class Renderer: """Render one event as a line, with any traceback on the lines after it.""" LEVEL_COLOURS = { 'debug': Fore.CYAN, 'info': Fore.GREEN, 'warning': Fore.YELLOW, 'error': Fore.RED, 'critical': Fore.RED + Style.BRIGHT, } def __init__(self, *, colours: bool = False, clock_only: bool = False) -> None: """Create a renderer. Arguments: colours: colour the level and module. clock_only: show the time alone rather than the full timestamp. """ self.colours = colours self.clock_only = clock_only def __call__(self, _logger: Any, _name: str, event_dict: EventDict) -> str: """Render an event.""" level = str(event_dict.pop('level', 'info')) timestamp = str(event_dict.pop('timestamp', '')) event = str(event_dict.pop('event', '')) # 'module' is set by add_module_name from the logger name module = str(event_dict.pop('module', 'unknown')) event_dict.pop('logger', None) # the traceback goes below the line, not inside its context exception = event_dict.pop('exception', None) parts = [ self._level(level), self._time(timestamp), self._module(module), event, ] context = ', '.join(f'{key}={value}' for key, value in event_dict.items() if not key.startswith('_')) if context: parts.append(f'({context})') line = ' '.join(part for part in parts if part) return f'{line}\n{exception}' if exception else line def _level(self, level: str) -> str: text = f'[{level.upper():8s}]' if not self.colours: return text return f'{self.LEVEL_COLOURS.get(level, "")}{text}{Style.RESET_ALL}' def _time(self, timestamp: str) -> str: if not timestamp: return '' if not self.clock_only: return timestamp # fall back to the whole string if it is not an ISO timestamp _, separator, time = timestamp.partition('T') return (time if separator else timestamp)[:CLOCK] def _module(self, module: str) -> str: if len(module) > MAX_MODULE: parts = module.split('.') if len(parts) > MIN_PARTS: module = f'{parts[0]}...{parts[-1]}' return f'{Fore.BLUE}{module}{Style.RESET_ALL}' if self.colours else module