Make log rotation also limited by size and controllable from settings. Resolves #2041
This commit is contained in:
parent
f124b3510b
commit
ff16eb1bfe
13 changed files with 98 additions and 40 deletions
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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):
|
||||||
|
|
|
||||||
|
|
@ -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):
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
|
|
@ -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):
|
||||||
|
|
|
||||||
|
|
@ -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):
|
||||||
|
|
|
||||||
|
|
@ -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):
|
||||||
|
|
|
||||||
|
|
@ -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):
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
"""
|
"""
|
||||||
|
suffix = ""
|
||||||
|
copy_suffix = 0
|
||||||
|
while True:
|
||||||
try:
|
try:
|
||||||
return "_".join(["{:02d}".format(part) for part in tupledate])
|
suffix = "_".join(["{:02d}".format(part) for part in tupledate])
|
||||||
except Exception:
|
except Exception:
|
||||||
# try taking a float unixtime
|
# try taking a float unixtime
|
||||||
return "_".join(["{:02d}".format(part) for part in self.toDate(tupledate)])
|
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):
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue