diff --git a/CHANGELOG.md b/CHANGELOG.md index 03b2fe2d2b..80c011be68 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -46,6 +46,8 @@ without arguments starts a full interactive Python console. the new `raise_exception` boolean if ranting to raise KeyError on a missing key. - Moved behavior of unmodified `Command` and `MuxCommand` `.func()` to new `.get_command_info()` method for easier overloading and access. (Volund) +- Removed unused `CYCLE_LOGFILES` setting. Added `SERVER_LOG_DAY_ROTATION` + and `SERVER_LOG_MAX_SIZE` (and equivalent for PORTAL) to control log rotation. ## Evennia 0.9 (2018-2019) diff --git a/evennia/commands/default/tests.py b/evennia/commands/default/tests.py index bb12212957..a250166530 100644 --- a/evennia/commands/default/tests.py +++ b/evennia/commands/default/tests.py @@ -1062,7 +1062,7 @@ class TestBuilding(CommandTest): # Test valid dbref ranges with no search term id1 = self.obj1.id id2 = self.obj2.id - maxid = ObjectDB.objects.latest('id').id + maxid = ObjectDB.objects.latest("id").id maxdiff = maxid - id1 + 1 mdiff = id2 - id1 + 1 diff --git a/evennia/contrib/turnbattle/tb_basic.py b/evennia/contrib/turnbattle/tb_basic.py index da8915ec86..35c5bc130f 100644 --- a/evennia/contrib/turnbattle/tb_basic.py +++ b/evennia/contrib/turnbattle/tb_basic.py @@ -441,11 +441,11 @@ class TBBasicTurnHandler(DefaultScript): """ combat_cleanup(character) # Clean up leftover combat attributes beforehand, just in case. character.db.combat_actionsleft = ( - 0 - ) # Actions remaining - start of turn adds to this, turn ends when it reaches 0 + 0 # Actions remaining - start of turn adds to this, turn ends when it reaches 0 + ) character.db.combat_turnhandler = ( - self - ) # Add a reference to this turn handler script to the character + self # Add a reference to this turn handler script to the character + ) character.db.combat_lastaction = "null" # Track last action taken in combat def start_turn(self, character): diff --git a/evennia/contrib/turnbattle/tb_equip.py b/evennia/contrib/turnbattle/tb_equip.py index e516e64ac1..5a2badc3e6 100644 --- a/evennia/contrib/turnbattle/tb_equip.py +++ b/evennia/contrib/turnbattle/tb_equip.py @@ -438,11 +438,11 @@ class TBEquipTurnHandler(DefaultScript): """ combat_cleanup(character) # Clean up leftover combat attributes beforehand, just in case. character.db.combat_actionsleft = ( - 0 - ) # Actions remaining - start of turn adds to this, turn ends when it reaches 0 + 0 # Actions remaining - start of turn adds to this, turn ends when it reaches 0 + ) character.db.combat_turnhandler = ( - self - ) # Add a reference to this turn handler script to the character + self # Add a reference to this turn handler script to the character + ) character.db.combat_lastaction = "null" # Track last action taken in combat def start_turn(self, character): @@ -553,8 +553,8 @@ class TBEWeapon(DefaultObject): self.db.damage_range = (15, 25) # Minimum and maximum damage on hit self.db.accuracy_bonus = 0 # Bonus to attack rolls (or penalty if negative) self.db.weapon_type_name = ( - "weapon" - ) # Single word for weapon - I.E. "dagger", "staff", "scimitar" + "weapon" # Single word for weapon - I.E. "dagger", "staff", "scimitar" + ) def at_drop(self, dropper): """ diff --git a/evennia/contrib/turnbattle/tb_items.py b/evennia/contrib/turnbattle/tb_items.py index 512337478a..1e6fce59f7 100644 --- a/evennia/contrib/turnbattle/tb_items.py +++ b/evennia/contrib/turnbattle/tb_items.py @@ -718,11 +718,11 @@ class TBItemsTurnHandler(DefaultScript): """ combat_cleanup(character) # Clean up leftover combat attributes beforehand, just in case. character.db.combat_actionsleft = ( - 0 - ) # Actions remaining - start of turn adds to this, turn ends when it reaches 0 + 0 # Actions remaining - start of turn adds to this, turn ends when it reaches 0 + ) character.db.combat_turnhandler = ( - self - ) # Add a reference to this turn handler script to the character + self # Add a reference to this turn handler script to the character + ) character.db.combat_lastaction = "null" # Track last action taken in combat def start_turn(self, character): diff --git a/evennia/contrib/turnbattle/tb_magic.py b/evennia/contrib/turnbattle/tb_magic.py index 0daa9b70c5..ab9f094d26 100644 --- a/evennia/contrib/turnbattle/tb_magic.py +++ b/evennia/contrib/turnbattle/tb_magic.py @@ -470,11 +470,11 @@ class TBMagicTurnHandler(DefaultScript): """ combat_cleanup(character) # Clean up leftover combat attributes beforehand, just in case. character.db.combat_actionsleft = ( - 0 - ) # Actions remaining - start of turn adds to this, turn ends when it reaches 0 + 0 # Actions remaining - start of turn adds to this, turn ends when it reaches 0 + ) character.db.combat_turnhandler = ( - self - ) # Add a reference to this turn handler script to the character + self # Add a reference to this turn handler script to the character + ) character.db.combat_lastaction = "null" # Track last action taken in combat def start_turn(self, character): diff --git a/evennia/contrib/turnbattle/tb_range.py b/evennia/contrib/turnbattle/tb_range.py index 1d0a297a10..c0eca41487 100644 --- a/evennia/contrib/turnbattle/tb_range.py +++ b/evennia/contrib/turnbattle/tb_range.py @@ -674,11 +674,11 @@ class TBRangeTurnHandler(DefaultScript): """ combat_cleanup(character) # Clean up leftover combat attributes beforehand, just in case. character.db.combat_actionsleft = ( - 0 - ) # Actions remaining - start of turn adds to this, turn ends when it reaches 0 + 0 # Actions remaining - start of turn adds to this, turn ends when it reaches 0 + ) character.db.combat_turnhandler = ( - self - ) # Add a reference to this turn handler script to the character + self # Add a reference to this turn handler script to the character + ) character.db.combat_lastaction = "null" # Track last action taken in combat def start_turn(self, character): diff --git a/evennia/server/deprecations.py b/evennia/server/deprecations.py index 3776364ba5..45eefd160b 100644 --- a/evennia/server/deprecations.py +++ b/evennia/server/deprecations.py @@ -96,6 +96,12 @@ def check_errors(settings): "must now be either None or a dict " "specifying the properties of the channel to create." ) + if hasattr(settings, "CYCLE_LOGFILES"): + raise DeprecationWarning( + "settings.CYCLE_LOGFILES is unused and should be removed. " + "Use PORTAL/SERVER_LOG_DAY_ROTATION and PORTAL/SERVER_LOG_MAX_SIZE " + "to control log cycling." + ) def check_warnings(settings): diff --git a/evennia/server/evennia_launcher.py b/evennia/server/evennia_launcher.py index 6203161865..ce0dfb2fa0 100644 --- a/evennia/server/evennia_launcher.py +++ b/evennia/server/evennia_launcher.py @@ -1153,7 +1153,7 @@ def tail_log_files(filename1, filename2, start_lines1=20, start_lines2=20, rate= # this happens if the file was cycled or manually deleted/edited. print( " ** Log file {filename} has cycled or been edited. " - "Restarting log. ".format(filehandle.name) + "Restarting log. ".format(filename=filehandle.name) ) new_linecount = 0 old_linecount = 0 diff --git a/evennia/server/portal/portal.py b/evennia/server/portal/portal.py index ebf321a566..7e7e3e93c7 100644 --- a/evennia/server/portal/portal.py +++ b/evennia/server/portal/portal.py @@ -213,7 +213,8 @@ application = service.Application("Portal") if "--nodaemon" not in sys.argv: logfile = logger.WeeklyLogFile( - os.path.basename(settings.PORTAL_LOG_FILE), os.path.dirname(settings.PORTAL_LOG_FILE) + os.path.basename(settings.PORTAL_LOG_FILE), os.path.dirname(settings.PORTAL_LOG_FILE), + day_rotation=settings.PORTAL_LOG_DAY_ROTATION, max_size=settings.PORTAL_LOG_MAX_SIZE ) application.setComponent(ILogObserver, logger.PortalLogObserver(logfile).emit) diff --git a/evennia/server/server.py b/evennia/server/server.py index 33cdf66be7..23ad294e45 100644 --- a/evennia/server/server.py +++ b/evennia/server/server.py @@ -616,7 +616,8 @@ application = service.Application("Evennia") if "--nodaemon" not in sys.argv: # custom logging, but only if we are not running in interactive mode logfile = logger.WeeklyLogFile( - os.path.basename(settings.SERVER_LOG_FILE), os.path.dirname(settings.SERVER_LOG_FILE) + os.path.basename(settings.SERVER_LOG_FILE), os.path.dirname(settings.SERVER_LOG_FILE), + day_rotation=settings.SERVER_LOG_DAY_ROTATION, max_size=settings.SERVER_LOG_MAX_SIZE ) application.setComponent(ILogObserver, logger.ServerLogObserver(logfile).emit) diff --git a/evennia/settings_default.py b/evennia/settings_default.py index 674aeee4be..b2a06e328e 100644 --- a/evennia/settings_default.py +++ b/evennia/settings_default.py @@ -135,17 +135,19 @@ else: break os.chdir(os.pardir) -# Place to put log files +# Place to put log files, how often to rotate the log and how big each log file +# may become before rotating. LOG_DIR = os.path.join(GAME_DIR, "server", "logs") SERVER_LOG_FILE = os.path.join(LOG_DIR, "server.log") +SERVER_LOG_DAY_ROTATION = 7 +SERVER_LOG_MAX_SIZE = 1000000 PORTAL_LOG_FILE = os.path.join(LOG_DIR, "portal.log") +PORTAL_LOG_DAY_ROTATION = 7 +PORTAL_LOG_MAX_SIZE = 1000000 +# The http log is usually only for debugging since it's very spammy HTTP_LOG_FILE = os.path.join(LOG_DIR, "http_requests.log") # if this is set to the empty string, lockwarnings will be turned off. LOCKWARNING_LOG_FILE = os.path.join(LOG_DIR, "lockwarnings.log") -# Rotate log files when server and/or portal stops. This will keep log -# file sizes down. Turn off to get ever growing log files and never -# lose log info. -CYCLE_LOGFILES = True # Number of lines to append to rotating channel logs when they rotate CHANNEL_LOG_NUM_TAIL_LINES = 20 # Max size (in bytes) of channel log files before they rotate diff --git a/evennia/utils/logger.py b/evennia/utils/logger.py index 98060208da..af4f8241a8 100644 --- a/evennia/utils/logger.py +++ b/evennia/utils/logger.py @@ -16,6 +16,7 @@ log_typemsg(). This is for historical, back-compatible reasons. import os import time +import glob from datetime import datetime from traceback import format_exc from twisted.python import log, logfile @@ -76,33 +77,78 @@ def timeformat(when=None): class WeeklyLogFile(logfile.DailyLogFile): """ - Log file that rotates once per week. Overrides key methods to change format + Log file that rotates once per week by default. Overrides key methods to change format. """ - day_rotation = 7 + def __init__(self, name, directory, defaultMode=None, day_rotation=7, max_size=1000000): + """ + Args: + name (str): Name of log file. + directory (str): Directory holding the file. + defaultMode (str): Permissions used to create file. Defaults to + current permissions of this file if it exists. + day_rotation (int): How often to rotate the file. + max_size (int): Max size of log file before rotation (regardless of + time). Defaults to 1M. + + """ + self.day_rotation = day_rotation + self.max_size = max_size + self.size = 0 + logfile.DailyLogFile.__init__(self, name, directory, defaultMode=defaultMode) + + def _openFile(self): + logfile.DailyLogFile._openFile(self) + self.size = self._file.tell() def shouldRotate(self): """Rotate when the date has changed since last write""" # all dates here are tuples (year, month, day) now = self.toDate() then = self.lastDate - return now[0] > then[0] or now[1] > then[1] or now[2] > (then[2] + self.day_rotation) + return (now[0] > then[0] or + now[1] > then[1] or + now[2] > (then[2] + self.day_rotation) or + self.size >= self.max_size) def suffix(self, tupledate): """Return the suffix given a (year, month, day) tuple or unixtime. - Format changed to have 03 for march instead of 3 etc (retaining unix file order) + Format changed to have 03 for march instead of 3 etc (retaining unix + file order) + + If we get duplicate suffixes in location (due to hitting size limit), + we append __1, __2 etc. + + Examples: + server.log.2020_01_29 + server.log.2020_01_29__1 + server.log.2020_01_29__2 """ - try: - return "_".join(["{:02d}".format(part) for part in tupledate]) - except Exception: - # try taking a float unixtime - return "_".join(["{:02d}".format(part) for part in self.toDate(tupledate)]) + suffix = "" + copy_suffix = 0 + while True: + try: + suffix = "_".join(["{:02d}".format(part) for part in tupledate]) + except Exception: + # try taking a float unixtime + suffix = "_".join(["{:02d}".format(part) for part in self.toDate(tupledate)]) + + suffix += f"__{copy_suffix}" if copy_suffix else "" + + if os.path.exists(f"{self.path}.{suffix}"): + # Append a higher copy_suffix to try to break the tie (starting from 2) + copy_suffix += 1 + else: + break + return suffix + def write(self, data): "Write data to log file" logfile.BaseLogFile.write(self, data) self.lastDate = max(self.lastDate, self.toDate()) + self.size += len(data) class PortalLogObserver(log.FileLogObserver):