]> Frank Brehm's Git Trees - my-stuff/py-logrotate.git/commitdiff
In Mailer mit Generierung Mail angefangen
authorFrank Brehm <frank@brehm-online.com>
Fri, 8 Jul 2011 07:17:43 +0000 (07:17 +0000)
committerFrank Brehm <frank@brehm-online.com>
Fri, 8 Jul 2011 07:17:43 +0000 (07:17 +0000)
git-svn-id: http://svn.brehm-online.com/svn/my-stuff/python/PyLogrotate/trunk@282 ec8d2aa5-1599-4edb-8739-2b3a1bc399aa

LogRotateCommon.py
LogRotateConfig.py
LogRotateHandler.py
LogRotateMailer.py
logrotate.py
po/LogRotateHandler.de.po
po/pylogrotate.de.po
po/pylogrotate.pot
test/apache2

index 6995471f0aed528d4e564e7d9f9f9346345c8678..3a13582008090860a82b7a9257259a876aef9573 100755 (executable)
@@ -18,6 +18,9 @@ import sys
 import locale
 import logging
 import gettext
+import csv
+import pprint
+import email.utils
 
 revision = '$Revision$'
 revision = re.sub( r'\$', '', revision )
@@ -427,6 +430,55 @@ def period2days(period, use_locale_radix = False, verbose = 0):
 
     return days
 
+#------------------------------------------------------------------------
+
+def get_address_list(address_str, verbose = 0):
+    '''
+    Retrieves all mail addresses from address_str and give them back
+    as a list of tuples.
+
+    @param address_str: the string with all mail addresses as a comma
+                        separated list
+    @type address_str:  str
+    @param verbose:     level of verbosity
+    @type verbose:      int
+
+    @return: list of tuples in the form of the return value
+             of email.utils.parseaddr()
+    @rtype:  list
+
+    '''
+
+    t = gettext.translation('LogRotateCommon', locale_dir, fallback=True)
+    _ = t.lgettext
+    pp = pprint.PrettyPrinter(indent=4)
+
+    addr_list = []
+    addresses = []
+
+    for row in csv.reader([address_str], doublequote=False, skipinitialspace=True):
+        for address in row:
+            addr_list.append(address)
+
+    if verbose > 2:
+        msg = _("Found address entries:") + "\n" + pp.pformat(addr_list)
+        logger.debug(msg)
+
+    for address in addr_list:
+        address = re.sub(r',', ' ', address)
+        address = re.sub(r'\s+', ' ', address)
+        pair = email.utils.parseaddr(address)
+        if verbose > 2:
+            msg = _("Got mail address pair:") + "\n" + pp.pformat(pair)
+            logger.debug(msg)
+        if not email_valid(pair[1]):
+            msg = _("Found invalid mail address '%s'.") % (address)
+            logger.warning(msg)
+            continue
+        addresses.append(pair)
+
+    return addresses
+
 #========================================================================
 
 if __name__ == "__main__":
index 6ab31375ba89ad9a9287062b38ee45f13a8e61f3..ba613deafd5bbdc4c9bca08f8deeb44ddb7ff32d 100755 (executable)
@@ -26,6 +26,7 @@ import logging
 import email.utils
 
 from LogRotateCommon import split_parts, email_valid, period2days, human2bytes
+from LogRotateCommon import get_address_list
 from LogRotateScript import LogRotateScript
 
 revision = '$Revision$'
@@ -412,7 +413,6 @@ class LogrotateConfigurationReader(object):
         self.default['ifempty']       = True
         self.default['mailaddress']   = None
         self.default['mailfirst']     = None
-        self.default['mailfrom']      = None
         self.default['maxage']        = None
         self.default['missingok']     = False
         self.default['olddir']        = {
@@ -1093,16 +1093,21 @@ class LogrotateConfigurationReader(object):
                     )
                     return False
                 return True
-            if not email_valid(val):
+            address_list = get_address_list(val, self.verbose)
+            if len(address_list):
+                directive['mailaddress'] = address_list
+            else:
                 directive['mailaddress'] = None
