diff --git a/src/python/jw/pkg/lib/App.py b/src/python/jw/pkg/lib/App.py index e7bcbc17..016ddaa2 100644 --- a/src/python/jw/pkg/lib/App.py +++ b/src/python/jw/pkg/lib/App.py @@ -9,7 +9,19 @@ from argparse import ArgumentDefaultsHelpFormatter, ArgumentParser, Namespace from typing import Any, cast, override, TYPE_CHECKING from .AsyncRunner import AsyncRunner -from .log import DEBUG, ERR, NOTICE, log, log_m, set_log_flags, set_log_level +from .log import ( + DEBUG, + ERR, + NOTICE, + LogFlag, + log, + log_m, + parse_log_flags, + parse_log_level, + set_log_flags, + set_log_level +) + from .Types import LoadTypes from .util import pretty_cmd @@ -43,6 +55,30 @@ class App: # export default = None, ) + def _default_log_flags_env(self) -> str: + return 'JW_DEFAULT_LOG_FLAGS' + + def _default_log_flags(self, default: LogFlag) -> LogFlag: + return default + + def _default_log_level_env(self) -> str: + return 'JW_DEFAULT_LOG_LEVEL' + + def _default_log_level(self, default: int) -> int: + return default + + def _default_log_file_env(self) -> str: + return 'JW_DEFAULT_LOG_FILE' + + def _default_log_file(self, default: str | None) -> str | None: + return default + + def _default_show_backtrace_env(self) -> str: + return 'JW_DEFAULT_SHOW_BACKTRACE' + + def _default_show_backtrace(self, default: bool) -> bool: + return default + def __init__( self, description: str = '', @@ -113,20 +149,25 @@ class App: # export self.__args: Namespace | None = None self.__cmdline: str | None = None - self.__default_log_flags: str = os.getenv( - 'JW_DEFAULT_LOG_FLAGS', default = 'stderr,position,prio,color' + + self.__default_log_flags = self._default_log_flags( + LogFlag.STDERR | LogFlag.POSITION | LogFlag.PRIO | LogFlag.COLOR ) - self.__default_log_level: str | int | None = os.getenv( - 'JW_DEFAULT_LOG_LEVEL', default = NOTICE - ) - self.__default_log_file: str | None = os.getenv( - 'JW_DEFAULT_LOG_FILE', default = None - ) - backtrace: str | bool = os.getenv('JW_DEFAULT_SHOW_BACKTRACE', False) - self.__back_trace = isinstance(backtrace, str) and backtrace.lower() in { - '1', - 'true', - } + if (env := os.getenv(self._default_log_flags_env(), None)) is not None: + self.__default_log_flags = parse_log_flags(env) + + self.__default_log_level = self._default_log_level(NOTICE) + if (env := os.getenv(self._default_log_level_env(), None)) is not None: + self.__default_log_level = parse_log_level(env) + + self.__default_log_file = self._default_log_file(None) + if (env := os.getenv(self._default_log_file_env(), None)) is not None: + self.__default_log_file = env + + self.__back_trace = self._default_show_backtrace(False) + if (env := os.getenv(self._default_show_backtrace_env())) is not None: + self.__back_trace = env.lower() in ['1', 'true'] + set_log_flags(self.__default_log_flags) set_log_level(self.__default_log_level) diff --git a/src/python/jw/pkg/lib/log.py b/src/python/jw/pkg/lib/log.py index 6e9f70e7..87371181 100644 --- a/src/python/jw/pkg/lib/log.py +++ b/src/python/jw/pkg/lib/log.py @@ -5,9 +5,11 @@ import re import sys import syslog +from enum import Flag, auto + from datetime import datetime from os.path import basename -from typing import TYPE_CHECKING, cast +from typing import TYPE_CHECKING, cast, override if TYPE_CHECKING: import io @@ -81,16 +83,44 @@ CONSOLE_FONT_WHITE = '\033[37m' CONSOLE_FONT_BLINK = '\033[5m' CONSOLE_FONT_OFF = '\033[m' -f_position = 'position' -f_module = 'module' -f_date = 'date' -f_stderr = 'stderr' -f_stdout = 'stdout' -f_prio = 'prio' -f_color = 'color' -f_default = [ f_position, f_stderr, f_prio, f_color ] +# -- ytools compatible log flags +#syslog +#console +#color +#prio +#position +#ide +#trace_rename_thread_to_shorter +#trace_rename_thread_to_longer +#trace_inout +#skip_openlog +#id +#date +#pid +#highlight_first_error + +class LogFlag(Flag): + POSITION = auto() + MODULE = auto() + DATE = auto() + STDERR = auto() + STDOUT = auto() + PRIO = auto() + COLOR = auto() + + @override + def __str__(self) -> str: + def _get_name(f: LogFlag) -> str: + return f.name or '' + + return ','.join( + f.name.lower() + for f in sorted(self, key = _get_name) + if f.name + ) + +_flags = LogFlag.POSITION | LogFlag.STDERR | LogFlag.PRIO | LogFlag.COLOR -_flags = set(f_default) _log_prefix = '' _clean_log_prefix = '' _file_name_len = 20 @@ -181,6 +211,8 @@ def log_m(prio: int, *args: Any, **kwargs: Any) -> None: # export if isinstance(a, list): margs += '\n'.join([str(elem) for elem in a]) continue + if a == '': + continue margs += ' ' + str(a) if 'caller' not in kwargs: caller = get_caller_pos(1) @@ -204,43 +236,44 @@ def log( # export color_on = '' color_off = '' - if f_date in _flags: - msg += datetime.now().strftime("%b %d %H:%M:%S.%f ") + if LogFlag.DATE & _flags: + msg += datetime.now().strftime("%b %d %H:%M:%S.%f") + ' ' - if f_prio in _flags: + if LogFlag.PRIO & _flags: msg += _short_prio_str[prio] + ' ' - if f_position in _flags: + if LogFlag.POSITION & _flags: if 'caller' in kwargs: mod, name, line = kwargs['caller'] else: mod, name, line = get_caller_pos(1) - if f_module in _flags: + if LogFlag.MODULE & _flags: msg += pad(mod, _module_name_len) msg += pad(name, _file_name_len) + '[' + pad(str(line), 4, True) + ']' - if f_color in _flags: + if LogFlag.COLOR & _flags: color_on, color_off = console_color_chars(prio) margs = '' if len(args): - for a in args: - margs += ' ' + str(a) + margs = ' '.join(str(a) for a in args) if only_printable: margs = _special_char_regex.sub( lambda mo: _special_chars[mo.string[mo.start():mo.end()]], margs ) margs = re.sub('[\x01-\x1f]', '.', margs) + if msg and not msg.endswith(' '): + margs = ' ' + margs for file in _log_file_streams: print(msg + _clean_log_prefix + margs, file = file) msg += _log_prefix - if not len(msg): + if not len(msg) and not len(margs): return if len(margs): @@ -252,10 +285,10 @@ def log( # export elif _streams: files = [s.stream for s in _streams.values()] else: - if f_stdout in _flags: + if LogFlag.STDOUT & _flags: files.append(sys.stdout) - if f_stderr in _flags: + if LogFlag.STDERR & _flags: files.append(sys.stderr) if not len(files): @@ -307,30 +340,27 @@ def set_log_level(level: str | int | None = None) -> int: def set_level(level: str | int | None = None) -> int: # export return set_log_level(level) -def set_flags(flags: str | None) -> str: # export - global _flags - ret = ','.join(_flags) - if flags is not None: - _flags = set(flags.split(',')) +def parse_flags(flags: str) -> LogFlag: + ret: LogFlag = LogFlag(0) + s = set(flags.split(',')) + for f in LogFlag: + assert f.name is not None + if f.name.lower() in s: + ret |= f return ret -def set_log_flags(flags: str) -> str: - return set_flags(flags) +parse_log_flags = parse_flags - #syslog - #console - #color - #prio - #position - #ide - #trace_rename_thread_to_shorter - #trace_rename_thread_to_longer - #trace_inout - #skip_openlog - #id - #date - #pid - #highlight_first_error +def set_flags(flags: str | LogFlag | None) -> LogFlag: # export + global _flags + if isinstance(flags, str): + flags = parse_flags(flags) + ret = _flags + if flags is not None: + _flags = flags + return ret + +set_log_flags = set_flags def append_to_prefix(prefix: str) -> str: # export global _log_prefix diff --git a/test/integration/jw-pkg/help/test-expected.txt b/test/integration/jw-pkg/help/test-expected.txt index 95651dcb..391580ec 100644 --- a/test/integration/jw-pkg/help/test-expected.txt +++ b/test/integration/jw-pkg/help/test-expected.txt @@ -11,7 +11,7 @@ jw-pkg swiss army knife options: --log-flags LOG_FLAGS - Log flags (default: stderr,position,prio,color) + Log flags (default: color,position,prio,stderr) --log-level LOG_LEVEL Log level (default: 5) --log-file LOG_FILE Log file (default: None)