mirror of
https://github.com/imayushsaini/Bombsquad-Ballistica-Modded-Server.git
synced 2025-10-20 00:00:39 +00:00
configurable error logging
This commit is contained in:
parent
61e949f909
commit
72583e1ea9
1 changed files with 85 additions and 72 deletions
157
bombsquad_server
157
bombsquad_server
|
|
@ -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(
|
||||||
|
|
@ -704,7 +714,7 @@ class ServerManagerApp:
|
||||||
f' ba._servermode._cmd({val})\n').encode()
|
f' ba._servermode._cmd({val})\n').encode()
|
||||||
self._subprocess.stdin.write(execcode)
|
self._subprocess.stdin.write(execcode)
|
||||||
self._subprocess.stdin.flush()
|
self._subprocess.stdin.flush()
|
||||||
|
|
||||||
def _run_subprocess_until_exit(self) -> None:
|
def _run_subprocess_until_exit(self) -> None:
|
||||||
if self._subprocess is None:
|
if self._subprocess is None:
|
||||||
return
|
return
|
||||||
|
|
@ -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:
|
||||||
|
|
@ -750,7 +761,7 @@ class ServerManagerApp:
|
||||||
# If they want to force-kill our subprocess, simply exit this
|
# If they want to force-kill our subprocess, simply exit this
|
||||||
# loop; the cleanup code will kill the process if its still
|
# loop; the cleanup code will kill the process if its still
|
||||||
# alive.
|
# alive.
|
||||||
|
|
||||||
if (self._subprocess_force_kill_time is not None
|
if (self._subprocess_force_kill_time is not None
|
||||||
and time.time() > self._subprocess_force_kill_time):
|
and time.time() > self._subprocess_force_kill_time):
|
||||||
print(
|
print(
|
||||||
|
|
@ -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()
|
||||||
Loading…
Add table
Add a link
Reference in a new issue