-                self.logger.warning( ( _("Invalid Mail address '%s'.") % (val)))
-                return False
-            directive['mailaddress'] = val
             if self.verbose > 4:
-                self.logger.debug(
-                    ( _("Setting mail address in '%(directive)s' to '%(addr)s'. (file '%(file)s', line %(lnr)s)")
-                        % {'directive': directive_str, 'addr': val, 'file': filename, 'lnr': linenr})
-                )
+                pp = pprint.PrettyPrinter(indent=4)
+                msg = _("Setting mail address in '%(directive)s' to '%(addr)s'. (file '%(file)s', line %(lnr)s)") \
+                    % {
+                        'directive': directive_str,
+                        'addr': pp.pformat(directive['mailaddress']),
+                        'file': filename,
+                        'lnr': linenr,
+                    }
+                self.logger.debug(msg)
             return True
 
         # Check for mailfirst/maillast
@@ -1945,7 +1950,6 @@ class LogrotateConfigurationReader(object):
         self.new_log['ifempty']       = self.default['ifempty']
         self.new_log['mailaddress']   = self.default['mailaddress']
         self.new_log['mailfirst']     = self.default['mailfirst']
-        self.new_log['mailfrom']      = self.default['mailfrom']
         self.new_log['maxage']        = self.default['maxage']
         self.new_log['missingok']     = self.default['missingok']
         self.new_log['olddir']        = {
index 1cddd6d518f1c3ba7ba92f0090902e58b2297c30..dda18524e067c995b1e1748c93922d14c18c2ae3 100755 (executable)
@@ -107,6 +107,7 @@ class LogrotateHandler(object):
                         pid_file     = None,
                         mail_cmd     = None,
                         local_dir    = None,
+                        version      = None,
     ):
         '''
         Constructor.
@@ -132,6 +133,8 @@ class LogrotateHandler(object):
                              are located. If None, then system default
                              (/usr/share/locale) is used.
         @type local_dir:     str or None
+        @param version:      version number to show
+        @type version:       str
 
         @return: None
         '''
@@ -160,6 +163,14 @@ class LogrotateHandler(object):
         @type: int
         '''
 
+        self.version = __version__
+        '''
+        @ivar: version number to show, e.g. as the X-Mailer version
+        @type: str
+        '''
+        if version is not None:
+            self.version = version
+
         self.test = test
         '''
         @ivar: testmode, no real actions are made
