[#1949] [UI] Allow setting max size for rotating log file

This commit is contained in:
bendikro 2016-01-15 22:08:44 +01:00
parent c90af1ce6c
commit 6300f9154a
5 changed files with 141 additions and 36 deletions

View File

@ -13,8 +13,10 @@ import base64
import gettext
import locale
import logging
import numbers
import os
import platform
import re
import subprocess
import sys
import time
@ -465,6 +467,82 @@ def fdate(seconds, date_only=False, precision_secs=False):
return time.strftime("%x %H:%M", time.localtime(seconds))
def tokenize(text):
"""
Tokenize a text into numbers and strings.
Args:
text (str): The text to tokenize (a string).
Returns:
list: A list of strings and/or numbers.
This function is used to implement robust tokenization of user input
It automatically coerces integer and floating point numbers, ignores
whitespace and knows how to separate numbers from strings even without
whitespace.
"""
tokenized_input = []
for token in re.split(r'(\d+(?:\.\d+)?)', text):
token = token.strip()
if re.match(r'\d+\.\d+', token):
tokenized_input.append(float(token))
elif token.isdigit():
tokenized_input.append(int(token))
elif token:
tokenized_input.append(token)
return tokenized_input
size_units = (dict(prefix='b', divider=1, singular='byte', plural='bytes'),
dict(prefix='KiB', divider=1024**1),
dict(prefix='MiB', divider=1024**2),
dict(prefix='GiB', divider=1024**3),
dict(prefix='TiB', divider=1024**4),
dict(prefix='PiB', divider=1024**5),
dict(prefix='KB', divider=1000**1),
dict(prefix='MB', divider=1000**2),
dict(prefix='GB', divider=1000**3),
dict(prefix='TB', divider=1000**4),
dict(prefix='PB', divider=1000**5),
dict(prefix='m', divider=1000**2))
class InvalidSize(Exception):
pass
def parse_human_size(size):
"""
Parse a human readable data size and return the number of bytes.
Args:
size (str): The human readable file size to parse (a string).
Returns:
int: The corresponding size in bytes.
Raises:
InvalidSize: when the input can't be parsed.
"""
tokens = tokenize(size)
if tokens and isinstance(tokens[0], numbers.Number):
# If the input contains only a number, it's assumed to be the number of bytes.
if len(tokens) == 1:
return int(tokens[0])
# Otherwise we expect to find two tokens: A number and a unit.
if len(tokens) == 2 and isinstance(tokens[1], basestring):
normalized_unit = tokens[1].lower()
# Try to match the first letter of the unit.
for unit in size_units:
if normalized_unit.startswith(unit['prefix'].lower()):
return int(tokens[0] * unit['divider'])
# We failed to parse the size specification.
msg = "Failed to parse size! (input %r was tokenized as %r)"
raise InvalidSize(msg % (size, tokens))
def is_url(url):
"""
A simple test to check if the URL is valid

View File

@ -107,15 +107,19 @@ levels = {
}
def setup_logger(level="error", filename=None, filemode="w"):
def setup_logger(level="error", filename=None, filemode="w", logrotate=None):
"""
Sets up the basic logger and if `:param:filename` is set, then it will log
to that file instead of stdout.
:param level: str, the level to log
:param filename: str, the file to log to
Args:
level (str): The log level to use (Default: "error")
filename (str, optional): The log filename. Default is None meaning log
to terminal
filemode (str): The filemode to use when opening the log file
logrotate (int, optional): The size of the logfile in bytes when enabling
log rotation (Default is None meaning disabled)
"""
if logging.getLoggerClass() is not Logging:
logging.setLoggerClass(Logging)
logging.addLevelName(5, "TRACE")
@ -125,19 +129,16 @@ def setup_logger(level="error", filename=None, filemode="w"):
root_logger = logging.getLogger()
if filename and filemode == "a":
if filename and logrotate:
handler = logging.handlers.RotatingFileHandler(
filename, filemode,
maxBytes=50 * 1024 * 1024, # 50 Mb
backupCount=3,
encoding="utf-8",
delay=0
filename, maxBytes=logrotate,
backupCount=5, encoding="utf-8"
)
elif filename and filemode == "w":
handler = getattr(
logging.handlers, "WatchedFileHandler", logging.FileHandler)(
filename, filemode, "utf-8", delay=0
)
filename, mode=filemode, encoding="utf-8"
)
else:
handler = logging.StreamHandler(stream=sys.stdout)

View File

@ -74,3 +74,21 @@ class CommonTestCase(unittest.TestCase):
self.failUnless(VersionSplit("1.4.0") > VersionSplit("1.4.0.dev123"))
self.failUnless(VersionSplit("1.4.0.dev1") < VersionSplit("1.4.0"))
self.failUnless(VersionSplit("1.4.0a1") < VersionSplit("1.4.0"))
def test_parse_human_size(self):
from deluge.common import parse_human_size
sizes = [("1", 1),
("10 bytes", 10),
("2048 bytes", 2048),
("1MiB", 2**(10 * 2)),
("1 MiB", 2**(10 * 2)),
("1 GiB", 2**(10 * 3)),
("1 GiB", 2**(10 * 3)),
("1M", 10**6),
("1MB", 10**6),
("1 GB", 10**9),
("1 TB", 10**12)]
for human_size, byte_size in sizes:
parsed = parse_human_size(human_size)
self.assertEquals(parsed, byte_size, "Mismatch when converting '%s'" % human_size)

View File

@ -14,21 +14,21 @@ import platform
import sys
import textwrap
import deluge.common
import deluge.configmanager
import deluge.log
from deluge import common
from deluge.log import setup_logger
def get_version():
version_str = "%s\n" % (deluge.common.get_version())
version_str = "%s\n" % (common.get_version())
try:
from deluge._libtorrent import lt
version_str += "libtorrent: %s\n" % lt.version
except ImportError:
pass
version_str += "Python: %s\n" % platform.python_version()
version_str += "OS: %s %s\n" % (platform.system(), " ".join(deluge.common.get_os_version()))
version_str += "OS: %s %s\n" % (platform.system(), " ".join(common.get_os_version()))
return version_str
@ -93,6 +93,7 @@ class BaseArgParser(argparse.ArgumentParser):
kwargs["formatter_class"] = lambda prog: DelugeTextHelpFormatter(prog, max_help_position=33, width=90)
super(BaseArgParser, self).__init__(*args, add_help=False, **kwargs)
self.common_setup = False
self.group = self.add_argument_group('Common Options')
self.group.add_argument('--version', action='version', version='%(prog)s ' + get_version(),
help="Show program's version number and exit")
@ -104,37 +105,43 @@ class BaseArgParser(argparse.ArgumentParser):
help="Set the log level: %s" % ", ".join(deluge.log.levels))
self.group.add_argument("-q", "--quiet", action="store_true", default=False,
help="Sets the log level to 'none', this is the same as `-L none`")
self.group.add_argument("-r", "--rotate-logs", action="store_true", default=False,
help="Rotate logfiles.")
self.group.add_argument("--log-rotate", action="store", nargs="?", const="50M",
help="Enable logfile rotation (optional max file size, default: %(const)s)."
"Log file rotate count is 5")
self.group.add_argument("--profile", metavar="<results file>", action="store", nargs="?", default=False,
help="Profile %(prog)s with cProfile. Prints results to stdout"
"unless a filename is specififed.")
"unless a filename is specififed")
self.group.add_argument("-h", "--help", action=HelpAction, help='Show this help message and exit')
def parse_args(self, *args):
options, remaining = super(BaseArgParser, self).parse_known_args(*args)
options.remaining = remaining
# Setup the logger
if options.quiet:
options.loglevel = "none"
if options.loglevel:
options.loglevel = options.loglevel.lower()
if not self.common_setup:
self.common_setup = True
# Setup the logger
if options.quiet:
options.loglevel = "none"
if options.loglevel:
options.loglevel = options.loglevel.lower()
logfile_mode = 'w'
if options.rotate_logs:
logfile_mode = 'a'
logfile_mode = 'w'
logrotate = options.log_rotate
if options.log_rotate:
logfile_mode = 'a'
logrotate = common.parse_human_size(options.log_rotate)
# Setup the logger
setup_logger(level=options.loglevel, filename=options.logfile, filemode=logfile_mode)
# Setup the logger
setup_logger(level=options.loglevel, filename=options.logfile, filemode=logfile_mode,
logrotate=logrotate)
if options.config:
if not deluge.configmanager.set_config_dir(options.config):
log = logging.getLogger(__name__)
log.error("There was an error setting the config dir! Exiting..")
sys.exit(1)
else:
if not os.path.exists(deluge.common.get_default_config_dir()):
os.makedirs(deluge.common.get_default_config_dir())
if options.config:
if not deluge.configmanager.set_config_dir(options.config):
log = logging.getLogger(__name__)
log.error("There was an error setting the config dir! Exiting..")
sys.exit(1)
else:
if not os.path.exists(common.get_default_config_dir()):
os.makedirs(common.get_default_config_dir())
return options

View File

@ -24,6 +24,7 @@ log = logging.getLogger(__name__)
# defined in setup.py
#
def load_commands(command_dir):
def get_command(name):