diff --git a/.efrocachemap b/.efrocachemap index 808538f8..19ae2423 100644 --- a/.efrocachemap +++ b/.efrocachemap @@ -3995,26 +3995,26 @@ "assets/src/ba_data/python/ba/_generated/__init__.py": "https://files.ballistica.net/cache/ba1/ee/e8/cad05aa531c7faf7ff7b96db7f6e", "assets/src/ba_data/python/ba/_generated/enums.py": "https://files.ballistica.net/cache/ba1/b2/e5/0ee0561e16257a32830645239f34", "ballisticacore-windows/Generic/BallisticaCore.ico": "https://files.ballistica.net/cache/ba1/89/c0/e32c7d2a35dc9aef57cc73b0911a", - "build/prefab/full/linux_arm64_gui/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/0a/56/252de9190ee6367ccbf37174783d", - "build/prefab/full/linux_arm64_gui/release/ballisticacore": "https://files.ballistica.net/cache/ba1/f5/30/29f5a9d9cc5c6f5c76e3058d3621", - "build/prefab/full/linux_arm64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/3e/e5/037d736cacd93a4b005cc93e72ad", - "build/prefab/full/linux_arm64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/22/04/430aa3457c427f0814058c2b4483", - "build/prefab/full/linux_x86_64_gui/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/93/68/307719e44199480a5ee051d993f5", - "build/prefab/full/linux_x86_64_gui/release/ballisticacore": "https://files.ballistica.net/cache/ba1/9f/50/6f8a60e5375bf651bdebda617249", - "build/prefab/full/linux_x86_64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/cf/03/a5a5748fda33c876fbf3e8261b02", - "build/prefab/full/linux_x86_64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/82/3e/5725b87a8cc1e90f69bec58c65d5", - "build/prefab/full/mac_arm64_gui/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/64/16/1589abfd35715bd2aa2915766148", - "build/prefab/full/mac_arm64_gui/release/ballisticacore": "https://files.ballistica.net/cache/ba1/a8/36/584d685f3bea03753acf7344dfce", - "build/prefab/full/mac_arm64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/3d/09/cbb451c2e8f856de61c0eafc5fdc", - "build/prefab/full/mac_arm64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/b3/c9/9b3e221426dae6a047a893a4eb39", - "build/prefab/full/mac_x86_64_gui/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/61/2e/af3b07614ea2fb60f70b3d3b442a", - "build/prefab/full/mac_x86_64_gui/release/ballisticacore": "https://files.ballistica.net/cache/ba1/6e/fb/4b6e3e14ae9e329ae2a5c2eaab24", - "build/prefab/full/mac_x86_64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/86/3b/f8fc04eefa313d673ee98d20e360", - "build/prefab/full/mac_x86_64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/d1/83/544e088664612666bbaa6c1ff422", - "build/prefab/full/windows_x86_gui/debug/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/79/13/29d322c6e8f7717ec87d5027bb20", - "build/prefab/full/windows_x86_gui/release/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/94/01/19f43fe2ee530d48f31665d22ff0", - "build/prefab/full/windows_x86_server/debug/dist/BallisticaCoreHeadless.exe": "https://files.ballistica.net/cache/ba1/17/e9/d6369d897f3595fbe03202887447", - "build/prefab/full/windows_x86_server/release/dist/BallisticaCoreHeadless.exe": "https://files.ballistica.net/cache/ba1/07/ff/cd46cba42a67cf31d6454b9eba7d", + "build/prefab/full/linux_arm64_gui/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/8f/35/1a6fbc2bd9d367b5b5d8350199da", + "build/prefab/full/linux_arm64_gui/release/ballisticacore": "https://files.ballistica.net/cache/ba1/e8/df/e7aae0645d3813227e32628a0ff3", + "build/prefab/full/linux_arm64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/ce/43/1d18f5d73d3fe5d7f1ed4fdc472c", + "build/prefab/full/linux_arm64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/cb/04/5dc6236fd0ebeafbd013299a4766", + "build/prefab/full/linux_x86_64_gui/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/e1/3a/bbe527aae553058d38a89a85e6b5", + "build/prefab/full/linux_x86_64_gui/release/ballisticacore": "https://files.ballistica.net/cache/ba1/60/1c/2e11dded6067b1cb27e6f6d48a0a", + "build/prefab/full/linux_x86_64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/59/fd/0f61ebccbcfb85d01693c431f5a6", + "build/prefab/full/linux_x86_64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/9b/94/8a16341d49d6de25102c07c70675", + "build/prefab/full/mac_arm64_gui/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/ac/1d/d48d569a072d45d96ea86760b9f0", + "build/prefab/full/mac_arm64_gui/release/ballisticacore": "https://files.ballistica.net/cache/ba1/d7/31/f3a671560f4efb8708430f0ce985", + "build/prefab/full/mac_arm64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/6e/76/fa07d7183f1bd0d438657339d33a", + "build/prefab/full/mac_arm64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/b0/8b/60a531e23f24bba638e3fc615ed3", + "build/prefab/full/mac_x86_64_gui/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/9c/1b/519b7ba8f1718787d8ab62f61222", + "build/prefab/full/mac_x86_64_gui/release/ballisticacore": "https://files.ballistica.net/cache/ba1/4b/a0/138ece248132798d69cc81bce581", + "build/prefab/full/mac_x86_64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/a1/58/18ba7845fb9524a5cfef4d14bf7f", + "build/prefab/full/mac_x86_64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/12/93/7e04d239fd333188b1412272c873", + "build/prefab/full/windows_x86_gui/debug/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/44/cb/2144fb8e2fc054d605e8e3baea77", + "build/prefab/full/windows_x86_gui/release/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/89/90/a98081fbe24f8d062443ce84f3cc", + "build/prefab/full/windows_x86_server/debug/dist/BallisticaCoreHeadless.exe": "https://files.ballistica.net/cache/ba1/c9/80/1de60807e22d9a46c6902badbe7f", + "build/prefab/full/windows_x86_server/release/dist/BallisticaCoreHeadless.exe": "https://files.ballistica.net/cache/ba1/d7/f1/e2d6d8fdedd4ec4f3a6c0cc6bc14", "build/prefab/lib/linux_arm64_gui/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/3b/0c/2f4061ab877d415a1c30e0e736db", "build/prefab/lib/linux_arm64_gui/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/3c/5a/2b0714af254c64954ccfe51c70b3", "build/prefab/lib/linux_arm64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/1f/ae/c8a885b1a1868b6846b606cdb456", @@ -4031,14 +4031,14 @@ "build/prefab/lib/mac_x86_64_gui/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/44/df/efb51d1c226eac613d48e2cbf0b8", "build/prefab/lib/mac_x86_64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/1c/f6/357fe951c86c9fc5b1b737cd91ae", "build/prefab/lib/mac_x86_64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/04/17/e2de0ab5df6b938d828e8662ce6d", - "build/prefab/lib/windows/Debug_Win32/BallisticaCoreGenericInternal.lib": "https://files.ballistica.net/cache/ba1/69/dc/6fc1614b2548c6ac76c9e891c2e2", - "build/prefab/lib/windows/Debug_Win32/BallisticaCoreGenericInternal.pdb": "https://files.ballistica.net/cache/ba1/c2/1b/263c5e001c6891d774d941f0bdfd", - "build/prefab/lib/windows/Debug_Win32/BallisticaCoreHeadlessInternal.lib": "https://files.ballistica.net/cache/ba1/7b/3a/f77ffca8d7c45b859d1e48c1b468", - "build/prefab/lib/windows/Debug_Win32/BallisticaCoreHeadlessInternal.pdb": "https://files.ballistica.net/cache/ba1/88/15/1aa07f986d0bf7dac9a1f39635f2", - "build/prefab/lib/windows/Release_Win32/BallisticaCoreGenericInternal.lib": "https://files.ballistica.net/cache/ba1/4b/e1/646d3095ab442e0b18d4c0de9689", - "build/prefab/lib/windows/Release_Win32/BallisticaCoreGenericInternal.pdb": "https://files.ballistica.net/cache/ba1/0f/fe/034c116781ddfe6cc89ada030056", - "build/prefab/lib/windows/Release_Win32/BallisticaCoreHeadlessInternal.lib": "https://files.ballistica.net/cache/ba1/91/64/10fcd883cf0d15895d72a638e2ad", - "build/prefab/lib/windows/Release_Win32/BallisticaCoreHeadlessInternal.pdb": "https://files.ballistica.net/cache/ba1/1e/69/bf40bc8defe923cfa6d48cb5dd04", + "build/prefab/lib/windows/Debug_Win32/BallisticaCoreGenericInternal.lib": "https://files.ballistica.net/cache/ba1/97/5d/5255b7a90235bc570e71bfaf9f60", + "build/prefab/lib/windows/Debug_Win32/BallisticaCoreGenericInternal.pdb": "https://files.ballistica.net/cache/ba1/07/0b/c65c6f6b009633a7cf66ea1c9e08", + "build/prefab/lib/windows/Debug_Win32/BallisticaCoreHeadlessInternal.lib": "https://files.ballistica.net/cache/ba1/81/09/10f7873ec315479806a9daa6e100", + "build/prefab/lib/windows/Debug_Win32/BallisticaCoreHeadlessInternal.pdb": "https://files.ballistica.net/cache/ba1/0f/09/ab4219ed9d6b6b63439a33f38aac", + "build/prefab/lib/windows/Release_Win32/BallisticaCoreGenericInternal.lib": "https://files.ballistica.net/cache/ba1/fd/ae/d0a4fb20969028322bab2ae2365d", + "build/prefab/lib/windows/Release_Win32/BallisticaCoreGenericInternal.pdb": "https://files.ballistica.net/cache/ba1/0b/cf/4b7529302b842bc75695f93c4172", + "build/prefab/lib/windows/Release_Win32/BallisticaCoreHeadlessInternal.lib": "https://files.ballistica.net/cache/ba1/ad/b0/7d2ca14baad3fdb2aac296352570", + "build/prefab/lib/windows/Release_Win32/BallisticaCoreHeadlessInternal.pdb": "https://files.ballistica.net/cache/ba1/68/f8/156cbf9f5cf0fcbae9f12e8b18e8", "src/ballistica/generated/python_embedded/binding.inc": "https://files.ballistica.net/cache/ba1/7d/3e/229a581cb2454ed856f1d8b564a7", "src/ballistica/generated/python_embedded/bootstrap.inc": "https://files.ballistica.net/cache/ba1/aa/a5/3ddc86d1789b2bf1d376b7671a3d" } \ No newline at end of file diff --git a/.idea/dictionaries/ericf.xml b/.idea/dictionaries/ericf.xml index a679acc7..ab5c5eba 100644 --- a/.idea/dictionaries/ericf.xml +++ b/.idea/dictionaries/ericf.xml @@ -337,6 +337,7 @@ capturetheflag carentity cashregistersound + cbegin cbgn cbits cbot @@ -699,6 +700,7 @@ eachother eaddrnotavail easteregghunt + echofile edcc editcontroller editgame @@ -891,6 +893,7 @@ floofcls floooff floop + flushhhhh flycheck fmod fname @@ -1391,6 +1394,7 @@ listvalidconfigs lival llzma + lmap lmerged lmod lmodfile diff --git a/CHANGELOG.md b/CHANGELOG.md index b0e80f79..ca34f7b6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -### 1.7.7 (build 20723, api 7, 2022-08-26) +### 1.7.7 (build 20725, api 7, 2022-09-01) - Added `ba.app.meta.load_exported_classes()` for loading classes discovered by the meta subsystem cleanly in a background thread. - Improved logging of missing playlist game types. - Some ba.Lstr functionality can now be used in background threads. diff --git a/assets/.asset_manifest_public.json b/assets/.asset_manifest_public.json index 306022cc..b447e3ff 100644 --- a/assets/.asset_manifest_public.json +++ b/assets/.asset_manifest_public.json @@ -511,6 +511,7 @@ "ba_data/python/efro/__pycache__/__init__.cpython-310.opt-1.pyc", "ba_data/python/efro/__pycache__/call.cpython-310.opt-1.pyc", "ba_data/python/efro/__pycache__/error.cpython-310.opt-1.pyc", + "ba_data/python/efro/__pycache__/log.cpython-310.opt-1.pyc", "ba_data/python/efro/__pycache__/rpc.cpython-310.opt-1.pyc", "ba_data/python/efro/__pycache__/terminal.cpython-310.opt-1.pyc", "ba_data/python/efro/__pycache__/util.cpython-310.opt-1.pyc", @@ -532,6 +533,7 @@ "ba_data/python/efro/dataclassio/_prep.py", "ba_data/python/efro/dataclassio/extras.py", "ba_data/python/efro/error.py", + "ba_data/python/efro/log.py", "ba_data/python/efro/message/__init__.py", "ba_data/python/efro/message/__pycache__/__init__.cpython-310.opt-1.pyc", "ba_data/python/efro/message/__pycache__/_message.cpython-310.opt-1.pyc", diff --git a/assets/Makefile b/assets/Makefile index bda34d3f..a8898ae9 100644 --- a/assets/Makefile +++ b/assets/Makefile @@ -664,6 +664,7 @@ SCRIPT_TARGETS_PY_PUBLIC_TOOLS = \ build/ba_data/python/efro/dataclassio/_prep.py \ build/ba_data/python/efro/dataclassio/extras.py \ build/ba_data/python/efro/error.py \ + build/ba_data/python/efro/log.py \ build/ba_data/python/efro/message/__init__.py \ build/ba_data/python/efro/message/_message.py \ build/ba_data/python/efro/message/_module.py \ @@ -694,6 +695,7 @@ SCRIPT_TARGETS_PYC_PUBLIC_TOOLS = \ build/ba_data/python/efro/dataclassio/__pycache__/_prep.cpython-310.opt-1.pyc \ build/ba_data/python/efro/dataclassio/__pycache__/extras.cpython-310.opt-1.pyc \ build/ba_data/python/efro/__pycache__/error.cpython-310.opt-1.pyc \ + build/ba_data/python/efro/__pycache__/log.cpython-310.opt-1.pyc \ build/ba_data/python/efro/message/__pycache__/__init__.cpython-310.opt-1.pyc \ build/ba_data/python/efro/message/__pycache__/_message.cpython-310.opt-1.pyc \ build/ba_data/python/efro/message/__pycache__/_module.cpython-310.opt-1.pyc \ diff --git a/ballisticacore-cmake/.idea/dictionaries/ericf.xml b/ballisticacore-cmake/.idea/dictionaries/ericf.xml index b9d0ddbe..b10c4aa3 100644 --- a/ballisticacore-cmake/.idea/dictionaries/ericf.xml +++ b/ballisticacore-cmake/.idea/dictionaries/ericf.xml @@ -188,6 +188,7 @@ cancelbtn capitan cargs + cbegin cbgn cbresults cbtnoffs @@ -362,6 +363,7 @@ dxgi dynamicdata echidna + echofile edef effmult efro @@ -460,6 +462,7 @@ floooff floop flopsy + flushhhhh fname fnode fnumc @@ -703,6 +706,7 @@ linkstoryboards listobj llock + lmap localmodlibs localns lockpath diff --git a/src/ballistica/ballistica.cc b/src/ballistica/ballistica.cc index e781430d..1c7d2d9f 100644 --- a/src/ballistica/ballistica.cc +++ b/src/ballistica/ballistica.cc @@ -21,7 +21,7 @@ namespace ballistica { // These are set automatically via script; don't modify them here. -const int kAppBuildNumber = 20723; +const int kAppBuildNumber = 20725; const char* kAppVersion = "1.7.7"; // Our standalone globals. diff --git a/tools/efro/log.py b/tools/efro/log.py new file mode 100644 index 00000000..a2e78637 --- /dev/null +++ b/tools/efro/log.py @@ -0,0 +1,241 @@ +# Released under the MIT License. See LICENSE for details. +# +"""Logging functionality.""" +from __future__ import annotations + +import sys +import time +import logging +import datetime +import threading +from enum import Enum +from typing import TYPE_CHECKING, Annotated +from dataclasses import dataclass + +from efro.util import utc_now +from efro.dataclassio import ioprepped, IOAttrs, dataclass_to_json +from efro.terminal import TerminalColor + +if TYPE_CHECKING: + from typing import Any, Callable + from pathlib import Path + + +class LogLevel(Enum): + """Severity level for a log entry. + + Note: these are numeric values so they can be compared in severity. + """ + DEBUG = 0 + INFO = 1 + WARNING = 2 + ERROR = 3 + CRITICAL = 4 + + +@ioprepped +@dataclass +class LogEntry: + """Structured log entry.""" + name: Annotated[str, + IOAttrs('n', soft_default='root', store_default=False)] + message: Annotated[str, IOAttrs('m')] + level: Annotated[LogLevel, IOAttrs('l')] + time: Annotated[datetime.datetime, IOAttrs('t')] + + +class StructuredLogHandler(logging.StreamHandler): + """Fancy-pants handler for logging output. + + Writes logs to disk in structured json format and echoes them + to stdout/stderr with pretty colors. + """ + + def __init__(self, + path: str | Path | None, + echofile: Any, + suppress_non_root_debug: bool = False): + super().__init__() + # pylint: disable=consider-using-with + self._file = (None + if path is None else open(path, 'w', encoding='utf-8')) + self._echofile = echofile + self._callbacks: list[Callable[[LogEntry], None]] = [] + self._suppress_non_root_debug = suppress_non_root_debug + + def emit(self, record: logging.LogRecord) -> None: + if (self._suppress_non_root_debug and record.name != 'root' + and record.levelname == 'DEBUG'): + return + msg = self.format(record) + + # Translate Python log levels to our own. + level = { + 'DEBUG': LogLevel.DEBUG, + 'INFO': LogLevel.INFO, + 'WARNING': LogLevel.WARNING, + 'ERROR': LogLevel.ERROR, + 'CRITICAL': LogLevel.CRITICAL + }[record.levelname] + + entry = LogEntry(message=msg, + name=record.name, + level=level, + time=datetime.datetime.fromtimestamp( + record.created, datetime.timezone.utc)) + + for call in self._callbacks: + call(entry) + + # Also route log entries to the echo file (generally stdout) + if self._echofile is not None: + cbegin: str + cend: str + cbegin, cend = { + LogLevel.DEBUG: + (TerminalColor.CYAN.value, TerminalColor.RESET.value), + LogLevel.INFO: ('', ''), + LogLevel.WARNING: + (TerminalColor.YELLOW.value, TerminalColor.RESET.value), + LogLevel.ERROR: + (TerminalColor.RED.value, TerminalColor.RESET.value), + LogLevel.CRITICAL: + (TerminalColor.STRONG_MAGENTA.value + + TerminalColor.BOLD.value + TerminalColor.BG_BLACK.value, + TerminalColor.RESET.value), + }[level] + + self._echofile.write(f'{cbegin}{msg}{cend}\n') + + # Note to self: it sounds like logging wraps calls to us + # in a lock so we shouldn't have to worry about garbled + # json output due to multiple threads writing at once, + # but may be good to find out for sure? + if self._file is not None: + entry_s = dataclass_to_json(entry) + assert '\n' not in entry_s # make sure its a single line + print(entry_s, file=self._file, flush=True) + + def emit_custom(self, name: str, message: str, level: LogLevel) -> None: + """Custom emit call for our stdout/stderr redirection.""" + entry = LogEntry(name=name, + message=message, + level=level, + time=utc_now()) + + # Inform anyone who wants to know about this log's level. + for call in self._callbacks: + call(entry) + + if self._file is not None: + entry_s = dataclass_to_json(entry) + assert '\n' not in entry_s # Make sure its a single line. + print(entry_s, file=self._file, flush=True) + + def add_callback(self, call: Callable[[LogEntry], None]) -> None: + """Add a callback to be run for each added entry.""" + self._callbacks.append(call) + + +class LogRedirect: + """A file-like object for redirecting stdout/stderr to our log.""" + + def __init__(self, name: str, orig_out: Any, + log_handler: StructuredLogHandler, log_level: LogLevel): + self._name = name + self._orig_out = orig_out + self._log_handler = log_handler + self._log_level = log_level + self._chunk = '' + self._chunk_start_time = 0.0 + self._lock = threading.Lock() + + def write(self, s: str) -> None: + """Write something to output.""" + + assert isinstance(s, str) + + # First, ship it off to the original destination. + self._orig_out.write(s) + + # Now add this to our chunk and ship completed chunks + # off to the logger. + # Let's consider a chunk completed when we're passed + # a single '\n' by itself. (print() statement will do + # this at the end by default). + # We may get some false positives/negatives this way + # but it should result in *most* big multi-line print + # statements being wrapped into a single log entry. + # Also, flush with only_old=True can be called periodically + # to dump any pending chunks that don't happen to fit + # this pattern. + with self._lock: + if s == '\n': + self._log_handler.emit_custom(name=self._name, + message=self._chunk, + level=self._log_level) + self._chunk = '' + else: + if self._chunk == '': + self._chunk_start_time = time.time() + self._chunk += s + + def flush(self, only_old: bool = False) -> None: + """Flushhhhh!""" + self._orig_out.flush() + if only_old and time.time() - self._chunk_start_time < 0.5: + return + with self._lock: + if self._chunk != '': + chunk = self._chunk + if chunk.endswith('\n'): + chunk = chunk[:-1] + self._log_handler.emit_custom(name=self._name, + message=chunk, + level=self._log_level) + self._chunk = '' + + +def setup_logging( + log_path: str | Path | None, + level: LogLevel, + suppress_non_root_debug: bool = False) -> StructuredLogHandler: + """Set up our logging environment. + + Returns the custom handler which can be used to fetch information + about logs that have passed through it. (worst log-levels, etc.). + """ + + lmap = { + LogLevel.DEBUG: logging.DEBUG, + LogLevel.INFO: logging.INFO, + LogLevel.WARNING: logging.WARNING, + LogLevel.ERROR: logging.ERROR, + LogLevel.CRITICAL: logging.CRITICAL, + } + + # Wire logger output to go to a structured log file. + # Also echo it to stderr IF we're running in a terminal. + loghandler = StructuredLogHandler( + log_path, + echofile=sys.stderr if sys.stderr.isatty() else None, + suppress_non_root_debug=suppress_non_root_debug) + + logging.basicConfig(level=lmap[level], + format='%(message)s', + handlers=[loghandler]) + + # DISABLING THIS BIT FOR NOW - want to keep things as pure as possible. + if bool(False): + # Now wire Python stdout/stderr output to generate log entries + # in addition to its regular routing. Make sure to do this *after* we + # tell the log-handler to write to stderr, otherwise we get an infinite + # loop. + # NOTE: remember that this won't capture subcommands or other + # non-python stdout/stderr output. + sys.stdout = LogRedirect( # type: ignore + 'stdout', sys.stdout, loghandler, LogLevel.INFO) + sys.stderr = LogRedirect( # type: ignore + 'stderr', sys.stderr, loghandler, LogLevel.INFO) + + return loghandler