diff --git a/evennia/commands/cmdhandler.py b/evennia/commands/cmdhandler.py index d89452c3a9..b4945d5520 100644 --- a/evennia/commands/cmdhandler.py +++ b/evennia/commands/cmdhandler.py @@ -35,6 +35,7 @@ command line. The processing of a command works as follows: from collections import defaultdict from weakref import WeakValueDictionary +from traceback import format_exc from copy import copy from twisted.internet.defer import inlineCallbacks, returnValue from django.conf import settings @@ -44,6 +45,8 @@ from evennia.utils.utils import string_suggestions, to_unicode from django.utils.translation import ugettext as _ +_IN_GAME_ERRORS = settings.IN_GAME_ERRORS + __all__ = ("cmdhandler",) _GA = object.__getattribute__ _CMDSET_MERGE_CACHE = WeakValueDictionary() @@ -76,46 +79,76 @@ CMD_LOGINSTART = "__unloggedin_look_command" # Function for handling multiple command matches. _SEARCH_AT_RESULT = utils.variable_from_module(*settings.SEARCH_AT_RESULT.rsplit('.', 1)) -# Output strings +# Output strings. The first is the IN_GAME_ERRORS return, the second +# is the normal "production message to echo to the player. -_ERROR_UNTRAPPED = """ -An untrapped error occurred. Please file a bug report detailing the -steps to reproduce. Server log time stamp is '{timestamp}'. +_ERROR_UNTRAPPED = ( """ - -_ERROR_CMDSETS = """ -A cmdset merger error occurred. Please file a bug report detailing the -steps to reproduce. Server log time stamp is '{timestamp}'. +An untrapped error occurred. +""", """ +An untrapped error occurred. Please file a bug report detailing the steps to reproduce. +""") -_ERROR_NOCMDSETS = """ +_ERROR_CMDSETS = ( +""" +A cmdset merger-error occurred. This is often due to a syntax +error in one of the cmdsets to merge. +""", +""" +A cmdset merger-error occurred. Please file a bug report detailing the +steps to reproduce. +""") + +_ERROR_NOCMDSETS = ( +""" +No command sets found! This is a critical bug that can have +multiple causes. +""", +""" No command sets found! This is a sign of a critical bug. If disconnecting/reconnecting doesn't" solve the problem, try to contact -the server admin through" some other means for assistance. Server log -time stamp is '{timestamp}'. -""" +the server admin through" some other means for assistance. +""") -_ERROR_CMDHANDLER = """ -A command handler bug occurred. Please file a bug report with the -Evennia project. Include the relvant traceback from the server log at -time stamp '{timestamp}'. +_ERROR_CMDHANDLER = ( """ +A command handler bug occurred. If this is not due to a local change, +please file a bug report with the Evennia project, including the +traceback and steps to reproduce. +""", +""" +A command handler bug occurred. Please notify staff - they should +likely file a bug report with the Evennia project. +""") _ERROR_RECURSION_LIMIT = "Command recursion limit ({recursion_limit}) " \ "reached for '{raw_string}' ({cmdclass})." -def _msg_err(receiver, string): +def _msg_err(receiver, stringtuple): """ Helper function for returning an error to the caller. Args: receiver (Object): object to get the error message. - string (str): string which will be shown to the user. + stringtuple (tuple): tuple with two strings - one for the + _IN_GAME_ERRORS mode (with the traceback) and one with the + production string (with a timestamp) to be shown to the user. """ - receiver.msg(string.format(timestamp=logger.timeformat()).strip()) - + string = "{traceback}\n{errmsg}\n(Traceback was logged {timestamp})." + timestamp = logger.timeformat() + tracestring = format_exc() + #logger.log_trace() + if _IN_GAME_ERRORS: + receiver.msg(string.format(traceback=tracestring, + errmsg=stringtuple[0].strip(), + timestamp=timestamp).strip()) + else: + receiver.msg(string.format(traceback=tracestring.splitlines()[-1], + errmsg=stringtuple[1].strip(), + timestamp=timestamp).strip()) # custom Exceptions @@ -177,7 +210,6 @@ def get_and_merge_cmdsets(caller, session, player, obj, callertype): channel_cmdset = yield CHANNELHANDLER.get_cmdset(player) returnValue(channel_cmdset) except Exception: - logger.log_trace() _msg_err(caller, _ERROR_CMDSETS) raise ErrorReported @@ -221,7 +253,6 @@ def get_and_merge_cmdsets(caller, session, player, obj, callertype): cset.duplicates = True if cset.duplicates is None else cset.duplicates returnValue(local_obj_cmdsets) except Exception: - logger.log_trace() _msg_err(caller, _ERROR_CMDSETS) raise ErrorReported @@ -235,7 +266,6 @@ def get_and_merge_cmdsets(caller, session, player, obj, callertype): try: yield obj.at_cmdset_get() except Exception: - logger.log_trace() _msg_err(caller, _ERROR_CMDSETS) raise ErrorReported try: @@ -323,7 +353,6 @@ def get_and_merge_cmdsets(caller, session, player, obj, callertype): except ErrorReported: raise except Exception: - logger.log_trace() _msg_err(caller, _ERROR_CMDSETS) raise ErrorReported @@ -449,7 +478,6 @@ def cmdhandler(called_by, raw_string, _testing=False, callertype="session", sess returnValue(ret) except Exception: - logger.log_trace() _msg_err(caller, _ERROR_UNTRAPPED) raise ErrorReported @@ -579,10 +607,8 @@ def cmdhandler(called_by, raw_string, _testing=False, callertype="session", sess except Exception: # We should not end up here. If we do, it's a programming bug. - logger.log_trace() _msg_err(error_to, _ERROR_UNTRAPPED) except Exception: # This catches exceptions in cmdhandler exceptions themselves - logger.log_trace() _msg_err(error_to, _ERROR_CMDHANDLER) diff --git a/evennia/commands/cmdsethandler.py b/evennia/commands/cmdsethandler.py index 237084bf21..14445cf171 100644 --- a/evennia/commands/cmdsethandler.py +++ b/evennia/commands/cmdsethandler.py @@ -66,6 +66,7 @@ the 'Fishing' set. Fishing from a boat? No problem! from builtins import object from future.utils import raise_ import sys +from traceback import format_exc from importlib import import_module from inspect import trace from django.conf import settings @@ -78,6 +79,29 @@ __all__ = ("import_cmdset", "CmdSetHandler") _CACHED_CMDSETS = {} _CMDSET_PATHS = utils.make_iter(settings.CMDSET_PATHS) +_IN_GAME_ERRORS = settings.IN_GAME_ERRORS + +# Output strings + +_ERROR_CMDSET_IMPORT = _( +"""{traceback} +Error loading cmdset '{path}' +(Traceback was logged {timestamp})""") + +_ERROR_CMDSET_KEYERROR = _( +"""Error loading cmdset: No cmdset class '{classname}' in '{path}'. +(Traceback was logged {timestamp})""") + +_ERROR_CMDSET_SYNTAXERROR = _( +"""{traceback} +SyntaxError encountered when loading cmdset '{path}'. +(Traceback was logged {timestamp})""") + +_ERROR_CMDSET_EXCEPTION = _( +"""{traceback} +Compile/Run error when loading cmdset '{path}'.", +(Traceback was logged {timestamp})""") + class _ErrorCmdSet(CmdSet): """ @@ -160,25 +184,34 @@ def import_cmdset(path, cmdsetobj, emit_to_obj=None, no_logging=False): cmdsetclass = cmdsetclass(cmdsetobj) errstring = "" return cmdsetclass - except ImportError as e: + except ImportError as err: logger.log_trace() - errstring += _("\nError loading cmdset {path}: \"{error}\"") - errstring = errstring.format(path=python_path, error=e) + errstring += _ERROR_CMDSET_IMPORT + if _IN_GAME_ERRORS: + errstring = errstring.format(path=python_path, traceback=format_exc(), timestamp=logger.timeformat()) + else: + errstring = errstring.format(path=python_path, traceback=err, timestamp=logger.timeformat()) break except KeyError: logger.log_trace() - errstring += _("\nError in loading cmdset: No cmdset class '{classname}' in {path}.") - errstring = errstring.format(classname=classname, path=python_path) + errstring += _ERROR_CMDSET_KEYERROR + errstring = errstring.format(classname=classname, path=python_path, timestamp=logger.timeformat()) break - except SyntaxError as e: + except SyntaxError as err: logger.log_trace() - errstring += _("\nSyntaxError encountered when loading cmdset '{path}': \"{error}\".") - errstring = errstring.format(path=python_path, error=e) + errstring += _ERROR_CMDSET_SYNTAXERROR + if _IN_GAME_ERRORS: + errstring = errstring.format(path=python_path, traceback=format_exc(), timestamp=logger.timeformat()) + else: + errstring = errstring.format(path=python_path, traceback=err, timestamp=logger.timeformat()) break - except Exception as e: + except Exception as err: logger.log_trace() - errstring += _("\nCompile/Run error when loading cmdset '{path}': \"{error}\".") - errstring = errstring.format(path=python_path, error=e) + errstring += _ERROR_CMDSET_EXCEPTION + if _IN_GAME_ERRORS: + errstring = errstring.format(path=python_path, traceback=format_exc(), timestamp=logger.timeformat()) + else: + errstring = errstring.format(path=python_path, traceback=err, timestamp=logger.timeformat()) break if errstring: @@ -189,7 +222,7 @@ def import_cmdset(path, cmdsetobj, emit_to_obj=None, no_logging=False): if emit_to_obj and not ServerConfig.objects.conf("server_starting_mode"): emit_to_obj.msg(errstring) err_cmdset = _ErrorCmdSet() - err_cmdset.errmessage = errstring + _("\n (See log for details.)") + err_cmdset.errmessage = errstring return err_cmdset # classes diff --git a/evennia/settings_default.py b/evennia/settings_default.py index e8e7bd5f81..076ebd9913 100644 --- a/evennia/settings_default.py +++ b/evennia/settings_default.py @@ -23,6 +23,9 @@ import sys # This is the name of your game. Make it catchy! SERVERNAME = "Evennia" +# Lockdown mode will cut off the game from any external connections +# and only allow connections from localhost. Requires a cold reboot. +LOCKDOWN_MODE = False # Activate telnet service TELNET_ENABLED = True # A list of ports the Evennia telnet server listens on Can be one or many. @@ -204,6 +207,11 @@ MAX_CONNECTION_RATE = 2 MAX_COMMAND_RATE = 80 # The warning to echo back to users if they send commands too fast COMMAND_RATE_WARNING ="You entered commands too fast. Wait a moment and try again." +# If this is true, errors and tracebacks from the engine will be +# echoed as text in-game as well as to the log. This can speed up +# debugging. Showing full tracebacks to regular users could be a +# security problem - this should *not* be active in a production game! +IN_GAME_ERRORS = False ###################################################################### # Evennia Database config