@@ -254,6 +265,21 @@ class LogrotateHandler(object):
         @type: dict
         '''
 
+        self.files2send = {}
+        '''
+        @ivar: dictionary with all all rotated logfiles to send via
+               mail to one or more recipients.
+               Keys are the file names of the (even existing) rotated
+               and maybe compressed logfiles.
+               Values are a tuple of (mailaddress, original_logfile), where
+               mailaddress is a comma separated list of mail addresses of
+               the recipients of the mails, and original_logfile is the name
+               of unrotated logfile.
+               This dict will filled by _do_rotate_file(), and will performed
+               by send_logfiles().
+        @type: dict
+        '''
+
         #################################################
         # Create a logger object
         self.logger = logging.getLogger('pylogrotate')
@@ -299,8 +325,9 @@ class LogrotateHandler(object):
         # define a mailer object
         self.mailer = LogRotateMailer(
             local_dir = self.local_dir,
-            verbose   = self.verbose,
+            verbose = self.verbose,
             test_mode = self.test,
+            mailer_version = self.version,
         )
         if mail_cmd:
             self.mailer.sendmail = mail_cmd
@@ -359,6 +386,7 @@ class LogrotateHandler(object):
             'config_file':     self.config_file,
             'files_delete':    self.files_delete,
             'files_compress':  self.files_compress,
+            'files2send':      self.files2send,
             'force':           self.force,
             'local_dir':       self.local_dir,
             'logfiles':        self.logfiles,
@@ -374,6 +402,7 @@ class LogrotateHandler(object):
             'test':            self.test,
             'template':        self.template,
             'verbose':         self.verbose,
+            'version':         self.version,
         }
         if self.state_file:
             res['state_file'] = self.state_file.as_dict()
@@ -623,6 +652,10 @@ class LogrotateHandler(object):
             self._rotate_definition(cur_desc_index)
             cur_desc_index += 1
 
+        if self.verbose > 1:
+            line = 60 * '-'
+            print line + "\n\n"
+
         # Check for left over scripts to execute
         for scriptname in self.scripts.keys():
             if self.verbose >= 4:
@@ -649,6 +682,10 @@ class LogrotateHandler(object):
 
         _ = self.t.lgettext
 
+        if self.verbose > 1:
+            line = 60 * '-'
+            print line + "\n\n"
+
         if self.verbose >= 4:
             pp = pprint.PrettyPrinter(indent=4)
             msg = _("Rotating of logfile definition:") + \
@@ -660,7 +697,9 @@ class LogrotateHandler(object):
 
         for logfile in definition['files']:
             if self.verbose > 1:
-                msg = ( _("Performing logfile '%s' ...") % (logfile)) + "\n"
+                line = 30 * '-'
+                print (line + "\n")
+                msg = ( _("Performing logfile '%s' ...") % (logfile))
                 self.logger.debug(msg)
             should_rotate = self._should_rotate(logfile, cur_desc_index)
             if self.verbose > 1:
@@ -673,6 +712,9 @@ class LogrotateHandler(object):
                 continue
             self._rotate_file(logfile, cur_desc_index)
 
+        if self.verbose > 1:
+            print "\n"
+
         return
 
     #------------------------------------------------------------
@@ -844,6 +886,11 @@ class LogrotateHandler(object):
         file_from = rotations['rotate']['from']
         file_to = rotations['rotate']['to']
 
+        # First check for an existing mail address
+        if definition['mailaddress'] and definition['mailfirst']:
+            self.mailer.send_file(file_from, definition['mailaddress'])
+
+        # separate between copy(truncate) and move (and create)
         if definition['copytruncate'] or definition['copy']:
             # Copying logfile to target
             msg = _("Copying file '%(from)s' => '%(to)s'.") \
@@ -962,6 +1009,8 @@ class LogrotateHandler(object):
         if len(files_delete):
             for oldfile in files_delete:
                 self.files_delete[oldfile] = True
+                if definition['mailaddress'] and not definition['mailfirst']:
+                    self.files2send[oldfile] = (definition['mailaddress'], logfile)
 
         # get files to compress save them back in self.files_compress
         files_compress = self._collect_files_compress(oldfiles, compress_extension, cur_desc_index)
@@ -981,7 +1030,7 @@ class LogrotateHandler(object):
         Collects a list with all old logfiles, they have to compress.
 
         @param oldfiles: a dict whith all found old logfiles as keys and
-                        their modification time as values
+                         their modification time as values
         @type oldfiles:  dict
         @param compress_extension: file extension for rotated and
                                    compressed logfiles
@@ -1908,9 +1957,6 @@ class LogrotateHandler(object):
 
         _ = self.t.lgettext
 
-        test_mode = self.test
-        test_mode = False
-
         if self.verbose > 1:
             msg = _("Compressing source '%(source)s' to target'%(target)s' with command '%(cmd)s'.") \
                     % {'source': source, 'target': target, 'cmd': command}
@@ -2192,14 +2238,12 @@ class LogrotateHandler(object):
 
         _ = self.t.lgettext
 
-        test_mode = self.test
-
         if self.verbose > 1:
             msg = _("Compressing source '%(source)s' to target'%(target)s' with module gzip.") \
                     % {'source': source, 'target': target}
             self.logger.debug(msg)
 
-        if not test_mode:
+        if not self.test:
             # open source for reading
             f_in = None
             try:
@@ -2265,14 +2309,12 @@ class LogrotateHandler(object):
 
         _ = self.t.lgettext
 
-        test_mode = self.test
-
         if self.verbose > 1:
             msg = _("Compressing source '%(source)s' to target'%(target)s' with module bz2.") \
                     % {'source': source, 'target': target}
             self.logger.debug(msg)
 
-        if not test_mode:
+        if not self.test:
             # open source for reading
             f_in = None
             try:
@@ -2318,6 +2360,25 @@ class LogrotateHandler(object):
 
         return True
 
+
+    #------------------------------------------------------------
+    def send_logfiles(self):
+        '''
+        Sending all mails, they should be sent, to their recipients.
+        '''
+
+        _ = self.t.lgettext
+
+        if self.verbose > 1:
+            pp = pprint.PrettyPrinter(indent=4)
+            msg = _("Struct files2send:") + "\n" + pp.pformat(self.files2send)
+            self.logger.debug(msg)
+
+        for filename in self.files2send.keys():
+            self.mailer.send_file(filename, self.files2send[filename][0], self.files2send[filename][1])
+
+        return
+
 #========================================================================
 
 if __name__ == "__main__":
index 7e5951b19c1ca74772964963e822f0452ca00c2e..8eb20c42d50d87626e3121377da756c0c40ccc52 100755 (executable)
@@ -22,8 +22,15 @@ import os
 import os.path
 import pwd
 import socket
+import csv
 
+import mimetypes
 import email.utils
+from email import encoders
+from email.message import Message
+from email.mime.base import MIMEBase
+from email.mime.multipart import MIMEMultipart
+from email.mime.text import MIMEText
 
 from LogRotateCommon import email_valid
 
@@ -60,18 +67,21 @@ class LogRotateMailer(object):
     def __init__( self, local_dir = None,
                         verbose   = 0,
                         test_mode = False,
+                        mailer_version = None,
     ):
         '''
         Constructor.
 
