From 72583e1ea9da90ca04e351763eef1ff40eed271a Mon Sep 17 00:00:00 2001 From: Ayush Saini <36878972+imayushsaini@users.noreply.github.com> Date: Sun, 8 May 2022 22:26:44 +0530 Subject: [PATCH] configurable error logging --- bombsquad_server | 157 +++++++++++++++++++++++++---------------------- 1 file changed, 85 insertions(+), 72 deletions(-) diff --git a/bombsquad_server b/bombsquad_server index d92e1f0..91d2901 100644 --- a/bombsquad_server +++ b/bombsquad_server @@ -16,6 +16,10 @@ from pathlib import Path from threading import Lock, Thread, current_thread from typing import TYPE_CHECKING from nbstreamreader import NonBlockingStreamReader as NBSR + +ERROR_LOGGING=False + + # We make use of the bacommon and efro packages as well as site-packages # included with our bundled Ballistica dist, so we need to add those paths # before we import them. @@ -36,6 +40,7 @@ if TYPE_CHECKING: VERSION_STR = '1.3' + # Version history: # 1.3.1 # Windows binary is now named BallisticaCoreHeadless.exe @@ -100,7 +105,7 @@ class ServerManagerApp: self._subprocess_sent_unclean_exit = False self._subprocess_thread: Optional[Thread] = None self._subprocess_exited_cleanly: Optional[bool] = None - self.nbsr=None + self.nbsr = None # This may override the above defaults. self._parse_command_line_args() @@ -311,7 +316,7 @@ class ServerManagerApp: # the grace period, bring down the hammer. if immediate: self._subprocess_force_kill_time = ( - time.time() + self.IMMEDIATE_SHUTDOWN_TIME_LIMIT) + time.time() + self.IMMEDIATE_SHUTDOWN_TIME_LIMIT) def shutdown(self, immediate: bool = True) -> None: """Shut down the server subprocess and exit the wrapper. @@ -332,7 +337,7 @@ class ServerManagerApp: # the grace period, bring down the hammer. if immediate: self._subprocess_force_kill_time = ( - time.time() + self.IMMEDIATE_SHUTDOWN_TIME_LIMIT) + time.time() + self.IMMEDIATE_SHUTDOWN_TIME_LIMIT) def _parse_command_line_args(self) -> None: """Parse command line args.""" @@ -403,50 +408,50 @@ class ServerManagerApp: """Print app help.""" filename = os.path.basename(__file__) out = ( - f'{Clr.BLD}{filename} usage:{Clr.RST}\n' + cls._par( - 'This script handles configuring, launching, re-launching,' - ' and otherwise managing BallisticaCore operating' - ' in server mode. It can be run with no arguments, but' - ' accepts the following optional ones:') + f'\n' - f'{Clr.BLD}--help:{Clr.RST}\n' - f' Show this help.\n' - f'\n' - f'{Clr.BLD}--config [path]{Clr.RST}\n' + cls._par( - 'Set the config file read by the server script. The config' - ' file contains most options for what kind of game to host.' - ' It should be in yaml format. Note that yaml is backwards' - ' compatible with json so you can just write json if you' - ' want to. If not specified, the script will look for a' - ' file named \'config.yaml\' in the same directory as the' - ' script.') + '\n' - f'{Clr.BLD}--root [path]{Clr.RST}\n' + cls._par( - 'Set the ballistica root directory. This is where the server' - ' binary will read and write its caches, state files,' - ' downloaded assets to, etc. It needs to be a writable' - ' directory. If not specified, the script will use the' - ' \'dist/ba_root\' directory relative to itself.') + '\n' - f'{Clr.BLD}--interactive{Clr.RST}\n' - f'{Clr.BLD}--noninteractive{Clr.RST}\n' + cls._par( - 'Specify whether the script should run interactively.' - ' In interactive mode, the script creates a Python interpreter' - ' and reads commands from stdin, allowing for live interaction' - ' with the server. The server script will then exit when ' - 'end-of-file is reached in stdin. Noninteractive mode creates' - ' no interpreter and is more suited to being run in automated' - ' scenarios. By default, interactive mode will be used if' - ' a terminal is detected and noninteractive mode otherwise.') + - '\n' - f'{Clr.BLD}--no-auto-restart{Clr.RST}\n' + - cls._par('Auto-restart is enabled by default, which means the' - ' server manager will restart the server binary whenever' - ' it exits (even when uncleanly). Disabling auto-restart' - ' will cause the server manager to instead exit after a' - ' single run and also to return error codes if the' - ' server binary did so.') + '\n' - f'{Clr.BLD}--no-config-auto-restart{Clr.RST}\n' + cls._par( - 'By default, when auto-restart is enabled, the server binary' - ' will be automatically restarted if changes to the server' - ' config file are detected. This disables that behavior.')) + f'{Clr.BLD}{filename} usage:{Clr.RST}\n' + cls._par( + 'This script handles configuring, launching, re-launching,' + ' and otherwise managing BallisticaCore operating' + ' in server mode. It can be run with no arguments, but' + ' accepts the following optional ones:') + f'\n' + f'{Clr.BLD}--help:{Clr.RST}\n' + f' Show this help.\n' + f'\n' + f'{Clr.BLD}--config [path]{Clr.RST}\n' + cls._par( + 'Set the config file read by the server script. The config' + ' file contains most options for what kind of game to host.' + ' It should be in yaml format. Note that yaml is backwards' + ' compatible with json so you can just write json if you' + ' want to. If not specified, the script will look for a' + ' file named \'config.yaml\' in the same directory as the' + ' script.') + '\n' + f'{Clr.BLD}--root [path]{Clr.RST}\n' + cls._par( + 'Set the ballistica root directory. This is where the server' + ' binary will read and write its caches, state files,' + ' downloaded assets to, etc. It needs to be a writable' + ' directory. If not specified, the script will use the' + ' \'dist/ba_root\' directory relative to itself.') + '\n' + f'{Clr.BLD}--interactive{Clr.RST}\n' + f'{Clr.BLD}--noninteractive{Clr.RST}\n' + cls._par( + 'Specify whether the script should run interactively.' + ' In interactive mode, the script creates a Python interpreter' + ' and reads commands from stdin, allowing for live interaction' + ' with the server. The server script will then exit when ' + 'end-of-file is reached in stdin. Noninteractive mode creates' + ' no interpreter and is more suited to being run in automated' + ' scenarios. By default, interactive mode will be used if' + ' a terminal is detected and noninteractive mode otherwise.') + + '\n' + f'{Clr.BLD}--no-auto-restart{Clr.RST}\n' + + cls._par('Auto-restart is enabled by default, which means the' + ' server manager will restart the server binary whenever' + ' it exits (even when uncleanly). Disabling auto-restart' + ' will cause the server manager to instead exit after a' + ' single run and also to return error codes if the' + ' server binary did so.') + '\n' + f'{Clr.BLD}--no-config-auto-restart{Clr.RST}\n' + cls._par( + 'By default, when auto-restart is enabled, the server binary' + ' will be automatically restarted if changes to the server' + ' config file are detected. This disables that behavior.')) print(out) def load_config(self, strict: bool, print_confirmation: bool) -> None: @@ -479,8 +484,8 @@ class ServerManagerApp: print( f'{Clr.CYN}Please correct the error.' f' Will re-attempt load in {retry_seconds}' - f' seconds. (attempt {trynum+1} of' - f' {maxtries-1}).{Clr.RST}', + f' seconds. (attempt {trynum + 1} of' + f' {maxtries - 1}).{Clr.RST}', flush=True) for _j in range(retry_seconds): @@ -584,17 +589,22 @@ class ServerManagerApp: # Launch! try: - - - self._subprocess = subprocess.Popen( - [binary_name, '-cfgdir', self._ba_root_path ], + if ERROR_LOGGING: + self._subprocess = subprocess.Popen( + [binary_name, '-cfgdir', self._ba_root_path], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd='dist') - - self.nbsr=NBSR(self._subprocess.stdout) - self.nbsrerr=NBSR(self._subprocess.stderr) + + self.nbsr = NBSR(self._subprocess.stdout) + self.nbsrerr = NBSR(self._subprocess.stderr) + else: + self._subprocess = subprocess.Popen( + [binary_name, '-cfgdir', self._ba_root_path], + stdin=subprocess.PIPE, + cwd='dist') + except Exception as exc: self._subprocess_exited_cleanly = False print( @@ -704,7 +714,7 @@ class ServerManagerApp: f' ba._servermode._cmd({val})\n').encode() self._subprocess.stdin.write(execcode) self._subprocess.stdin.flush() - + def _run_subprocess_until_exit(self) -> None: if self._subprocess is None: return @@ -724,14 +734,15 @@ class ServerManagerApp: break # output=self._subprocess.stdout.readline() # print(output) - out=self.nbsr.readline(0.1) - out2=self.nbsrerr.readline(0.1) - if out: - sys.stdout.write(out.decode("utf-8") ) - _thread.start_new_thread(dump_logs,(out.decode("utf-8"),)) - if out2: - sys.stdout.write(out2.decode("utf-8") ) - _thread.start_new_thread(dump_logs,(out2.decode("utf-8"),)) + if ERROR_LOGGING: + out = self.nbsr.readline(0.1) + out2 = self.nbsrerr.readline(0.1) + if out: + sys.stdout.write(out.decode("utf-8")) + _thread.start_new_thread(dump_logs, (out.decode("utf-8"),)) + if out2: + sys.stdout.write(out2.decode("utf-8")) + _thread.start_new_thread(dump_logs, (out2.decode("utf-8"),)) # Pass along any commands to our process. with self._subprocess_commands_lock: for incmd in self._subprocess_commands: @@ -750,7 +761,7 @@ class ServerManagerApp: # If they want to force-kill our subprocess, simply exit this # loop; the cleanup code will kill the process if its still # alive. - + if (self._subprocess_force_kill_time is not None and time.time() > self._subprocess_force_kill_time): print( @@ -763,7 +774,6 @@ class ServerManagerApp: # Watch for the server process exiting.. code: Optional[int] = self._subprocess.poll() if code is not None: - clr = Clr.CYN if code == 0 else Clr.RED print( f'{clr}Server subprocess exited' @@ -867,7 +877,7 @@ class ServerManagerApp: try: self._subprocess.wait(timeout=10) self._subprocess_exited_cleanly = ( - self._subprocess.returncode == 0) + self._subprocess.returncode == 0) except subprocess.TimeoutExpired: self._subprocess_exited_cleanly = False self._subprocess.kill() @@ -883,15 +893,18 @@ def main() -> None: # Any others will bubble up and give us the usual mess. exc.pretty_print() sys.exit(1) + + def dump_logs(msg): if os.path.isfile('logs.log'): - size=os.path.getsize('logs.log') - + size = os.path.getsize('logs.log') + if size > 2000000: os.remove('logs.log') - with open("logs.log","a") as f: + with open("logs.log", "a") as f: f.write(msg) + if __name__ == '__main__': - main() + main() \ No newline at end of file