# piker: trading gear for hackers # Copyright (C) 2018-present Tyler Goodlet (in stewardship of piker0) # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Affero General Public License for more details. # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . """ Log like a forester! """ import logging import json import reprlib from typing import ( Callable, ) import tractor from pygments import ( highlight, lexers, formatters, ) # Makes it so we only see the full module name when using ``__name__`` # without the extra "piker." prefix. _proj_name: str = 'piker' def get_logger( name: str|None = None, **tractor_log_kwargs, ) -> logging.Logger: ''' Return the package log or a sub-logger if a `name=` is provided, which defaults to the calling module's pkg-namespace path. See `tractor.log.get_logger()` for details. ''' pkg_name: str = _proj_name if ( name and pkg_name in name ): name: str = name.lstrip(f'{_proj_name}.') return tractor.log.get_logger( name=name, pkg_name=pkg_name, **tractor_log_kwargs, ) def get_console_log( level: str|None = None, name: str|None = None, pkg_name: str|None = None, with_tractor_log: bool = False, # ?TODO, support a "log-spec" style `str|dict[str, str]` which # dictates both the sublogger-key and a level? # -> see similar idea in `modden`'s usage. **tractor_log_kwargs, ) -> logging.Logger: ''' Get the package logger and enable a handler which writes to stderr. Yeah yeah, i know we can use `DictConfig`. You do it.. Bp ''' pkg_name: str = _proj_name if ( name and pkg_name in name ): name: str = name.lstrip(f'{_proj_name}.') tll: str|None = None if ( with_tractor_log is not False ): tll = level elif maybe_actor := tractor.current_actor( err_on_no_runtime=False, ): tll = maybe_actor.loglevel if tll: t_log = tractor.log.get_console_log( level=tll, name='tractor', # <- XXX, force root tractor log! **tractor_log_kwargs, ) # TODO/ allow only enabling certain tractor sub-logs? assert t_log.name == 'tractor' return tractor.log.get_console_log( level=level, name=name, pkg_name=pkg_name, **tractor_log_kwargs, ) def colorize_json( data: dict, style='algol_nu', ): ''' Colorize json output using ``pygments``. ''' formatted_json = json.dumps( data, sort_keys=True, indent=4, ) return highlight( formatted_json, lexers.JsonLexer(), # likeable styles: algol_nu, tango, monokai formatters.TerminalTrueColorFormatter(style=style) ) # TODO, eventually defer to the version in `modden` once # it becomes a dep! def mk_repr( **repr_kws, ) -> Callable[[str], str]: ''' Allocate and deliver a `repr.Repr` instance with provided input settings using the std-lib's `reprlib` mod, * https://docs.python.org/3/library/reprlib.html ------ Ex. ------ An up to 6-layer-nested `dict` as multi-line: - https://stackoverflow.com/a/79102479 - https://docs.python.org/3/library/reprlib.html#reprlib.Repr.maxlevel ''' def_kws: dict[str, int] = dict( indent=2, maxlevel=6, # recursion levels maxstring=66, # match editor line-len limit ) def_kws |= repr_kws reprr = reprlib.Repr(**def_kws) return reprr.repr