Working start/reload/reset/stop from launcher

This commit is contained in:
Griatch 2018-01-14 22:53:36 +01:00
parent ef999362c7
commit 5133034b4b
7 changed files with 136 additions and 56 deletions

View file

@ -192,15 +192,21 @@ class AMPServerClientProtocol(amp.AMPMultiConnectionProtocol):
elif operation == amp.PSYNC: # portal_session_sync elif operation == amp.PSYNC: # portal_session_sync
# force a resync of sessions from the portal side # force a resync of sessions from the portal side
server_sessionhandler.portal_sessions_sync(kwargs.get("sessiondata")) server_sessionhandler.portal_sessions_sync(kwargs.get("sessiondata"))
elif operation == amp.SRELOAD: # server reload elif operation == amp.SRELOAD: # server reload
# shut down in reload mode # shut down in reload mode
server_sessionhandler.all_sessions_portal_sync()
server_sessionhandler.server.shutdown(mode='reload') server_sessionhandler.server.shutdown(mode='reload')
elif operation == amp.SRESET: elif operation == amp.SRESET:
# shut down in reset mode # shut down in reset mode
server_sessionhandler.all_sessions_portal_sync()
server_sessionhandler.server.shutdown(mode='reset') server_sessionhandler.server.shutdown(mode='reset')
elif operation == amp.SSHUTD: # server shutdown elif operation == amp.SSHUTD: # server shutdown
# shutdown in stop mode # shutdown in stop mode
server_sessionhandler.server.shutdown(mode='shutdown') server_sessionhandler.server.shutdown(mode='shutdown')
else: else:
raise Exception("operation %(op)s not recognized." % {'op': operation}) raise Exception("operation %(op)s not recognized." % {'op': operation})
return {} return {}

View file

