Make log rotation also limited by size and controllable from settings. Resolves #2041

This commit is contained in:
Griatch 2020-01-29 23:16:53 +01:00
parent f124b3510b
commit ff16eb1bfe
13 changed files with 98 additions and 40 deletions

View file

@ -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. the new `raise_exception` boolean if ranting to raise KeyError on a missing key.
- Moved behavior of unmodified `Command` and `MuxCommand` `.func()` to new - Moved behavior of unmodified `Command` and `MuxCommand` `.func()` to new
`.get_command_info()` method for easier overloading and access. (Volund) `.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) ## Evennia 0.9 (2018-2019)

View file

@ -1062,7 +1062,7 @@ class TestBuilding(CommandTest):
# Test valid dbref ranges with no search term # Test valid dbref ranges with no search term
id1 = self.obj1.id id1 = self.obj1.id
id2 = self.obj2.id id2 = self.obj2.id
maxid = ObjectDB.objects.latest('id').id maxid = ObjectDB.objects.latest("id").id
maxdiff = maxid - id1 + 1 maxdiff = maxid - id1 + 1
mdiff = id2 - id1 + 1 mdiff = id2 - id1 + 1

View file

@ -441,11 +441,11 @@ class TBBasicTurnHandler(DefaultScript):
""" """
combat_cleanup(character) # Clean up leftover combat attributes beforehand, just in case. combat_cleanup(character) # Clean up leftover combat attributes beforehand, just in case.
character.db.combat_actionsleft = ( character.db.combat_actionsleft = (
0 0 # Actions remaining - start of turn adds to this, turn ends when it reaches 0
) # Actions remaining - start of turn adds to this, turn ends when it reaches 0 )
character.db.combat_turnhandler = ( character.db.combat_turnhandler = (
self self # Add a reference to this turn handler script to the character
) # Add a reference to this turn handler script to the character )
character.db.combat_lastaction = "null" # Track last action taken in combat character.db.combat_lastaction = "null" # Track last action taken in combat
def start_turn(self, character): def start_turn(self, character):

View file

@ -438,11 +438,11 @@ class TBEquipTurnHandler(DefaultScript):
""" """
combat_cleanup(character) # Clean up leftover combat attributes beforehand, just in case. combat_cleanup(character) # Clean up leftover combat attributes beforehand, just in case.
character.db.combat_actionsleft = ( character.db.combat_actionsleft = (
0 0 # Actions remaining - start of turn adds to this, turn ends when it reaches 0
) # Actions remaining - start of turn adds to this, turn ends when it reaches 0 )
character.db.combat_turnhandler = ( character.db.combat_turnhandler = (
self self # Add a reference to this turn handler script to the character
) # Add a reference to this turn handler script to the character )
character.db.combat_lastaction = "null" # Track last action taken in combat character.db.combat_lastaction = "null" # Track last action taken in combat
def start_turn(self, character): 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.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.accuracy_bonus = 0 # Bonus to attack rolls (or penalty if negative)
self.db.weapon_type_name = ( self.db.weapon_type_name = (
"weapon" "weapon" # Single word for weapon - I.E. "dagger", "staff", "scimitar"
) # Single word for weapon - I.E. "dagger", "staff", "scimitar" )
def at_drop(self, dropper): def at_drop(self, dropper):
""" """

View file

@ -718,11 +718,11 @@ class TBItemsTurnHandler(DefaultScript):
""" """
combat_cleanup(character) # Clean up leftover combat attributes beforehand, just in case. combat_cleanup(character) # Clean up leftover combat attributes beforehand, just in case.
character.db.combat_actionsleft = ( character.db.combat_actionsleft = (
0 0 # Actions remaining - start of turn adds to this, turn ends when it reaches 0
) # Actions remaining - start of turn adds to this, turn ends when it reaches 0 )
character.db.combat_turnhandler = ( character.db.combat_turnhandler = (
self self # Add a reference to this turn handler script to the character
) # Add a reference to this turn handler script to the character )
character.db.combat_lastaction = "null" # Track last action taken in combat character.db.combat_lastaction = "null" # Track last action taken in combat
def start_turn(self, character): def start_turn(self, character):

View file

@ -470,11 +470,11 @@ class TBMagicTurnHandler(DefaultScript):
""" """
combat_cleanup(character) # Clean up leftover combat attributes beforehand, just in case. combat_cleanup(character) # Clean up leftover combat attributes beforehand, just in case.
character.db.combat_actionsleft = ( character.db.combat_actionsleft = (
0 0 # Actions remaining - start of turn adds to this, turn ends when it reaches 0
) # Actions remaining - start of turn adds to this, turn ends when it reaches 0 )
character.db.combat_turnhandler = ( character.db.combat_turnhandler = (
self self # Add a reference to this turn handler script to the character
) # Add a reference to this turn handler script to the character )
character.db.combat_lastaction = "null" # Track last action taken in combat character.db.combat_lastaction = "null" # Track last action taken in combat
def start_turn(self, character): def start_turn(self, character):

View file

@ -674,11 +674,11 @@ class TBRangeTurnHandler(DefaultScript):
""" """
combat_cleanup(character) # Clean up leftover combat attributes beforehand, just in case. combat_cleanup(character) # Clean up leftover combat attributes beforehand, just in case.
character.db.combat_actionsleft = ( character.db.combat_actionsleft = (
0 0 # Actions remaining - start of turn adds to this, turn ends when it reaches 0
) # Actions remaining - start of turn adds to this, turn ends when it reaches 0 )
character.db.combat_turnhandler = ( character.db.combat_turnhandler = (
self self # Add a reference to this turn handler script to the character
) # Add a reference to this turn handler script to the character )
character.db.combat_lastaction = "null" # Track last action taken in combat character.db.combat_lastaction = "null" # Track last action taken in combat
def start_turn(self, character): def start_turn(self, character):

View file

@ -96,6 +96,12 @@ def check_errors(settings):
"must now be either None or a dict " "must now be either None or a dict "
"specifying the properties of the channel to create." "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): def check_warnings(settings):

View file

@ -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. # this happens if the file was cycled or manually deleted/edited.
print( print(
" ** Log file {filename} has cycled or been edited. " " ** Log file {filename} has cycled or been edited. "
"Restarting log. ".format(filehandle.name) "Restarting log. ".format(filename=filehandle.name)
) )
new_linecount = 0 new_linecount = 0
old_linecount = 0 old_linecount = 0

View file

@ -213,7 +213,8 @@ application = service.Application("Portal")
if "--nodaemon" not in sys.argv: if "--nodaemon" not in sys.argv:
logfile = logger.WeeklyLogFile( 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) application.setComponent(ILogObserver, logger.PortalLogObserver(logfile).emit)

View file

@ -616,7 +616,8 @@ application = service.Application("Evennia")
if "--nodaemon" not in sys.argv: if "--nodaemon" not in sys.argv:
# custom logging, but only if we are not running in interactive mode # custom logging, but only if we are not running in interactive mode
logfile = logger.WeeklyLogFile( 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) application.setComponent(ILogObserver, logger.ServerLogObserver(logfile).emit)

View file

@ -135,17 +135,19 @@ else:
break break
os.chdir(os.pardir) 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") LOG_DIR = os.path.join(GAME_DIR, "server", "logs")
SERVER_LOG_FILE = os.path.join(LOG_DIR, "server.log") 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_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") HTTP_LOG_FILE = os.path.join(LOG_DIR, "http_requests.log")
# if this is set to the empty string, lockwarnings will be turned off. # if this is set to the empty string, lockwarnings will be turned off.
LOCKWARNING_LOG_FILE = os.path.join(LOG_DIR, "lockwarnings.log") 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 # Number of lines to append to rotating channel logs when they rotate
CHANNEL_LOG_NUM_TAIL_LINES = 20 CHANNEL_LOG_NUM_TAIL_LINES = 20
# Max size (in bytes) of channel log files before they rotate # Max size (in bytes) of channel log files before they rotate

View file

@ -16,6 +16,7 @@ log_typemsg(). This is for historical, back-compatible reasons.
import os import os
import time import time
import glob
from datetime import datetime from datetime import datetime
from traceback import format_exc from traceback import format_exc
from twisted.python import log, logfile from twisted.python import log, logfile
@ -76,33 +77,78 @@ def timeformat(when=None):
class WeeklyLogFile(logfile.DailyLogFile): 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): def shouldRotate(self):
"""Rotate when the date has changed since last write""" """Rotate when the date has changed since last write"""
# all dates here are tuples (year, month, day) # all dates here are tuples (year, month, day)
now = self.toDate() now = self.toDate()
then = self.lastDate 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): def suffix(self, tupledate):
"""Return the suffix given a (year, month, day) tuple or unixtime. """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: suffix = ""
return "_".join(["{:02d}".format(part) for part in tupledate]) copy_suffix = 0
except Exception: while True:
# try taking a float unixtime try:
return "_".join(["{:02d}".format(part) for part in self.toDate(tupledate)]) 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): def write(self, data):
"Write data to log file" "Write data to log file"
logfile.BaseLogFile.write(self, data) logfile.BaseLogFile.write(self, data)
self.lastDate = max(self.lastDate, self.toDate()) self.lastDate = max(self.lastDate, self.toDate())
self.size += len(data)
class PortalLogObserver(log.FileLogObserver): class PortalLogObserver(log.FileLogObserver):