# # Python module for common functions related logging and error notification. # import Setup, time, sys, traceback, select, types, os, popen2 # Reading from a process' buffer will block until the buffer has been # entirely read, or the stream has closed. For this reason, we must # set the buffer really small. PROC_BUFFSIZE = 1024 PIPE_READ_PAUSE_LENGTH = 1 PIPE_READ_ATTEMPTS = 4 NEWLINE = '\n' # Flag used for identifying if there was a fatal error _FATAL_COUNT = 0 # Logging data FATAL = 0 ERR = 1 WARN = 2 INFO = 3 VERBOSE = 4 DEBUG = 5 _LOGLEVEL_TEXT = ('FATAL', 'ERR', 'WARN', 'INFO', 'VERBOSE', 'DEBUG') def log(level, msg, sendEmail=True): ''' Logs a message to the daemon log file. Use this over "print". ''' if not isinstance(level, int): level = VERBOSE if level <= FATAL: mod = sys.modules['cutil'] mod._FATAL_COUNT = mod._FATAL_COUNT + 1 if level <= Setup.LOGGING.logLevel: outmsg = "%s [%s] " % (time.strftime(Setup.LOGGING.timeFormat), _LOGLEVEL_TEXT[level]) if isinstance(msg, list) or isinstance(msg, tuple): for x in msg: outmsg = outmsg + str(x) else: outmsg = outmsg + str(msg) f = file(Setup.LOGGING.logfile, 'a') try: f.write(outmsg + NEWLINE) finally: f.close() if sendEmail and level <= Setup.LOGGING.emailLogLevel: emailProblem(outmsg) def hasFatal(): ''' Checks if a FATAL error was reported. This can be used to signal the Daemon process to stop running because a critical event could not be properly handled. Returns True if a FATAL message is known, otherwise False. ''' mod = sys.modules['cutil'] return mod._FATAL_COUNT > 0 def clearFatal(): ''' Clears the current fatal status. ''' mod = sys.modules['cutil'] mod._FATAL_COUNT = 0 def normalPopen(cmd): ''' Called just like Popen, but doesn't set non-blocking. ''' conn = popen2.Popen4(cmd, PROC_BUFFSIZE) return conn def read_write(inStream, outStream, outputLines, timeout = 0, lines = None, retries = 3): ''' Attempts to read from inStream then write the outputLines to outStream. This will block up to timeout seconds (0 means never block; this is a floating-point value). Returns list of lines that were read in. ''' log(DEBUG, "enter read_write") if lines is None: lines = [] infdL = [] outfdL = [] infd = None outfd = None if inStream: infd = inStream.fileno() infdL = [ infd ] if outStream: outfd = outStream.fileno() outfdL = [ outfd ] outputBytes = '' if outputLines: if type(outputLines) in types.StringTypes: outputBytes = outputLines + NEWLINE else: for x in outputLines: outputBytes = outputBytes + x + NEWLINE log(DEBUG, ("-- sending data [", outputBytes, "]")) count = len(outputBytes) wrote = 0 ret = '' loops = 0 anyRead = True while anyRead or wrote < count: anyRead = False if timeout is not None and timeout >= 0: fdLL = select.select(infdL, outfdL, [], timeout) else: fdLL = select.select(infdL, outfdL, []) if len(fdLL[0]) > 0: # read input log(DEBUG, "-- Reading data from stream") # should use the stream to read, but... #ret = ret + inStream.read(1024) r = os.read(infd, 1024) if r: log(DEBUG, (" -- [", r, "]")) ret = ret + r anyRead = True if len(fdLL[1]) > 0: # write after the read log(DEBUG, ("-- writing ", len(outputBytes), " bytes of data to stream ",outfd, " (type ",type(outfd),")")) # should use the stream to write, but... #x = outStream.write(outputBytes) x = os.write(outfd, outputBytes) if x is None: x = 0 wrote = wrote + x if wrote < count: outputBytes = outputBytes[x:] loops = loops + 1 if loops > retries: raise IOError(32, 'Too many retries to write data') else: # no more writes: eliminate it from our select outfd = [] lines.extend(ret.splitlines()) log(DEBUG, ("-- leaving read_write w/ ", len(lines), " lines read")) return lines def log_error(level=ERR, sendEmail=True): """ Print a stack trace usefull for debugging. """ info = sys.exc_info() log(level, traceback.format_exception(info[0],info[1],info[2]), sendEmail) def emailProblem(msg): send_email(Setup.LOGGING.adminEmail, Setup.LOGGING.emailErrorSubject, msg) def _connectMailServer(host): ''' Allow for better unit tests by being able to mock this method out ''' import smtplib return smtplib.SMTP(host) def send_email(toEmails, subject, body): ''' Send a plain-text e-mail ''' mailhost = Setup.LOGGING.smtpHost if mailhost is None or len(mailhost) <= 0: log(VERBOSE, 'Sending e-mails is disabled.') return COMMASPACE = ', ' try: #print "connecting to server %s: %s" % (mailhost, str(_connectMailServer)) mailport = _connectMailServer(mailhost) except: log(ERR, 'Unable to connect to SMTP host "' + mailhost + '"!', False) log_error(sendEmail=False) return if Setup.LOGGING.emailReplyTo: replyto_line = 'Reply-To: %s\n' % Setup.LOGGING.emailReplyTo else: replyto_line = '' if type(toEmails) in types.StringTypes: toEmails = [ toEmails ] message = 'From: %s\n' +\ 'To: %s\n' +\ 'Subject: %s %s\n' +\ '%s' +\ '\n' +\ '%s' #print 'message body = [%s]' % message message = message % ( Setup.LOGGING.emailSender, COMMASPACE.join(toEmails), Setup.LOGGING.emailSubjectPrefix, subject, replyto_line, body) try: failed = mailport.sendmail(Setup.LOGGING.emailSender, toEmails, message) if failed: log(ERR, ['Problem sending email to SMTP host: ', repr(failed)], False) except: log(ERR, 'Problem sending email to SMTP host "' + mailhost + '"!', False) log_error(sendEmail=False) try: mailport.quit() except: log_error(sendEmail=False)