summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDaniel Vincze <dvincze@cloudbasesolutions.com>2018-11-07 14:49:42 +0200
committerDaniel Vincze <dvincze@cloudbasesolutions.com>2018-11-21 13:46:14 +0200
commit22e8a347c8ba914cefa26df42dde632937259a79 (patch)
tree2a398811bffbad3023ad25bed3fb80a08c9b25cf
parent059873c93255743efd3dc15e4361f306010961b5 (diff)
downloadoslo-log-22e8a347c8ba914cefa26df42dde632937259a79.tar.gz
Add config options for log rotation3.42.0
On Windows, in-use files cannot be moved or deleted. For this reason, we need the service itself to take care of rotating logs. For convenience reasons, we're exposing the built-in rotating log handlers through a set of config options. More specifically, we're adding the following new config options: - log_rotate_interval - log_rotate_interval_type - max_logfile_count - max_logfile_size_mb Change-Id: I01db4efc08e2cb64db9cbf793f3a159f54859fe7 Closes-Bug: #1802262
-rw-r--r--doc/source/admin/index.rst1
-rw-r--r--doc/source/admin/log_rotation.rst45
-rw-r--r--oslo_log/_options.py29
-rw-r--r--oslo_log/log.py32
-rw-r--r--[-rwxr-xr-x]oslo_log/tests/unit/test_log.py54
-rw-r--r--releasenotes/notes/log-rotation-595f8232cd987a6d.yaml9
6 files changed, 169 insertions, 1 deletions
diff --git a/doc/source/admin/index.rst b/doc/source/admin/index.rst
index b777abd..cc98d10 100644
--- a/doc/source/admin/index.rst
+++ b/doc/source/admin/index.rst
@@ -7,3 +7,4 @@
advanced_config
journal
+ log_rotation
diff --git a/doc/source/admin/log_rotation.rst b/doc/source/admin/log_rotation.rst
new file mode 100644
index 0000000..88abfc1
--- /dev/null
+++ b/doc/source/admin/log_rotation.rst
@@ -0,0 +1,45 @@
+=============
+ Log rotation
+=============
+
+oslo.log can work with ``logrotate``, picking up file changes once log files
+are rotated. Make sure to set the ``watch-log-file`` config option.
+
+Log rotation on Windows
+-----------------------
+
+On Windows, in-use files cannot be renamed or moved. For this reason,
+oslo.log allows setting maximum log file sizes or log rotation interval,
+in which case the service itself will take care of the log rotation (as
+opposed to having an external daemon).
+
+Configuring log rotation
+------------------------
+
+Use the following options to set a maximum log file size. In this sample,
+log files will be rotated when reaching 1GB, having at most 30 log files.
+
+.. code-block:: ini
+
+ [DEFAULT]
+ log_rotation_type = size
+ max_logfile_size_mb = 1024 # MB
+ max_logfile_count = 30
+
+The following sample configures log rotation to be performed every 12 hours.
+
+.. code-block:: ini
+
+ [DEFAULT]
+ log_rotation_type = interval
+ log_rotate_interval = 12
+ log_rotate_interval_type = H
+ max_logfile_count = 60
+
+.. note::
+
+ The time of the next rotation is computed when the service starts or when a
+ log rotation is performed, using the time of the last file modification or
+ the service start time, to which the configured log rotation interval is
+ added. This means that service restarts may delay periodic log file
+ rotations. \ No newline at end of file
diff --git a/oslo_log/_options.py b/oslo_log/_options.py
index 8f01043..5790ed5 100644
--- a/oslo_log/_options.py
+++ b/oslo_log/_options.py
@@ -111,6 +111,35 @@ generic_log_opts = [
cfg.BoolOpt('use_eventlog',
default=False,
help='Log output to Windows Event Log.'),
+ cfg.IntOpt('log_rotate_interval',
+ default=1,
+ help='The amount of time before the log files are rotated. '
+ 'This option is ignored unless log_rotation_type is set'
+ 'to "interval".'),
+ cfg.StrOpt('log_rotate_interval_type',
+ choices=['Seconds', 'Minutes', 'Hours', 'Days', 'Weekday',
+ 'Midnight'],
+ ignore_case=True,
+ default='days',
+ help='Rotation interval type. The time of the last file '
+ 'change (or the time when the service was started) is '
+ 'used when scheduling the next rotation.'),
+ cfg.IntOpt('max_logfile_count',
+ default=30,
+ help='Maximum number of rotated log files.'),
+ cfg.IntOpt('max_logfile_size_mb',
+ default=200,
+ help='Log file maximum size in MB. This option is ignored if '
+ '"log_rotation_type" is not set to "size".'),
+ cfg.StrOpt('log_rotation_type',
+ default='none',
+ choices=[('interval',
+ 'Rotate logs at predefined time intervals.'),
+ ('size',
+ 'Rotate logs once they reach a predefined size.'),
+ ('none', 'Do not rotate log files.')],
+ ignore_case=True,
+ help='Log rotation type.')
]
log_opts = [
diff --git a/oslo_log/log.py b/oslo_log/log.py
index d663ec4..8cf5d49 100644
--- a/oslo_log/log.py
+++ b/oslo_log/log.py
@@ -40,6 +40,7 @@ except ImportError:
from oslo_config import cfg
from oslo_utils import importutils
+from oslo_utils import units
import six
from six import moves
@@ -60,6 +61,15 @@ TRACE = handlers._TRACE
logging.addLevelName(TRACE, 'TRACE')
+LOG_ROTATE_INTERVAL_MAPPING = {
+ 'seconds': 's',
+ 'minutes': 'm',
+ 'hours': 'h',
+ 'days': 'd',
+ 'weekday': 'w',
+ 'midnight': 'midnight'
+}
+
def _get_log_file_path(conf, binary=None):
logfile = conf.log_file
@@ -344,13 +354,33 @@ def _setup_logging_from_conf(conf, project, version):
logpath = _get_log_file_path(conf)
if logpath:
+ # On Windows, in-use files cannot be moved or deleted.
if conf.watch_log_file and platform.system() == 'Linux':
from oslo_log import watchers
file_handler = watchers.FastWatchedFileHandler
+ filelog = file_handler(logpath)
+ elif conf.log_rotation_type.lower() == "interval":
+ file_handler = logging.handlers.TimedRotatingFileHandler
+ when = conf.log_rotate_interval_type.lower()
+ interval_type = LOG_ROTATE_INTERVAL_MAPPING[when]
+ # When weekday is configured, "when" has to be a value between
+ # 'w0'-'w6' (w0 for Monday, w1 for Tuesday, and so on)'
+ if interval_type == 'w':
+ interval_type = interval_type + str(conf.log_rotate_interval)
+ filelog = file_handler(logpath,
+ when=interval_type,
+ interval=conf.log_rotate_interval,
+ backupCount=conf.max_logfile_count)
+ elif conf.log_rotation_type.lower() == "size":
+ file_handler = logging.handlers.RotatingFileHandler
+ maxBytes = conf.max_logfile_size_mb * units.Mi
+ filelog = file_handler(logpath,
+ maxBytes=maxBytes,
+ backupCount=conf.max_logfile_count)
else:
file_handler = logging.handlers.WatchedFileHandler
+ filelog = file_handler(logpath)
- filelog = file_handler(logpath)
log_root.addHandler(filelog)
if conf.use_stderr:
diff --git a/oslo_log/tests/unit/test_log.py b/oslo_log/tests/unit/test_log.py
index aa3b98c..dcd524e 100755..100644
--- a/oslo_log/tests/unit/test_log.py
+++ b/oslo_log/tests/unit/test_log.py
@@ -47,6 +47,7 @@ from oslo_log import _options
from oslo_log import formatters
from oslo_log import handlers
from oslo_log import log
+from oslo_utils import units
MIN_LOG_INI = b"""[loggers]
@@ -107,6 +108,7 @@ class CommonLoggerTestsMixIn(object):
'%(message)s')
self.log = None
log._setup_logging_from_conf(self.config_fixture.conf, 'test', 'test')
+ self.log_handlers = log.getLogger(None).logger.handlers
def test_handlers_have_context_formatter(self):
formatters_list = []
@@ -159,6 +161,58 @@ class CommonLoggerTestsMixIn(object):
mock_logger = loggers_mock.return_value.logger
mock_logger.addHandler.assert_any_call(handler_mock.return_value)
+ @mock.patch('oslo_log.watchers.FastWatchedFileHandler')
+ @mock.patch('oslo_log.log._get_log_file_path', return_value='test.conf')
+ @mock.patch('platform.system', return_value='Linux')
+ def test_watchlog_on_linux(self, platfotm_mock, path_mock, handler_mock):
+ self.config(watch_log_file=True)
+ log._setup_logging_from_conf(self.CONF, 'test', 'test')
+ handler_mock.assert_called_once_with(path_mock.return_value)
+ self.assertEqual(self.log_handlers[0], handler_mock.return_value)
+
+ @mock.patch('logging.handlers.WatchedFileHandler')
+ @mock.patch('oslo_log.log._get_log_file_path', return_value='test.conf')
+ @mock.patch('platform.system', return_value='Windows')
+ def test_watchlog_on_windows(self, platform_mock, path_mock, handler_mock):
+ self.config(watch_log_file=True)
+ log._setup_logging_from_conf(self.CONF, 'test', 'test')
+ handler_mock.assert_called_once_with(path_mock.return_value)
+ self.assertEqual(self.log_handlers[0], handler_mock.return_value)
+
+ @mock.patch('logging.handlers.TimedRotatingFileHandler')
+ @mock.patch('oslo_log.log._get_log_file_path', return_value='test.conf')
+ def test_timed_rotate_log(self, path_mock, handler_mock):
+ rotation_type = 'interval'
+ when = 'weekday'
+ interval = 2
+ backup_count = 2
+ self.config(log_rotation_type=rotation_type,
+ log_rotate_interval=interval,
+ log_rotate_interval_type=when,
+ max_logfile_count=backup_count)
+ log._setup_logging_from_conf(self.CONF, 'test', 'test')
+ handler_mock.assert_called_once_with(path_mock.return_value,
+ when='w2',
+ interval=interval,
+ backupCount=backup_count)
+ self.assertEqual(self.log_handlers[0], handler_mock.return_value)
+
+ @mock.patch('logging.handlers.RotatingFileHandler')
+ @mock.patch('oslo_log.log._get_log_file_path', return_value='test.conf')
+ def test_rotate_log(self, path_mock, handler_mock):
+ rotation_type = 'size'
+ max_logfile_size_mb = 100
+ maxBytes = max_logfile_size_mb * units.Mi
+ backup_count = 2
+ self.config(log_rotation_type=rotation_type,
+ max_logfile_size_mb=max_logfile_size_mb,
+ max_logfile_count=backup_count)
+ log._setup_logging_from_conf(self.CONF, 'test', 'test')
+ handler_mock.assert_called_once_with(path_mock.return_value,
+ maxBytes=maxBytes,
+ backupCount=backup_count)
+ self.assertEqual(self.log_handlers[0], handler_mock.return_value)
+
class LoggerTestCase(CommonLoggerTestsMixIn, test_base.BaseTestCase):
def setUp(self):
diff --git a/releasenotes/notes/log-rotation-595f8232cd987a6d.yaml b/releasenotes/notes/log-rotation-595f8232cd987a6d.yaml
new file mode 100644
index 0000000..b66f6fb
--- /dev/null
+++ b/releasenotes/notes/log-rotation-595f8232cd987a6d.yaml
@@ -0,0 +1,9 @@
+---
+features:
+ - |
+ The following new config options will allow rotating log files,
+ especially useful on Windows:
+ * ``log_rotate_interval``
+ * ``log_rotate_interval_type``
+ * ``max_logfile_count``
+ * ``max_logfile_size_mb``