configurable error logging

This commit is contained in:
Ayush Saini 2022-05-08 22:26:44 +05:30
parent 61e949f909
commit 72583e1ea9

View file

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