@ -19,7 +19,7 @@ import shutil
import importlib import importlib
from distutils.version import LooseVersion from distutils.version import LooseVersion
from argparse import ArgumentParser from argparse import ArgumentParser
from subprocess import Popen, check_output, call, CalledProcessError, STDOUT from subprocess import Popen, check_output, call, CalledProcessError, STDOUT, PIPE
try: try:
import cPickle as pickle import cPickle as pickle
@ -88,6 +88,7 @@ SSTART = chr(15) # server start
PSHUTD = chr(16) # portal (+server) shutdown PSHUTD = chr(16) # portal (+server) shutdown
SSHUTD = chr(17) # server-only shutdown SSHUTD = chr(17) # server-only shutdown
PSTATUS = chr(18) # ping server or portal status PSTATUS = chr(18) # ping server or portal status
SRESET = chr(19) # shutdown server in reset mode
# requirements # requirements
PYTHON_MIN = '2.7' PYTHON_MIN = '2.7'
@ -519,13 +520,18 @@ def _get_twistd_cmdline(pprofiler, sprofiler):
Compile the command line for starting a Twisted application using the 'twistd' executable. Compile the command line for starting a Twisted application using the 'twistd' executable.
""" """
portal_cmd = [TWISTED_BINARY, portal_cmd = [TWISTED_BINARY,
"--logfile={}".format(PORTAL_LOGFILE), "--logfile={}".format(PORTAL_LOGFILE),
"--python={}".format(PORTAL_PY_FILE)] "--python={}".format(PORTAL_PY_FILE)]
server_cmd = [TWISTED_BINARY, server_cmd = [TWISTED_BINARY,
"--logfile={}".format(PORTAL_LOGFILE), "--logfile={}".format(SERVER_LOGFILE),
"--python={}".format(PORTAL_PY_FILE)] "--python={}".format(SERVER_PY_FILE)]
if os.name != 'nt':
# PID files only for UNIX
portal_cmd.append("--pidfile={}".format(PORTAL_PIDFILE))
server_cmd.append("--pidfile={}".format(SERVER_PIDFILE))
if pprofiler: if pprofiler:
portal_cmd.extend(["--savestats", portal_cmd.extend(["--savestats",
"--profiler=cprofiler", "--profiler=cprofiler",
@ -534,6 +540,8 @@ def _get_twistd_cmdline(pprofiler, sprofiler):
server_cmd.extend(["--savestats", server_cmd.extend(["--savestats",
"--profiler=cprofiler", "--profiler=cprofiler",
"--profile={}".format(SPROFILER_LOGFILE)]) "--profile={}".format(SPROFILER_LOGFILE)])
return portal_cmd, server_cmd return portal_cmd, server_cmd
@ -552,7 +560,6 @@ def query_status(repeat=False):
reactor.stop() reactor.stop()
def _errback(fail): def _errback(fail):
print("status fail: %s", fail)
pstatus, sstatus = False, False pstatus, sstatus = False, False
print("Portal: {}\nServer: {}".format(wmap[pstatus], wmap[sstatus])) print("Portal: {}\nServer: {}".format(wmap[pstatus], wmap[sstatus]))
reactor.stop() reactor.stop()
@ -591,7 +598,7 @@ def wait_for_status(portal_running=True, server_running=True, callback=None, err
if errback: if errback:
errback(prun, srun) errback(prun, srun)
else: else:
print("Timeout.") print("Connection to Evennia timed out. Try again.")
reactor.stop() reactor.stop()
else: else:
reactor.callLater(rate, wait_for_status, reactor.callLater(rate, wait_for_status,
@ -613,7 +620,7 @@ def wait_for_status(portal_running=True, server_running=True, callback=None, err
if errback: if errback:
errback(portal_running, server_running) errback(portal_running, server_running)
else: else:
print("Timeout.") print("Connection to Evennia timed out. Try again.")
reactor.stop() reactor.stop()
else: else:
reactor.callLater(rate, wait_for_status, reactor.callLater(rate, wait_for_status,
@ -622,14 +629,13 @@ def wait_for_status(portal_running=True, server_running=True, callback=None, err
return send_instruction(PSTATUS, None, _callback, _errback) return send_instruction(PSTATUS, None, _callback, _errback)
# ------------------------------------------------------------ # ------------------------------------------------------------
# #
# Operational functions # Operational functions
# #
# ------------------------------------------------------------ # ------------------------------------------------------------
def start_evennia(pprofiler=False, sprofiler=False): def start_evennia(pprofiler=False, sprofiler=False):
""" """
This will start Evennia anew by launching the Evennia Portal (which in turn This will start Evennia anew by launching the Evennia Portal (which in turn
@ -638,8 +644,12 @@ def start_evennia(pprofiler=False, sprofiler=False):
""" """
portal_cmd, server_cmd = _get_twistd_cmdline(pprofiler, sprofiler) portal_cmd, server_cmd = _get_twistd_cmdline(pprofiler, sprofiler)
def _fail(fail):
print(fail)
reactor.stop()
def _server_started(*args): def _server_started(*args):
print("... Server started.\nEvennia running.", args) print("... Server started.\nEvennia running.")
reactor.stop() reactor.stop()
def _portal_started(*args): def _portal_started(*args):
@ -653,21 +663,22 @@ def start_evennia(pprofiler=False, sprofiler=False):
reactor.stop() reactor.stop()
else: else:
print("Server starting {}...".format("(under cProfile)" if pprofiler else "")) print("Server starting {}...".format("(under cProfile)" if pprofiler else ""))
send_instruction(SSTART, server_cmd, _server_started) send_instruction(SSTART, server_cmd, _server_started, _fail)
def _portal_not_running(fail): def _portal_not_running(fail):
print("Portal starting {}...".format("(under cProfile)" if sprofiler else "")) print("Portal starting {}...".format("(under cProfile)" if sprofiler else ""))
try: try:
Popen(portal_cmd, env=getenv()) Popen(portal_cmd, env=getenv(), bufsize=-1)
except Exception as e: except Exception as e:
print(PROCESS_ERROR.format(component="Portal", traceback=e)) print(PROCESS_ERROR.format(component="Portal", traceback=e))
reactor.stop()
wait_for_status(True, None, _portal_started) wait_for_status(True, None, _portal_started)
send_instruction(PSTATUS, None, _portal_running, _portal_not_running) send_instruction(PSTATUS, None, _portal_running, _portal_not_running)
reactor.run() reactor.run()
def reload_evennia(sprofiler=False): def reload_evennia(sprofiler=False, reset=False):
""" """
This will instruct the Portal to reboot the Server component. This will instruct the Portal to reboot the Server component.
@ -675,18 +686,23 @@ def reload_evennia(sprofiler=False):
_, server_cmd = _get_twistd_cmdline(False, sprofiler) _, server_cmd = _get_twistd_cmdline(False, sprofiler)
def _server_restarted(*args): def _server_restarted(*args):
print("... Server re-started.", args) print("... Server re-started.")
reactor.stop() reactor.stop()
def _server_reloaded(*args): def _server_reloaded(*args):
print("... Server reloaded.", args) print("... Server {}.".format("reset" if reset else "reloaded"))
reactor.stop() reactor.stop()
def _server_not_running(*args):
send_instruction(SSTART, server_cmd)
wait_for_status(True, True, _server_reloaded)
def _portal_running(response): def _portal_running(response):
_, srun, _, _ = _parse_status(response) _, srun, _, _ = _parse_status(response)
if srun: if srun:
print("Server reloading ...") print("Server {}...".format("resetting" if reset else "reloading"))
send_instruction(SRELOAD, server_cmd, _server_reloaded) send_instruction(SRESET if reset else SRELOAD, server_cmd)
wait_for_status(True, False, _server_not_running)
else: else:
print("Server down. Re-starting ...") print("Server down. Re-starting ...")
send_instruction(SSTART, server_cmd, _server_restarted) send_instruction(SSTART, server_cmd, _server_restarted)
@ -710,7 +726,7 @@ def stop_evennia():
reactor.stop() reactor.stop()
def _server_stopped(*args): def _server_stopped(*args):
print("... Server stopped.\nStopping Portal ...", args) print("... Server stopped.\nStopping Portal ...")
send_instruction(PSHUTD, {}) send_instruction(PSHUTD, {})
wait_for_status(False, None, _portal_stopped) wait_for_status(False, None, _portal_stopped)
@ -718,7 +734,8 @@ def stop_evennia():
prun, srun, ppid, spid = _parse_status(response) prun, srun, ppid, spid = _parse_status(response)
if srun: if srun:
print("Server stopping ...") print("Server stopping ...")
send_instruction(SSHUTD, {}, _server_stopped) send_instruction(SSHUTD, {})
wait_for_status(True, False, _server_stopped)
else: else:
print("Server already stopped.\nStopping Portal ...") print("Server already stopped.\nStopping Portal ...")
send_instruction(PSHUTD, {}) send_instruction(PSHUTD, {})
@ -726,6 +743,7 @@ def stop_evennia():
def _portal_not_running(fail): def _portal_not_running(fail):
print("Evennia is not running.") print("Evennia is not running.")
reactor.stop()
send_instruction(PSTATUS, None, _portal_running, _portal_not_running) send_instruction(PSTATUS, None, _portal_running, _portal_not_running)
reactor.run() reactor.run()
@ -741,8 +759,8 @@ def stop_server_only():
reactor.stop() reactor.stop()
def _portal_running(response): def _portal_running(response):
_, server_running = [stat == 'RUNNING' for stat in response['status'].split("|")] _, srun, _, _ = _parse_status(response)
if server_running: if srun:
print("Server stopping ...") print("Server stopping ...")
send_instruction(SSHUTD, {}) send_instruction(SSHUTD, {})
wait_for_status(True, False, _server_stopped) wait_for_status(True, False, _server_stopped)
@ -1239,7 +1257,7 @@ def init_game_directory(path, check_db=True):
AMP_INTERFACE = settings.AMP_INTERFACE AMP_INTERFACE = settings.AMP_INTERFACE
SERVER_PY_FILE = os.path.join(EVENNIA_LIB, "server", "server.py") SERVER_PY_FILE = os.path.join(EVENNIA_LIB, "server", "server.py")
PORTAL_PY_FILE = os.path.join(EVENNIA_LIB, "portal", "portal", "portal.py") PORTAL_PY_FILE = os.path.join(EVENNIA_LIB, "server", "portal", "portal.py")
SERVER_PIDFILE = os.path.join(GAMEDIR, SERVERDIR, "server.pid") SERVER_PIDFILE = os.path.join(GAMEDIR, SERVERDIR, "server.pid")
PORTAL_PIDFILE = os.path.join(GAMEDIR, SERVERDIR, "portal.pid") PORTAL_PIDFILE = os.path.join(GAMEDIR, SERVERDIR, "portal.pid")
@ -1602,9 +1620,6 @@ def main():
"service", metavar="component", nargs='?', default="all", "service", metavar="component", nargs='?', default="all",
help=("Which component to operate on: " help=("Which component to operate on: "
"'server', 'portal' or 'all' (default if not set).")) "'server', 'portal' or 'all' (default if not set)."))
parser.add_argument(
"--status", action='store_true', dest='get_status',
default=None, help='Get current server status.')
parser.epilog = ( parser.epilog = (
"Common usage: evennia start|stop|reload. Django-admin database commands:" "Common usage: evennia start|stop|reload. Django-admin database commands:"
"evennia migration|flush|shell|dbshell (see the django documentation for more " "evennia migration|flush|shell|dbshell (see the django documentation for more "
@ -1656,11 +1671,6 @@ def main():
print(ERROR_INITSETTINGS) print(ERROR_INITSETTINGS)
sys.exit() sys.exit()
if args.get_status:
init_game_directory(CURRENT_DIR, check_db=True)
query_status()
sys.exit()
if args.dummyrunner: if args.dummyrunner:
# launch the dummy runner # launch the dummy runner
init_game_directory(CURRENT_DIR, check_db=True) init_game_directory(CURRENT_DIR, check_db=True)
@ -1673,13 +1683,17 @@ def main():
# launch menu for operation # launch menu for operation
init_game_directory(CURRENT_DIR, check_db=True) init_game_directory(CURRENT_DIR, check_db=True)
run_menu() run_menu()
elif option in ('sstart', 'sreload', 'sstop', 'ssstop', 'start', 'reload', 'stop'): elif option in ('status', 'sstart', 'sreload', 'sreset', 'sstop', 'ssstop', 'start', 'reload', 'stop'):
# operate the server directly # operate the server directly
init_game_directory(CURRENT_DIR, check_db=True) init_game_directory(CURRENT_DIR, check_db=True)
if option == "status":
query_status()
if option == "sstart": if option == "sstart":
start_evennia(False, args.profiler) start_evennia(False, args.profiler)
elif option == 'sreload': elif option == 'sreload':
reload_evennia(args.profiler) reload_evennia(args.profiler)
elif option == 'sreset':
reload_evennia(args.profiler, reset=True)
elif option == 'sstop': elif option == 'sstop':
stop_evennia() stop_evennia()
elif option == 'ssstop': elif option == 'ssstop':

View file

@ -346,6 +346,7 @@ def main():
del portal_argv[-2] del portal_argv[-2]
# Start processes # Start processes
print("server_argv:", server_argv, portal_argv)
start_services(server_argv, portal_argv, doexit=args.doexit) start_services(server_argv, portal_argv, doexit=args.doexit)

View file

@ -89,8 +89,8 @@ def catch_traceback(func):
if not _LOGGER: if not _LOGGER:
from evennia.utils import logger as _LOGGER from evennia.utils import logger as _LOGGER
_LOGGER.log_trace() _LOGGER.log_trace()
print("error", err)
raise # make sure the error is visible on the other side of the connection too raise # make sure the error is visible on the other side of the connection too
print(err)
return decorator return decorator

View file

@ -8,7 +8,7 @@ import os
import sys import sys
from twisted.internet import protocol from twisted.internet import protocol
from evennia.server.portal import amp from evennia.server.portal import amp
from subprocess import Popen from subprocess import Popen, STDOUT, PIPE
from evennia.utils import logger from evennia.utils import logger
@ -48,6 +48,8 @@ class AMPServerFactory(protocol.ServerFactory):
self.portal = portal self.portal = portal
self.protocol = AMPServerProtocol self.protocol = AMPServerProtocol
self.broadcasts = [] self.broadcasts = []
self.server_connection = None
self.disconnect_callbacks = {}
def buildProtocol(self, addr): def buildProtocol(self, addr):
""" """
@ -80,12 +82,45 @@ class AMPServerProtocol(amp.AMPMultiConnectionProtocol):
""" """
# start the Server # start the Server
process = Popen(server_twistd_cmd, env=getenv()) try:
# store the pid for future reference process = Popen(server_twistd_cmd, env=getenv(), bufsize=-1, stdout=PIPE, stderr=STDOUT)
except Exception:
self.factory.portal.server_process_id = None
logger.log_trace()
return 0
# there is a short window before the server logger is up where we must
# catch the stdout of the Server or eventual tracebacks will be lost.
with process.stdout as out:
logger.log_server(out.read())
# store the pid and launch argument for future reference
self.factory.portal.server_process_id = process.pid self.factory.portal.server_process_id = process.pid
self.factory.portal.server_twistd_cmd = server_twistd_cmd self.factory.portal.server_twistd_cmd = server_twistd_cmd
return process.pid return process.pid
def connectionLost(self, reason):
"""
Set up a simple callback mechanism to let the amp-server wait for a connection to close.
"""
callback, args, kwargs = self.factory.disconnect_callbacks.pop(self, (None, None, None))
if callback:
try:
callback(*args, **kwargs)
except Exception:
logger.log_trace()
def wait_for_disconnect(self, callback, *args, **kwargs):
"""
Add a callback for when this connection is lost.
Args:
callback (callable): Will be called with *args, **kwargs
once this protocol is disconnected.
"""
self.factory.disconnect_callbacks[self] = (callback, args, kwargs)
def stop_server(self, mode='shutdown'): def stop_server(self, mode='shutdown'):
""" """
Shut down server in one or more modes. Shut down server in one or more modes.
@ -95,11 +130,11 @@ class AMPServerProtocol(amp.AMPMultiConnectionProtocol):
""" """
if mode == 'reload': if mode == 'reload':
self.send_AdminPortal2Server(amp.DUMMYSESSION, amp.SRELOAD) self.send_AdminPortal2Server(amp.DUMMYSESSION, operation=amp.SRELOAD)
if mode == 'reset': elif mode == 'reset':
self.send_AdminPortal2Server(amp.DUMMYSESSION, amp.SRESET) self.send_AdminPortal2Server(amp.DUMMYSESSION, operation=amp.SRESET)
if mode == 'shutdown': elif mode == 'shutdown':
self.send_AdminPortal2Server(amp.DUMMYSESSION, amp.SSHUTD) self.send_AdminPortal2Server(amp.DUMMYSESSION, operation=amp.SSHUTD)
# sending amp data # sending amp data
@ -148,8 +183,8 @@ class AMPServerProtocol(amp.AMPMultiConnectionProtocol):
""" """
# check if the server is connected # check if the server is connected
server_connected = any(1 for prtcl in self.factory.broadcasts server_connected = (self.factory.server_connection and
if prtcl is not self and prtcl.transport.connected) self.factory.server_connection.transport.connected)
server_pid = self.factory.portal.server_process_id server_pid = self.factory.portal.server_process_id
portal_pid = os.getpid() portal_pid = os.getpid()
@ -180,14 +215,14 @@ class AMPServerProtocol(amp.AMPMultiConnectionProtocol):
def _retval(success, txt): def _retval(success, txt):
return {"result": amp.dumps((success, txt))} return {"result": amp.dumps((success, txt))}
server_connected = any(1 for prtcl in self.factory.broadcasts server_connected = (self.factory.server_connection and
if prtcl is not self and prtcl.transport.connected) self.factory.server_connection.transport.connected)
server_pid = self.factory.portal.server_process_id server_pid = self.factory.portal.server_process_id
logger.log_msg("AMP SERVER operation == %s received" % (ord(operation))) logger.log_msg("AMP SERVER operation == %s received" % (ord(operation)))
logger.log_msg("AMP SERVER arguments: %s" % (amp.loads(arguments))) logger.log_msg("AMP SERVER arguments: %s" % (amp.loads(arguments)))
if operation == amp.SSTART: # portal start if operation == amp.SSTART: # portal start #15
# first, check if server is already running # first, check if server is already running
if server_connected: if server_connected:
return _retval(False, return _retval(False,
@ -195,27 +230,36 @@ class AMPServerProtocol(amp.AMPMultiConnectionProtocol):
else: else:
spid = self.start_server(amp.loads(arguments)) spid = self.start_server(amp.loads(arguments))
return _retval(True, "Server started with PID {spid}.".format(spid=spid)) return _retval(True, "Server started with PID {spid}.".format(spid=spid))
elif operation == amp.SRELOAD: # reload server
elif operation == amp.SRELOAD: # reload server #14
if server_connected: if server_connected:
self.stop(mode='reload') # don't restart until the server connection goes down
spid = self.start_server(amp.loads(arguments)) self.stop_server(mode='reload')
return _retval(True, "Server restarted with PID {spid}.".format(spid=spid))
else: else:
spid = self.start_server(amp.loads(arguments)) spid = self.start_server(amp.loads(arguments))
return _retval(True, "Server started with PID {spid}.".format(spid=spid)) return _retval(True, "Server started with PID {spid}.".format(spid=spid))
elif operation == amp.SRESET: # reload server
elif operation == amp.SRESET: # reload server #19
if server_connected: if server_connected:
self.stop_server(mode='reset') self.stop_server(mode='reset')
spid = self.start_server(amp.loads(arguments))
return _retval(True, "Server restarted with PID {spid}.".format(spid=spid)) return _retval(True, "Server restarted with PID {spid}.".format(spid=spid))
else: else:
spid = self.start_server(amp.loads(arguments)) spid = self.start_server(amp.loads(arguments))
return _retval(True, "Server started with PID {spid}.".format(spid=spid)) return _retval(True, "Server started with PID {spid}.".format(spid=spid))
elif operation == amp.PSHUTD: # portal + server shutdown
elif operation == amp.SSHUTD: # server-only shutdown #17
if server_connected:
self.stop_server(mode='shutdown')
return _retval(True, "Server stopped.")
else:
return _retval(False, "Server not running")
elif operation == amp.PSHUTD: # portal + server shutdown #16
if server_connected: if server_connected:
self.stop_server(mode='shutdown') self.stop_server(mode='shutdown')
return _retval(True, "Server stopped.") return _retval(True, "Server stopped.")
self.factory.portal.shutdown(restart=False) self.factory.portal.shutdown(restart=False)
else: else:
raise Exception("operation %(op)s not recognized." % {'op': operation}) raise Exception("operation %(op)s not recognized." % {'op': operation})
# fallback # fallback
@ -254,6 +298,9 @@ class AMPServerProtocol(amp.AMPMultiConnectionProtocol):
operation = kwargs.pop("operation") operation = kwargs.pop("operation")
portal_sessionhandler = self.factory.portal.sessions portal_sessionhandler = self.factory.portal.sessions
# store this transport since we know it comes from the Server
self.factory.server_connection = self
if operation == amp.SLOGIN: # server_session_login if operation == amp.SLOGIN: # server_session_login
# a session has authenticated; sync it. # a session has authenticated; sync it.
session = portal_sessionhandler.get(sessid) session = portal_sessionhandler.get(sessid)
@ -271,12 +318,14 @@ class AMPServerProtocol(amp.AMPMultiConnectionProtocol):
portal_sessionhandler.server_disconnect_all(reason=kwargs.get("reason")) portal_sessionhandler.server_disconnect_all(reason=kwargs.get("reason"))
elif operation == amp.SRELOAD: # server reload elif operation == amp.SRELOAD: # server reload
self.stop_server(mode='reload') self.factory.server_connection.wait_for_disconnect(
self.start(self.factory.portal.server_twisted_cmd) self.start_server, self.factory.portal.server_twisted_cmd)
self.stop_server(mode='reload')
elif operation == amp.SRESET: # server reset elif operation == amp.SRESET: # server reset
self.stop_server(mode='reset') self.factory.server_connection.wait_for_disconnect(
self.start(self.factory.portal.server_twisted_cmd) self.start_server, self.factory.portal.server_twisted_cmd)
self.stop_server(mode='reset')
elif operation == amp.SSHUTD: # server-only shutdown elif operation == amp.SSHUTD: # server-only shutdown
self.stop_server(mode='shutdown') self.stop_server(mode='shutdown')

View file

@ -366,6 +366,7 @@ class Evennia(object):
once - in both cases the reactor is once - in both cases the reactor is
dead/stopping already. dead/stopping already.
""" """
print("server.shutdown mode=", mode)
if _reactor_stopping and hasattr(self, "shutdown_complete"): if _reactor_stopping and hasattr(self, "shutdown_complete"):
# this means we have already passed through this method # this means we have already passed through this method
# once; we don't need to run the shutdown procedure again. # once; we don't need to run the shutdown procedure again.

View file

@ -124,6 +124,15 @@ def log_err(errmsg):
log_errmsg = log_err log_errmsg = log_err
def log_server(servermsg):
try:
servermsg = str(servermsg)
except Exception as e:
servermsg = str(e)
for line in servermsg.splitlines():
log_msg('[SRV] %s' % line)
def log_warn(warnmsg): def log_warn(warnmsg):
""" """
Prints/logs any warnings that aren't critical but should be noted. Prints/logs any warnings that aren't critical but should be noted.