-        @param local_dir: The directory, where the i18n-files (*.mo)
-                          are located. If None, then system default
-                          (/usr/share/locale) is used.
-        @type local_dir:  str or None
-        @param verbose:   verbosity (debug) level
-        @type verbose:    int
-        @param test_mode: test mode - no write actions are made
-        @type test_mode:  bool
+        @param local_dir:      The directory, where the i18n-files (*.mo)
+                               are located. If None, then system default
+                               (/usr/share/locale) is used.
+        @type local_dir:       str or None
+        @param verbose:        verbosity (debug) level
+        @type verbose:         int
+        @param test_mode:      test mode - no write actions are made
+        @type test_mode:       bool
+        @param mailer_version: version of the X-Mailer tag in the mail header
+        @type mailer_version:  str
 
         @return: None
         '''
@@ -155,6 +165,14 @@ class LogRotateMailer(object):
         @type: str or None
         '''
 
+        self.mailer_version = __version__
+        '''
+        @ivar: version of the X-Mailer tag in the mail header
+        @type: str
+        '''
+        if mailer_version is not None:
+            self.mailer_version = mailer_version
+
     #------------------------------------------------------------
     # Defintion of some properties
 
@@ -349,17 +367,18 @@ class LogRotateMailer(object):
         '''
 
         res = {}
-        res['t']           = self.t
-        res['verbose']     = self.verbose
-        res['test_mode']   = self.test_mode
-        res['logger']      = self.logger
-        res['sendmail']    = self.sendmail
-        res['from']        = self.from_address
-        res['smtp_host']   = self.smtp_host
-        res['smtp_port']   = self.smtp_port
-        res['smtp_tls']    = self.smtp_tls
-        res['smtp_user']   = self.smtp_user
-        res['smtp_passwd'] = self.smtp_passwd
+        res['t']              = self.t
+        res['verbose']        = self.verbose
+        res['test_mode']      = self.test_mode
+        res['logger']         = self.logger
+        res['sendmail']       = self.sendmail
+        res['from']           = self.from_address
+        res['smtp_host']      = self.smtp_host
+        res['smtp_port']      = self.smtp_port
+        res['smtp_tls']       = self.smtp_tls
+        res['smtp_user']      = self.smtp_user
+        res['smtp_passwd']    = self.smtp_passwd
+        res['mailer_version'] = self.mailer_version
 
         return res
 
@@ -417,6 +436,73 @@ class LogRotateMailer(object):
 
         return
 
+    #-------------------------------------------------------
+    def send_file(self, filename, addresses, original=None,
+            mime_type='text/plain'):
+        '''
+        Mails the file with the given file name as an attachement
+        to the given recipient(s).
+
+        Raises a LogRotateMailerError on harder errors.
+
+        @param filename:  The file name of the file to send (the existing,
+                          rotated and maybe compressed logfile).
+        @type filename:   str
+        @param addresses: A list of tuples of a pair in the form
+                          of the return value of email.utils.parseaddr()
+        @type addresses:  list
+        @param original:  The file name of the original (unrotated) logfile for
+                          informational purposes.
+                          If not given, filename is used instead.
+        @type original:   str or None
+        @param mime_type: MIME type (content type) of the original logfile,
+                          defaults to 'text/plain'
+        @type mime_type:  str
+
+        @return: success of sending
+        @rtype:  bool
+        '''
+
+        _ = self.t.lgettext
+
+        if not os.path.exists(filename):
+            msg = _("File '%s' dosn't exists.") % (filename)
+            self.logger.error(msg)
+            return False
+
+        if not os.path.isfile(filename):
+            msg = _("File '%s' is not a regular file.") % (filename)
+            self.logger.warning(msg)
+            return False
+
+        basename = os.path.basename(filename)
+        if not original:
+            original = os.path.abspath(filename)
+
+        msg = _("Sending mail with attached file '%(file)s' to: %(rcpt)s") \
+                % {'file': basename,
+                   'rcpt': ', '.join(map(lambda x: '"' + email.utils.formataddr(x) + '"', addresses))}
+        self.logger.debug(msg)
+
+        mail_container = MIMEMultipart()
+        mail_container['Subject']  = ( "Rotated logfile '%s'" % (filename) )
+        mail_container['X-Mailer'] = ( "pylogrotate version %s" % (self.mailer_version) )
+        mail_container['From']     = self.from_address
+        mail_container['To']       = ', '.join(map(lambda x: email.utils.formataddr(x), addresses))
+
+        ctype, encoding = mimetypes.guess_type(filename)
+        if self.verbose > 3:
+            msg = _("Guessed content-type: '%(ctype)s' and encoding '%(encoding)s'.") \
+                    % {'ctype': ctype, 'encoding': encoding }
+            self.logger.debug(msg)
+
+        composed = mail_container.as_string()
+        if self.verbose > 2:
+            msg = _("Generated E-mail:") + "\n" + composed
+            self.logger.debug(msg)
+
+        return True
+
 #========================================================================
 
 if __name__ == "__main__":
index 65cb0303ddc3e66ec212ade38772e3cd68d4b585..695cf5a709528238879cec63fbd05113dae3e7e6 100755 (executable)
@@ -36,7 +36,7 @@ revision = re.sub( r'Revision: ', r'r', revision )
 __author__    = 'Frank Brehm'
 __copyright__ = '(C) 2011 by Frank Brehm, Berlin'
 __contact__    = 'frank@brehm-online.com'
-__version__    = '0.2.2 ' + revision
+__version__    = '0.5.1 ' + revision
 __license__    = 'GPL3'
 
 
@@ -95,7 +95,7 @@ def main():
               % {'prog': cur_proc, 'date': datetime.now().isoformat(' '), }
               ) + "\n"
 
-    sep_line = '-' * 79
+    sep_line = '=' * 79
 
     if testmode:
         print _("Test mode is ON.")
@@ -124,6 +124,7 @@ def main():
             pid_file     = opt_parser.options.pidfile,
             mail_cmd     = opt_parser.options.mailcmd,
             local_dir    = local_dir,
+            version      = __version__,
         )
     except LogrotateHandlerError, e:
         sys.stderr.write(str(e) + "\n")
@@ -144,13 +145,19 @@ def main():
     print ""
     if verbose_level > 0:
         print sep_line + "\n"
-    print _("Stage 3: deleting of old logfiles") + "\n"
+    print _("Stage 3: sending logfiles per mail") + "\n"
+    lr_handler.send_logfiles()
+
+    print ""
+    if verbose_level > 0:
+        print sep_line + "\n"
+    print _("Stage 4: deleting of old logfiles") + "\n"
     lr_handler.delete_oldfiles()
 
     print ""
     if verbose_level > 0:
         print sep_line + "\n"
-    print _("Stage 4: compression of old log files") + "\n"
+    print _("Stage 5: compression of old log files") + "\n"
     lr_handler.compress()
 
     lr_handler = None
index e766e438546f2d7e29f9b89d9261c6b335770fc3..3bb0456e100eac1e358c918c5531ebef3e5731b7 100644 (file)
@@ -339,7 +339,7 @@ msgstr "Gefundene Logdateien zum Löschen:"
 
 #: LogRotateHandler.py:1102
 msgid "No old logfiles to delete found."
-msgstr "Keile Logdateien zum Löschen gefunden."
+msgstr "Keine Logdateien zum Löschen gefunden."
 
 #: LogRotateHandler.py:1132
 #, python-format
index 4e4a5d51f2cca58c94d486ce4e8b319decae9f42..24f8c43eaf4153f53d6c470d9eed29382f78f22a 100644 (file)
@@ -54,23 +54,27 @@ msgstr "Nur Überprüfung der Konfiguration."
 msgid "Stage 1: reading configuration"
 msgstr "Phase 1: Einlesen der Konfiguration"
 
-#: logrotate.py:125
+#: logrotate.py:133
 msgid "Handler object structure"
 msgstr "Struktur des Handlerobjektes"
 
-#: logrotate.py:133
+#: logrotate.py:141
 msgid "Stage 2: underlying log rotation"
 msgstr "Phase 2: Eigentliches Rotieren"
 
-#: logrotate.py:139
-msgid "Stage 3: deleting of old logfiles"
-msgstr "Phase 3: Löschen der alten Logdateien"
+#: logrotate.py:147
+msgid "Stage 3: sending logfiles per mail"
+msgstr "Phase 3: Verschicken von Logdateien per Mail"
+
+#: logrotate.py:153
+msgid "Stage 4: deleting of old logfiles"
+msgstr "Phase 4: Löschen der alten Logdateien"
 
-#: logrotate.py:145
-msgid "Stage 4: compression of old log files"
-msgstr "Phase 4: Komprimieren der alten Logdateien"
+#: logrotate.py:159
+msgid "Stage 5: compression of old log files"
+msgstr "Phase 5: Komprimieren der alten Logdateien"
 
-#: logrotate.py:152
+#: logrotate.py:166
 #, python-format
 msgid "[%(date)s]: %(prog)s ended logrotation."
 msgstr "[%(date)s]: %(prog)s hat Logrotation beendet."
index 759c530e7497e27e7fe62a5f6ec57fb358f7a0e1..5848fba683e9bdc68450d7291c9e178f370b670d 100644 (file)
@@ -54,23 +54,27 @@ msgstr ""
 msgid "Stage 1: reading configuration"
 msgstr ""
 
-#: logrotate.py:125
+#: logrotate.py:133
 msgid "Handler object structure"
 msgstr ""
 
-#: logrotate.py:133
+#: logrotate.py:141
 msgid "Stage 2: underlying log rotation"
 msgstr ""
 
-#: logrotate.py:139
-msgid "Stage 3: deleting of old logfiles"
+#: logrotate.py:147
+msgid "Stage 3: sending logfiles per mail"
+msgstr ""
+
+#: logrotate.py:153
+msgid "Stage 4: deleting of old logfiles"
 msgstr ""
 
-#: logrotate.py:145
-msgid "Stage 4: compression of old log files"
+#: logrotate.py:159
+msgid "Stage 5: compression of old log files"
 msgstr ""
 
-#: logrotate.py:152
+#: logrotate.py:166
 #, python-format
 msgid "[%(date)s]: %(prog)s ended logrotation."
 msgstr ""
index a6129d8a7fb596226b4fc217de7ca8f6c97c9214..108ee95ebd83882d3e86ba52022e8dd358046440 100644 (file)
@@ -46,8 +46,9 @@ endscript
     delaycompress
     #start 1
     daily
-    maxage 1y
-    mail    test@uhu-banane.de
+    #maxage 1y
+    maxage 1d
+    mail    test@uhu-banane.de, Frank Brehm <frank@brehm-online.com>, "Brehm, Frank" <frank.brehm@profitbricks.com>
     noolddir
     postrotate apache_restart
 }