From: Frank Brehm Date: Thu, 30 Jun 2011 11:41:42 +0000 (+0000) Subject: _do_rotate_file weitergemacht X-Git-Url: https://git.uhu-banane.de/?a=commitdiff_plain;h=9391eae60000ab9f1023dd280c267363dd5b68be;p=my-stuff%2Fpy-logrotate.git _do_rotate_file weitergemacht git-svn-id: http://svn.brehm-online.com/svn/my-stuff/python/PyLogrotate/trunk@266 ec8d2aa5-1599-4edb-8739-2b3a1bc399aa --- diff --git a/LogRotateConfig.py b/LogRotateConfig.py index 0e1ff66..f584fcf 100755 --- a/LogRotateConfig.py +++ b/LogRotateConfig.py @@ -72,6 +72,7 @@ options_with_values = ( boolean_options = ( 'compress', + 'copy', 'copytruncate', 'ifempty', 'missingok', @@ -991,6 +992,28 @@ class LogrotateConfigurationReader(object): % {'option': key, 'directive': directive_str, 'value': str(option_value), 'file': filename, 'lnr': linenr}) ) directive[key] = option_value + if key == 'copy' and option_value: + if directive['copytruncate']: + msg = _("Option 'copy' disables option 'copytruncate'. (file '%(file)s', line %(lnr)s)") \ + % {'file': filename, 'lnr': linenr} + self.logger.warning(msg) + directive['copytruncate'] = False + if directive['create']['enabled']: + msg = _("Option 'copy' disables option 'create'. (file '%(file)s', line %(lnr)s)") \ + % {'file': filename, 'lnr': linenr} + self.logger.warning(msg) + directive['create']['enabled'] = False + elif key == 'copytruncate' and option_value: + if directive['copy']: + msg = _("Option 'copytruncate' disables option 'copy'. (file '%(file)s', line %(lnr)s)") \ + % {'file': filename, 'lnr': linenr} + self.logger.warning(msg) + directive['copy'] = False + if directive['create']['enabled']: + msg = _("Option 'copytruncate' disables option 'create'. (file '%(file)s', line %(lnr)s)") \ + % {'file': filename, 'lnr': linenr} + self.logger.warning(msg) + directive['create']['enabled'] = False return True # Check for integer options @@ -1262,6 +1285,20 @@ class LogrotateConfigurationReader(object): directive['create']['enabled'] = False return True + if directive['copy']: + msg = _("Option 'copy' was set, so option 'create' has no effect. (file '%(file)s', line %(lnr)s)") \ + % {'file': filename, 'lnr': linenr} + self.logger.warning(msg) + directive['create']['enabled'] = False + return True + + if directive['copytruncate']: + msg = _("Option 'copytruncate' was set, so option 'create' has no effect. (file '%(file)s', line %(lnr)s)") \ + % {'file': filename, 'lnr': linenr} + self.logger.warning(msg) + directive['create']['enabled'] = False + return True + values = [] if val is not None: values = split_parts(val) @@ -1850,6 +1887,7 @@ class LogrotateConfigurationReader(object): 'group': self.default['olddir']['group'], } self.new_log['rotate'] = self.default['rotate'] + self.new_log['sharedscripts'] = self.default['sharedscripts'] self.new_log['shred'] = self.default['shred'] self.new_log['size'] = self.default['size'] self.new_log['start'] = self.default['start'] diff --git a/LogRotateHandler.py b/LogRotateHandler.py index c212a4e..30fb196 100755 --- a/LogRotateHandler.py +++ b/LogRotateHandler.py @@ -25,6 +25,7 @@ import os.path import errno import socket import subprocess +import shutil from datetime import datetime, timedelta from LogRotateConfig import LogrotateConfigurationError @@ -223,9 +224,11 @@ class LogrotateHandler(object): ''' @ivar: list of all rotated logfiles. Each entry is a dict with three keys: - - 'original': str with the name of the unrotated file - - 'rotated': str with the name of the rotated file - - 'oldfiles: list with all old rotated files of this file + - 'original': str with the name of the unrotated file + - 'rotated': str with the name of the rotated file + - 'oldfiles: list with all old rotated files of this file + - 'desc_index': index of list self.config for appropriate + logfile definition @type: list ''' @@ -563,22 +566,27 @@ class LogrotateHandler(object): msg = _("Starting underlying rotation ...") self.logger.info(msg) - for definition in self.config: - self._rotate_definition(definition) + cur_desc_index = 0 + for d in self.config: + self._rotate_definition(cur_desc_index) + cur_desc_index += 1 return #------------------------------------------------------------ - def _rotate_definition(self, definition): + def _rotate_definition(self, cur_desc_index): ''' Rotation of a logfile definition from a configuration file. - @param definition: definitions from configuration file - @type definition: dict + @param cur_desc_index: index of self.config for definition + of logfile from configuration file + @type cur_desc_index: int @return: None ''' + definition = self.config[cur_desc_index] + _ = self.t.lgettext if self.verbose >= 4: @@ -591,7 +599,7 @@ class LogrotateHandler(object): if self.verbose > 1: msg = ( _("Performing logfile '%s' ...") % (logfile)) + "\n" self.logger.debug(msg) - should_rotate = self._should_rotate(logfile, definition) + should_rotate = self._should_rotate(logfile, cur_desc_index) if self.verbose > 1: if should_rotate: msg = _("logfile '%s' WILL rotated.") @@ -600,12 +608,12 @@ class LogrotateHandler(object): self.logger.debug(msg % (logfile)) if not should_rotate: continue - self._rotate_file(logfile, definition) + self._rotate_file(logfile, cur_desc_index) return #------------------------------------------------------------ - def _rotate_file(self, logfile, definition): + def _rotate_file(self, logfile, cur_desc_index): ''' Rotates a logfile with all with all necessary actions before and after rotation. @@ -614,12 +622,15 @@ class LogrotateHandler(object): @param logfile: the logfile to rotate @type logfile: str - @param definition: definitions from configuration file - @type definition: dict + @param cur_desc_index: index of self.config for definition + of logfile from configuration file + @type cur_desc_index: int @return: None ''' + definition = self.config[cur_desc_index] + _ = self.t.lgettext sharedscripts = definition['sharedscripts'] @@ -645,15 +656,15 @@ class LogrotateHandler(object): # Executing prerotate scripts ... # bla bla bla - olddir = self._create_olddir(logfile, definition) + olddir = self._create_olddir(logfile, cur_desc_index) if olddir is None: return - if not self._do_rotate_file(logfile, definition, olddir): + if not self._do_rotate_file(logfile, cur_desc_index, olddir): return #------------------------------------------------------------ - def _do_rotate_file(self, logfile, definition, olddir = None): + def _do_rotate_file(self, logfile, cur_desc_index, olddir = None): ''' The underlaying unconditionally rotation of a logfile. @@ -661,8 +672,9 @@ class LogrotateHandler(object): @param logfile: the logfile to rotate @type logfile: str - @param definition: definitions from configuration file - @type definition: dict + @param cur_desc_index: index of self.config for definition + of logfile from configuration file + @type cur_desc_index: int @param olddir: the directory of the rotated logfile if "." or None, store the rotated logfile in their original directory @@ -672,6 +684,8 @@ class LogrotateHandler(object): @rtype: bool ''' + definition = self.config[cur_desc_index] + if (olddir is not None) and (olddir == "."): olddir = None @@ -683,13 +697,142 @@ class LogrotateHandler(object): msg = _("Do rotate logfile '%s' ...") % (logfile) self.logger.debug(msg) - target = self._get_rotation_target(logfile, definition, olddir) - rotations = self._get_rotations(logfile, target, definition) + target = self._get_rotation_target(logfile, cur_desc_index, olddir) + rotations = self._get_rotations(logfile, target, cur_desc_index) + + compress_extension = rotations['compress_extension'] + + # First move all cyclic stuff + for pair in rotations['move']: + file_from = pair['from'] + file_to = pair['to'] + if pair['compressed']: + file_from + compress_extension + file_to + compress_extension + msg = _("Moving file '%(from)s' => '%(to)'.") \ + % {'from': file_from, 'to': file_to } + self.logger.info(msg) + if not self.test: + try: + shutil.move(file_from, file_to) + except OSError: + msg = _("Error on moving '%(from)s' => '%(to)s': %(err)s") \ + % {'from': file_from, 'to': file_to, 'err': e.strerror} + self.logger.error(msg) + return False + + # Now the underlaying rotation + file_from = rotations['rotate']['from'] + file_to = rotations['rotate']['to'] + + if definition['copytruncate'] or definition['copy']: + # Copying logfile to target + msg = _("Copying file '%(from)s' => '%(to)'.") \ + % {'from': file_from, 'to': file_to } + self.logger.info(msg) + if not self.test: + try: + shutil.copy2(file_from, file_to) + except OSError: + msg = _("Error on copying '%(from)s' => '%(to)s': %(err)s") \ + % {'from': file_from, 'to': file_to, 'err': e.strerror} + self.logger.error(msg) + return False + if definition['copytruncate']: + msg = _("Truncating file '%s'.") % (file_from) + self.logger.info(msg) + if not self.test: + try: + fd = open(file_from, 'w') + fd.close() + except IOError, e: + msg = _("Error on truncing file '%(from)s': %(err)s") \ + % {'from': file_from, 'err': str(e)} + self.logger.error(msg) + return False + + else: + + # Moving logfile to target + msg = _("Moving file '%(from)s' => '%(to)'.") \ + % {'from': file_from, 'to': file_to } + self.logger.info(msg) + + # get old permissions of logfile + statinfo = os.stat(file_from) + + if not self.test: + try: + shutil.move(file_from, file_to) + except OSError: + msg = _("Error on moving '%(from)s' => '%(to)s': %(err)s") \ + % {'from': file_from, 'to': file_to, 'err': e.strerror} + self.logger.error(msg) + return False + + if definition['create']['enabled']: + + # Recreate logfile + msg = _("Recreating file '%s'.") % (file_from) + self.logger.info(msg) + if not self.test: + try: + fd = open(file_from, 'w') + fd.close() + except IOError, e: + msg = _("Error on creating file '%(from)s': %(err)s") \ + % {'from': file_from, 'err': str(e)} + self.logger.error(msg) + return False + + # Setting permissions and ownership + new_mode = statinfo.st_mode + new_uid = statinfo.st_uid + new_gid = statinfo.st_gid + + if not definition['create']['mode'] is None: + new_mode = definition['create']['mode'] + if not definition['create']['owner'] is None: + new_uid = definition['create']['owner'] + if not definition['create']['group'] is None: + new_gid = definition['create']['group'] + + statinfo = os.stat(file_from) + old_mode = statinfo.st_mode + old_uid = statinfo.st_uid + old_gid = statinfo.st_gid + + # Check and set permissions of new logfile + if new_mode != old_mode: + msg = _("Setting permissions of '%(file)s' to %(mode)4o.") \ + % {'file': file_from, 'mode': new_mode} + self.logger.info(msg) + if not self.test: + try: + os.chmod(file_from, new_mode) + except OSError, e: + msg = _("Error on chmod of '%(file)s': %(err)s") \ + % {'file': file_from, 'err': e.strerror} + self.logger.warning(msg) + + # Check and set ownership of new logfile + if (new_uid != old_uid) or (new_gid != old_gid): + msg = _("Setting ownership of '%(file)s' to uid %(uid)d and gid %(gid)d.") \ + % {'file': file_from, 'uid': new_uid, 'gid': new_gid} + self.logger.info(msg) + if not self.test: + try: + os.chown(file_from, new_uid, new_gid) + except OSError, e: + msg = _("Error on chown of '%(file)s': %(err)s") \ + % {'file': file_from, 'err': e.strerror} + self.logger.warning(msg) + return True #------------------------------------------------------------ - def _get_rotations(self, logfile, target, definition): + def _get_rotations(self, logfile, target, cur_desc_index): ''' Retrieves all files to move and to rotate and gives them back as a dict. @@ -698,31 +841,106 @@ class LogrotateHandler(object): @type logfile: str @param target: name of the rotated logfile @type target: str - @param definition: definitions from configuration file - @type definition: dict + @param cur_desc_index: index of self.config for definition + of logfile from configuration file + @type cur_desc_index: int @return: dict in the form:: { + 'compress_extension': '.gz', 'rotate': { 'from': , 'to': }, 'move': [ ... - { 'from': , 'to': }, - { 'from': , 'to': }, - { 'from': , 'to': }, + { 'from': , 'to': , 'compressed': True}, + { 'from': , 'to': , 'compressed': True}, + { 'from': , 'to': , 'compressed': False}, ], } - the order in the list 'move' is the order, how the - files have to rename. + + the order in the list 'move' is the order, how the + files have to rename. @rtype: dict ''' + definition = self.config[cur_desc_index] + _ = self.t.lgettext + if self.verbose > 2: + msg = _("Retrieving all movings and rotations for logfile '%(file)s' to target '%(target)s' ...") \ + % {'file': logfile, 'target': target} + self.logger.debug(msg) + result = { 'rotate': {}, 'move': [] } + # retrieve additional file extension of logfile after rotation + # without compress extension + extension = definition['extension'] + if extension is None: + extension = '' + match = re.search(r'^\s*$', extension) + if match: + extension = '' + if extension != '': + match = re.search(r'^\.', extension) + if not match: + extension = "." + extension + extension_wo_compress = extension + + # retrieve additional file extension of logfile after rotation + # for compress extension + compress_extension = '' + if definition['compress']: + compress_extension = definition['compress_ext'] + match = re.search(r'^\.', compress_extension) + if not match: + compress_extension = "." + compress_extension + result['compress_extension'] = compress_extension + + # appending a trailing '.0', if there are no other differences + # between logfile and target + i = definition['start'] + if i is None: + i = 0 + resulting_target = target + extension_wo_compress + target_wo_number = resulting_target + if resulting_target == logfile: + resulting_target = resulting_target + "." + str(i) + + result['rotate']['from'] = logfile + result['rotate']['to'] = resulting_target + + # resulting target exists, retrieve cyclic rotation + if os.path.exists(resulting_target): + if self.verbose > 3: + msg = _("Resulting target '%s' exists, retrieve cyclic rotation ...") \ + % (resulting_target) + self.logger.debug(msg) + target_wo_cext_old = target_wo_number + "." + str(i) + target_with_cext_old = target_wo_cext_old + compress_extension + while os.path.exists(target_wo_cext_old) or os.path.exists(target_with_cext_old): + i += 1 + target_wo_cext_new = target_wo_number + "." + str(i) + target_with_cext_new = target_wo_cext_new + compress_extension + if self.verbose > 4: + msg = _("Cyclic rotation from '%(from)s' to '%(to)s'.") \ + % {'from': target_wo_cext_old, 'to': target_wo_cext_new} + self.logger.debug(msg) + pair = { + 'from': target_wo_cext_old, + 'to': target_wo_cext_new, + 'compressed': False, + } + if definition['compress']: + if os.path.exists(target_with_cext_old): + pair['compressed'] = True + result['move'].insert(0, pair) + target_wo_cext_old = target_wo_cext_new + target_with_cext_old = target_with_cext_new + if self.verbose > 3: pp = pprint.PrettyPrinter(indent=4) msg = _("Found rotations:") + "\n" + pp.pformat(result) @@ -730,14 +948,15 @@ class LogrotateHandler(object): return result #------------------------------------------------------------ - def _get_rotation_target(self, logfile, definition, olddir = None): + def _get_rotation_target(self, logfile, cur_desc_index, olddir = None): ''' Retrieves the name of the rotated logfile and gives it back. @param logfile: the logfile to rotate @type logfile: str - @param definition: definitions from configuration file - @type definition: dict + @param cur_desc_index: index of self.config for definition + of logfile from configuration file + @type cur_desc_index: int @param olddir: the directory of the rotated logfile if None, store the rotated logfile in their original directory @@ -747,6 +966,8 @@ class LogrotateHandler(object): @rtype: str ''' + definition = self.config[cur_desc_index] + _ = self.t.lgettext if self.verbose > 2: @@ -776,14 +997,15 @@ class LogrotateHandler(object): return target #------------------------------------------------------------ - def _create_olddir(self, logfile, definition): + def _create_olddir(self, logfile, cur_desc_index): ''' Creating the olddir, if necessary. @param logfile: the logfile to rotate @type logfile: str - @param definition: definitions from configuration file (for olddir) - @type definition: dict + @param cur_desc_index: index of self.config for definition + of logfile from configuration file + @type cur_desc_index: int @return: Name of the retrieved olddir, ".", if storing the rotated logfiles in their original directory or @@ -792,6 +1014,8 @@ class LogrotateHandler(object): @rtype: str or None ''' + definition = self.config[cur_desc_index] + _ = self.t.lgettext uid = os.geteuid() @@ -987,7 +1211,7 @@ class LogrotateHandler(object): return False #------------------------------------------------------------ - def _should_rotate(self, logfile, definition): + def _should_rotate(self, logfile, cur_desc_index): ''' Determines, whether a logfile should rotated dependend on the informations in the definition. @@ -996,13 +1220,16 @@ class LogrotateHandler(object): @param logfile: the logfile to inspect @type logfile: str - @param definition: definitions from configuration file - @type definition: dict + @param cur_desc_index: index of self.config for definition + of logfile from configuration file + @type cur_desc_index: int @return: to rotate or not @rtype: bool ''' + definition = self.config[cur_desc_index] + _ = self.t.lgettext if self.verbose > 2: diff --git a/po/LogRotateConfig.de.po b/po/LogRotateConfig.de.po index f059ceb..0b3463e 100644 --- a/po/LogRotateConfig.de.po +++ b/po/LogRotateConfig.de.po @@ -234,6 +234,42 @@ msgstr "" "Setze boolsche Option »%(option)s« in »%(directive)s« auf »%(value)s«. " "(Datei »%(file)s«, Zeile %(lnr)s)" +#: LogRotateConfig.py:997 +#, python-format +msgid "" +"Option 'copy' disables option 'copytruncate'. " +"(file '%(file)s', line %(lnr)s)" +msgstr "" +"Die Option »copy« setzt die Option »copytruncate« außer Kraft." +"(Datei »%(file)s«, Zeile %(lnr)s)" + +#: LogRotateConfig.py:1001 +#, python-format +msgid "" +"Option 'copy' disables option 'create'. " +"(file '%(file)s', line %(lnr)s)" +msgstr "" +"Die Option »copy« setzt die Option »create« außer Kraft." +"(Datei »%(file)s«, Zeile %(lnr)s)" + +#: LogRotateConfig.py:1008 +#, python-format +msgid "" +"Option 'copytruncate' disables option 'copy'. " +"(file '%(file)s', line %(lnr)s)" +msgstr "" +"Die Option »copytruncate« setzt die Option »copy« außer Kraft." +"(Datei »%(file)s«, Zeile %(lnr)s)" + +#: LogRotateConfig.py:1013 +#, python-format +msgid "" +"Option 'copytruncate' disables option 'create'. " +"(file '%(file)s', line %(lnr)s)" +msgstr "" +"Die Option »copytruncate« setzt die Option »create« außer Kraft." +"(Datei »%(file)s«, Zeile %(lnr)s)" + #: LogRotateConfig.py:959 #, python-format msgid "Option '%s' without a necessary value." @@ -398,6 +434,24 @@ msgstr "Schaue nach »create« ... (Datei »%(file)s«, Zeile %(lnr)s)" msgid "Removing 'create'. (file '%(file)s', line %(lnr)s)" msgstr "Entferne »create«. (Datei »%(file)s«, Zeile %(lnr)s)" +#: LogRotateConfig.py:1289 +#, python-format +msgid "" +"Option 'copy' was set, so option 'create' has no effect. " +"(file '%(file)s', line %(lnr)s)" +msgstr "" +"Die Option »copy« wurde gesetzt, dadurch hat die Option »create« keinen Effekt. " +"(Datei »%(file)s«, Zeile %(lnr)s)" + +#: LogRotateConfig.py:1296 +#, python-format +msgid "" +"Option 'copytruncate' was set, so option 'create' has no effect. " +"(file '%(file)s', line %(lnr)s)" +msgstr "" +"Die Option »copytruncate« wurde gesetzt, dadurch hat die Option »create« keinen Effekt. " +"(Datei »%(file)s«, Zeile %(lnr)s)" + #: LogRotateConfig.py:1232 #, python-format msgid "" diff --git a/po/LogRotateConfig.pot b/po/LogRotateConfig.pot index 1ed94b7..59d39eb 100644 --- a/po/LogRotateConfig.pot +++ b/po/LogRotateConfig.pot @@ -204,6 +204,34 @@ msgid "" "%(lnr)s)" msgstr "" +#: LogRotateConfig.py:997 +#, python-format +msgid "" +"Option 'copy' disables option 'copytruncate'. " +"(file '%(file)s', line %(lnr)s)" +msgstr "" + +#: LogRotateConfig.py:1001 +#, python-format +msgid "" +"Option 'copy' disables option 'create'. " +"(file '%(file)s', line %(lnr)s)" +msgstr "" + +#: LogRotateConfig.py:1008 +#, python-format +msgid "" +"Option 'copytruncate' disables option 'copy'. " +"(file '%(file)s', line %(lnr)s)" +msgstr "" + +#: LogRotateConfig.py:1013 +#, python-format +msgid "" +"Option 'copytruncate' disables option 'create'. " +"(file '%(file)s', line %(lnr)s)" +msgstr "" + #: LogRotateConfig.py:959 #, python-format msgid "Option '%s' without a necessary value." @@ -342,6 +370,20 @@ msgstr "" msgid "Removing 'create'. (file '%(file)s', line %(lnr)s)" msgstr "" +#: LogRotateConfig.py:1289 +#, python-format +msgid "" +"Option 'copy' was set, so option 'create' has no effect. " +"(file '%(file)s', line %(lnr)s)" +msgstr "" + +#: LogRotateConfig.py:1296 +#, python-format +msgid "" +"Option 'copytruncate' was set, so option 'create' has no effect. " +"(file '%(file)s', line %(lnr)s)" +msgstr "" + #: LogRotateConfig.py:1232 #, python-format msgid "Trying to determine create mode '%(mode)s'... (file '%(file)s', line %(lnr)s)" diff --git a/test/apache2 b/test/apache2 index 0e786a4..4d6450f 100644 --- a/test/apache2 +++ b/test/apache2 @@ -5,6 +5,10 @@ pidfile /home/frank/Development/Python/PyLogrotate/logrotate.pid statusfile /home/frank/Development/Python/PyLogrotate/logrotate.status +script apache_restart + echo "/etc/init.d/apache2 reload > /dev/null 2>&1 || true" +endscript + /home/frank/devel/Python/PyLogrotate/test/log/access_log { compress missingok @@ -18,9 +22,22 @@ statusfile /home/frank/Development/Python/PyLogrotate/logrotate.status mail test@uhu-banane.de #olddir %Y-%m 0755 apache users olddir %Y-%m - postrotate - echo "/etc/init.d/apache2 reload > /dev/null 2>&1 || true" - endscript + postrotate apache_restart +} + +/home/frank/devel/Python/PyLogrotate/test/log/error_log { + compress + nomissingok + notifempty + sharedscripts + rotate 20 + nodateext + #start 1 + daily + maxage 1y + mail test@uhu-banane.de + noolddir + postrotate apache_restart } /home/frank/devel/Python/PyLogrotate/test/log/*.log {