mirror of
https://github.com/codex-storage/deluge.git
synced 2025-02-28 02:50:30 +00:00
[#1949] [UI] Allow setting max size for rotating log file
This commit is contained in:
parent
c90af1ce6c
commit
6300f9154a
@ -13,8 +13,10 @@ import base64
|
|||||||
import gettext
|
import gettext
|
||||||
import locale
|
import locale
|
||||||
import logging
|
import logging
|
||||||
|
import numbers
|
||||||
import os
|
import os
|
||||||
import platform
|
import platform
|
||||||
|
import re
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
@ -465,6 +467,82 @@ def fdate(seconds, date_only=False, precision_secs=False):
|
|||||||
return time.strftime("%x %H:%M", time.localtime(seconds))
|
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):
|
def is_url(url):
|
||||||
"""
|
"""
|
||||||
A simple test to check if the URL is valid
|
A simple test to check if the URL is valid
|
||||||
|
@ -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
|
Sets up the basic logger and if `:param:filename` is set, then it will log
|
||||||
to that file instead of stdout.
|
to that file instead of stdout.
|
||||||
|
|
||||||
:param level: str, the level to log
|
Args:
|
||||||
:param filename: str, the file to log to
|
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:
|
if logging.getLoggerClass() is not Logging:
|
||||||
logging.setLoggerClass(Logging)
|
logging.setLoggerClass(Logging)
|
||||||
logging.addLevelName(5, "TRACE")
|
logging.addLevelName(5, "TRACE")
|
||||||
@ -125,18 +129,15 @@ def setup_logger(level="error", filename=None, filemode="w"):
|
|||||||
|
|
||||||
root_logger = logging.getLogger()
|
root_logger = logging.getLogger()
|
||||||
|
|
||||||
if filename and filemode == "a":
|
if filename and logrotate:
|
||||||
handler = logging.handlers.RotatingFileHandler(
|
handler = logging.handlers.RotatingFileHandler(
|
||||||
filename, filemode,
|
filename, maxBytes=logrotate,
|
||||||
maxBytes=50 * 1024 * 1024, # 50 Mb
|
backupCount=5, encoding="utf-8"
|
||||||
backupCount=3,
|
|
||||||
encoding="utf-8",
|
|
||||||
delay=0
|
|
||||||
)
|
)
|
||||||
elif filename and filemode == "w":
|
elif filename and filemode == "w":
|
||||||
handler = getattr(
|
handler = getattr(
|
||||||
logging.handlers, "WatchedFileHandler", logging.FileHandler)(
|
logging.handlers, "WatchedFileHandler", logging.FileHandler)(
|
||||||
filename, filemode, "utf-8", delay=0
|
filename, mode=filemode, encoding="utf-8"
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
handler = logging.StreamHandler(stream=sys.stdout)
|
handler = logging.StreamHandler(stream=sys.stdout)
|
||||||
|
@ -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") > VersionSplit("1.4.0.dev123"))
|
||||||
self.failUnless(VersionSplit("1.4.0.dev1") < VersionSplit("1.4.0"))
|
self.failUnless(VersionSplit("1.4.0.dev1") < VersionSplit("1.4.0"))
|
||||||
self.failUnless(VersionSplit("1.4.0a1") < 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)
|
||||||
|
@ -14,21 +14,21 @@ import platform
|
|||||||
import sys
|
import sys
|
||||||
import textwrap
|
import textwrap
|
||||||
|
|
||||||
import deluge.common
|
|
||||||
import deluge.configmanager
|
import deluge.configmanager
|
||||||
import deluge.log
|
import deluge.log
|
||||||
|
from deluge import common
|
||||||
from deluge.log import setup_logger
|
from deluge.log import setup_logger
|
||||||
|
|
||||||
|
|
||||||
def get_version():
|
def get_version():
|
||||||
version_str = "%s\n" % (deluge.common.get_version())
|
version_str = "%s\n" % (common.get_version())
|
||||||
try:
|
try:
|
||||||
from deluge._libtorrent import lt
|
from deluge._libtorrent import lt
|
||||||
version_str += "libtorrent: %s\n" % lt.version
|
version_str += "libtorrent: %s\n" % lt.version
|
||||||
except ImportError:
|
except ImportError:
|
||||||
pass
|
pass
|
||||||
version_str += "Python: %s\n" % platform.python_version()
|
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
|
return version_str
|
||||||
|
|
||||||
|
|
||||||
@ -93,6 +93,7 @@ class BaseArgParser(argparse.ArgumentParser):
|
|||||||
kwargs["formatter_class"] = lambda prog: DelugeTextHelpFormatter(prog, max_help_position=33, width=90)
|
kwargs["formatter_class"] = lambda prog: DelugeTextHelpFormatter(prog, max_help_position=33, width=90)
|
||||||
super(BaseArgParser, self).__init__(*args, add_help=False, **kwargs)
|
super(BaseArgParser, self).__init__(*args, add_help=False, **kwargs)
|
||||||
|
|
||||||
|
self.common_setup = False
|
||||||
self.group = self.add_argument_group('Common Options')
|
self.group = self.add_argument_group('Common Options')
|
||||||
self.group.add_argument('--version', action='version', version='%(prog)s ' + get_version(),
|
self.group.add_argument('--version', action='version', version='%(prog)s ' + get_version(),
|
||||||
help="Show program's version number and exit")
|
help="Show program's version number and exit")
|
||||||
@ -104,17 +105,20 @@ class BaseArgParser(argparse.ArgumentParser):
|
|||||||
help="Set the log level: %s" % ", ".join(deluge.log.levels))
|
help="Set the log level: %s" % ", ".join(deluge.log.levels))
|
||||||
self.group.add_argument("-q", "--quiet", action="store_true", default=False,
|
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`")
|
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,
|
self.group.add_argument("--log-rotate", action="store", nargs="?", const="50M",
|
||||||
help="Rotate logfiles.")
|
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,
|
self.group.add_argument("--profile", metavar="<results file>", action="store", nargs="?", default=False,
|
||||||
help="Profile %(prog)s with cProfile. Prints results to stdout"
|
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')
|
self.group.add_argument("-h", "--help", action=HelpAction, help='Show this help message and exit')
|
||||||
|
|
||||||
def parse_args(self, *args):
|
def parse_args(self, *args):
|
||||||
options, remaining = super(BaseArgParser, self).parse_known_args(*args)
|
options, remaining = super(BaseArgParser, self).parse_known_args(*args)
|
||||||
options.remaining = remaining
|
options.remaining = remaining
|
||||||
|
|
||||||
|
if not self.common_setup:
|
||||||
|
self.common_setup = True
|
||||||
# Setup the logger
|
# Setup the logger
|
||||||
if options.quiet:
|
if options.quiet:
|
||||||
options.loglevel = "none"
|
options.loglevel = "none"
|
||||||
@ -122,11 +126,14 @@ class BaseArgParser(argparse.ArgumentParser):
|
|||||||
options.loglevel = options.loglevel.lower()
|
options.loglevel = options.loglevel.lower()
|
||||||
|
|
||||||
logfile_mode = 'w'
|
logfile_mode = 'w'
|
||||||
if options.rotate_logs:
|
logrotate = options.log_rotate
|
||||||
|
if options.log_rotate:
|
||||||
logfile_mode = 'a'
|
logfile_mode = 'a'
|
||||||
|
logrotate = common.parse_human_size(options.log_rotate)
|
||||||
|
|
||||||
# Setup the logger
|
# Setup the logger
|
||||||
setup_logger(level=options.loglevel, filename=options.logfile, filemode=logfile_mode)
|
setup_logger(level=options.loglevel, filename=options.logfile, filemode=logfile_mode,
|
||||||
|
logrotate=logrotate)
|
||||||
|
|
||||||
if options.config:
|
if options.config:
|
||||||
if not deluge.configmanager.set_config_dir(options.config):
|
if not deluge.configmanager.set_config_dir(options.config):
|
||||||
@ -134,7 +141,7 @@ class BaseArgParser(argparse.ArgumentParser):
|
|||||||
log.error("There was an error setting the config dir! Exiting..")
|
log.error("There was an error setting the config dir! Exiting..")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
else:
|
else:
|
||||||
if not os.path.exists(deluge.common.get_default_config_dir()):
|
if not os.path.exists(common.get_default_config_dir()):
|
||||||
os.makedirs(deluge.common.get_default_config_dir())
|
os.makedirs(common.get_default_config_dir())
|
||||||
|
|
||||||
return options
|
return options
|
||||||
|
@ -24,6 +24,7 @@ log = logging.getLogger(__name__)
|
|||||||
# defined in setup.py
|
# defined in setup.py
|
||||||
#
|
#
|
||||||
|
|
||||||
|
|
||||||
def load_commands(command_dir):
|
def load_commands(command_dir):
|
||||||
|
|
||||||
def get_command(name):
|
def get_command(name):
|
||||||
|
Loading…
x
Reference in New Issue
Block a user