[Top][All Lists]
[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[Rdiff-backup-commits] Changes to rdiff-backup/rdiff_backup/log.py [r1-0
From: |
Ben Escoto |
Subject: |
[Rdiff-backup-commits] Changes to rdiff-backup/rdiff_backup/log.py [r1-0] |
Date: |
Sat, 12 Nov 2005 00:40:01 -0500 |
Index: rdiff-backup/rdiff_backup/log.py
diff -u /dev/null rdiff-backup/rdiff_backup/log.py:1.19.2.1
--- /dev/null Sat Nov 12 05:40:01 2005
+++ rdiff-backup/rdiff_backup/log.py Sat Nov 12 05:40:01 2005
@@ -0,0 +1,270 @@
+# Copyright 2002 Ben Escoto
+#
+# This file is part of rdiff-backup.
+#
+# rdiff-backup is free software; you can redistribute it and/or modify
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# rdiff-backup is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with rdiff-backup; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+# USA
+
+"""Manage logging, displaying and recording messages with required verbosity"""
+
+import time, sys, traceback, types
+import Globals, static, re
+
+
+class LoggerError(Exception): pass
+
+class Logger:
+ """All functions which deal with logging"""
+ def __init__(self):
+ self.log_file_open = None
+ self.log_file_local = None
+ self.verbosity = self.term_verbosity = 3
+ # termverbset is true if the term_verbosity has been explicity
set
+ self.termverbset = None
+
+ def setverbosity(self, verbosity_string):
+ """Set verbosity levels. Takes a number string"""
+ try: self.verbosity = int(verbosity_string)
+ except ValueError:
+ Log.FatalError("Verbosity must be a number, received
'%s' "
+ "instead." %
verbosity_string)
+ if not self.termverbset: self.term_verbosity = self.verbosity
+
+ def setterm_verbosity(self, termverb_string):
+ """Set verbosity to terminal. Takes a number string"""
+ try: self.term_verbosity = int(termverb_string)
+ except ValueError:
+ Log.FatalError("Terminal verbosity must be a number,
received "
+ "'%s' instead." %
termverb_string)
+ self.termverbset = 1
+
+ def open_logfile(self, rpath):
+ """Inform all connections of an open logfile.
+
+ rpath.conn will write to the file, and the others will pass
+ write commands off to it.
+
+ """
+ assert not self.log_file_open
+ rpath.conn.log.Log.open_logfile_local(rpath)
+ for conn in Globals.connections:
+ conn.log.Log.open_logfile_allconn(rpath.conn)
+
+ def open_logfile_allconn(self, log_file_conn):
+ """Run on all connections to signal log file is open"""
+ self.log_file_open = 1
+ self.log_file_conn = log_file_conn
+
+ def open_logfile_local(self, rpath):
+ """Open logfile locally - should only be run on one
connection"""
+ assert rpath.conn is Globals.local_connection
+ try: self.logfp = rpath.open("a")
+ except (OSError, IOError), e:
+ raise LoggerError("Unable to open logfile %s: %s"
+ % (rpath.path, e))
+ self.log_file_local = 1
+ self.logrp = rpath
+
+ def close_logfile(self):
+ """Close logfile and inform all connections"""
+ if self.log_file_open:
+ for conn in Globals.connections:
+ conn.log.Log.close_logfile_allconn()
+ self.log_file_conn.log.Log.close_logfile_local()
+
+ def close_logfile_allconn(self):
+ """Run on every connection"""
+ self.log_file_open = None
+
+ def close_logfile_local(self):
+ """Run by logging connection - close logfile"""
+ assert self.log_file_conn is Globals.local_connection
+ assert not self.logfp.close()
+ self.log_file_local = None
+
+ def format(self, message, verbosity):
+ """Format the message, possibly adding date information"""
+ if verbosity < 9: return message + "\n"
+ else: return "%s %s\n" %
(time.asctime(time.localtime(time.time())),
+ message)
+
+ def __call__(self, message, verbosity):
+ """Log message that has verbosity importance
+
+ message can be a string, which is logged as-is, or a function,
+ which is then called and should return the string to be
+ logged. We do it this way in case producing the string would
+ take a significant amount of CPU.
+
+ """
+ if verbosity > self.verbosity and verbosity >
self.term_verbosity:
+ return
+
+ if not type(message) is types.StringType:
+ assert type(message) is types.FunctionType
+ message = message()
+
+ if verbosity <= self.verbosity: self.log_to_file(message)
+ if verbosity <= self.term_verbosity:
+ self.log_to_term(message, verbosity)
+
+ def log_to_file(self, message):
+ """Write the message to the log file, if possible"""
+ if self.log_file_open:
+ if self.log_file_local:
+ self.logfp.write(self.format(message,
self.verbosity))
+ self.logfp.flush()
+ else: self.log_file_conn.log.Log.log_to_file(message)
+
+ def log_to_term(self, message, verbosity):
+ """Write message to stdout/stderr"""
+ if verbosity <= 2 or Globals.server: termfp = sys.stderr
+ else: termfp = sys.stdout
+ termfp.write(self.format(message, self.term_verbosity))
+
+ def conn(self, direction, result, req_num):
+ """Log some data on the connection
+
+ The main worry with this function is that something in here
+ will create more network traffic, which will spiral to
+ infinite regress. So, for instance, logging must only be done
+ to the terminal, because otherwise the log file may be remote.
+
+ """
+ if self.term_verbosity < 9: return
+ if type(result) is types.StringType: result_repr = repr(result)
+ else: result_repr = str(result)
+ if Globals.server: conn_str = "Server"
+ else: conn_str = "Client"
+ self.log_to_term("%s %s (%d): %s" %
+ (conn_str, direction, req_num,
result_repr), 9)
+
+ def FatalError(self, message, no_fatal_message = 0, errlevel = 1):
+ """Log a fatal error and exit"""
+ assert no_fatal_message == 0 or no_fatal_message == 1
+ if no_fatal_message: prefix_string = ""
+ else: prefix_string = "Fatal Error: "
+ self(prefix_string + message, 1)
+ import Main
+ Main.cleanup()
+ sys.exit(errlevel)
+
+ def exception_to_string(self, arglist = []):
+ """Return string version of current exception plus what's in
arglist"""
+ type, value, tb = sys.exc_info()
+ s = ("Exception '%s' raised of class '%s':\n%s" %
+ (value, type, "".join(traceback.format_tb(tb))))
+ if arglist:
+ s += "__Arguments:\n" + "\n".join(map(str, arglist))
+ return s
+
+ def exception(self, only_terminal = 0, verbosity = 5):
+ """Log an exception and traceback
+
+ If only_terminal is None, log normally. If it is 1, then only
+ log to disk if log file is local (self.log_file_open = 1). If
+ it is 2, don't log to disk at all.
+
+ """
+ assert only_terminal in (0, 1, 2)
+ if (only_terminal == 0 or
+ (only_terminal == 1 and self.log_file_open)):
+ logging_func = self.__call__
+ else: logging_func = self.log_to_term
+
+ logging_func(self.exception_to_string(), verbosity)
+
+Log = Logger()
+
+
+class ErrorLog:
+ """Log each recoverable error in error_log file
+
+ There are three types of recoverable errors: ListError, which
+ happens trying to list a directory or stat a file, UpdateError,
+ which happen when trying to update a changed file, and
+ SpecialFileError, which happen when a special file cannot be
+ created. See the error policy file for more info.
+
+ """
+ _log_fileobj = None
+ _log_inc_rp = None
+ def open(cls, time_string, compress = 1):
+ """Open the error log, prepare for writing"""
+ if not Globals.isbackup_writer:
+ return
Globals.backup_writer.log.ErrorLog.open(time_string,
+
compress)
+ assert not cls._log_fileobj and not cls._log_inc_rp, "log
already open"
+ assert Globals.isbackup_writer
+ if compress: typestr = 'data.gz'
+ else: typestr = 'data'
+ cls._log_inc_rp = Globals.rbdir.append("error_log.%s.%s" %
+
(time_string, typestr))
+ assert not cls._log_inc_rp.lstat(), ("""Error file %s already
exists.
+
+This is probably caused by your attempting to run two backups simultaneously
+or within one second of each other. Wait a second and try again.""" %
+
(cls._log_inc_rp.path,))
+ cls._log_fileobj = cls._log_inc_rp.open("wb", compress =
compress)
+
+ def isopen(cls):
+ """True if the error log file is currently open"""
+ if Globals.isbackup_writer or not Globals.backup_writer:
+ return cls._log_fileobj is not None
+ else: return Globals.backup_writer.log.ErrorLog.isopen()
+
+ def write(cls, error_type, rp, exc):
+ """Add line to log file indicating error exc with file rp"""
+ if not Globals.isbackup_writer:
+ return
Globals.backup_writer.log.ErrorLog.write(error_type,
+
rp, exc)
+ s = cls.get_log_string(error_type, rp, exc)
+ Log(s, 2)
+ if Globals.null_separator: s += "\0"
+ else:
+ s = re.sub("\n", " ", s)
+ s += "\n"
+ cls._log_fileobj.write(s)
+
+ def get_indexpath(cls, obj):
+ """Return filename for logging. rp is a rpath, string, or
tuple"""
+ try: return obj.get_indexpath()
+ except AttributeError:
+ if type(obj) is types.TupleType: return "/".join(obj)
+ else: return str(obj)
+
+ def write_if_open(cls, error_type, rp, exc):
+ """Call cls.write(...) if error log open, only log otherwise"""
+ if not Globals.isbackup_writer and Globals.backup_writer:
+ return Globals.backup_writer.log.ErrorLog.write_if_open(
+ error_type, rp, str(exc)) # convert exc bc of
exc picking prob
+ if cls.isopen(): cls.write(error_type, rp, exc)
+ else: Log(cls.get_log_string(error_type, rp, exc), 2)
+
+ def get_log_string(cls, error_type, rp, exc):
+ """Return log string to put in error log"""
+ assert (error_type == "ListError" or error_type ==
"UpdateError" or
+ error_type == "SpecialFileError"), "Unknown
type "+error_type
+ return "%s %s %s" % (error_type, cls.get_indexpath(rp),
str(exc))
+
+ def close(cls):
+ """Close the error log file"""
+ if not Globals.isbackup_writer:
+ return Globals.backup_writer.log.ErrorLog.close()
+ assert not cls._log_fileobj.close()
+ cls._log_fileobj = cls._log_inc_rp = None
+
+static.MakeClass(ErrorLog)
+
[Prev in Thread] |
Current Thread |
[Next in Thread] |
- [Rdiff-backup-commits] Changes to rdiff-backup/rdiff_backup/log.py [r1-0],
Ben Escoto <=