import locale
import logging
import gettext
+import csv
+import pprint
+import email.utils
revision = '$Revision$'
revision = re.sub( r'\$', '', revision )
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__":
import email.utils
from LogRotateCommon import split_parts, email_valid, period2days, human2bytes
+from LogRotateCommon import get_address_list
from LogRotateScript import LogRotateScript
revision = '$Revision$'
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'] = {
)
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
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'] = {
pid_file = None,
mail_cmd = None,
local_dir = None,
+ version = None,
):
'''
Constructor.
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
'''
@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
@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')
# 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
'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,
'test': self.test,
'template': self.template,
'verbose': self.verbose,
+ 'version': self.version,
}
if self.state_file:
res['state_file'] = self.state_file.as_dict()
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:
_ = 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:") + \
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:
continue
self._rotate_file(logfile, cur_desc_index)
+ if self.verbose > 1:
+ print "\n"
+
return
#------------------------------------------------------------
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'.") \
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)
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
_ = 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}
_ = 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:
_ = 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:
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__":
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
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
'''
@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
'''
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
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__":
__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'
% {'prog': cur_proc, 'date': datetime.now().isoformat(' '), }
) + "\n"
- sep_line = '-' * 79
+ sep_line = '=' * 79
if testmode:
print _("Test mode is ON.")
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")
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
#: 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
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."
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 ""
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
}