Package duplicity :: Module log
[hide private]
[frames] | no frames]

Source Code for Module duplicity.log

  1  # -*- Mode:Python; indent-tabs-mode:nil; tab-width:4 -*- 
  2  # 
  3  # Copyright 2002 Ben Escoto <ben@emerose.org> 
  4  # Copyright 2007 Kenneth Loafman <kenneth@loafman.com> 
  5  # Copyright 2008 Michael Terry <mike@mterry.name> 
  6  # Copyright 2011 Canonical Ltd 
  7  # 
  8  # This file is part of duplicity. 
  9  # 
 10  # Duplicity is free software; you can redistribute it and/or modify it 
 11  # under the terms of the GNU General Public License as published by the 
 12  # Free Software Foundation; either version 2 of the License, or (at your 
 13  # option) any later version. 
 14  # 
 15  # Duplicity is distributed in the hope that it will be useful, but 
 16  # WITHOUT ANY WARRANTY; without even the implied warranty of 
 17  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU 
 18  # General Public License for more details. 
 19  # 
 20  # You should have received a copy of the GNU General Public License 
 21  # along with duplicity; if not, write to the Free Software Foundation, 
 22  # Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 
 23   
 24  """Log various messages depending on verbosity level""" 
 25   
 26  import os 
 27  import sys 
 28  import logging 
 29   
 30  MIN = 0 
 31  ERROR = 0 
 32  WARNING = 2 
 33  NOTICE = 3 
 34  INFO = 5 
 35  DEBUG = 9 
 36  MAX = 9 
 37   
 38  _logger = None 
 39   
40 -def DupToLoggerLevel(verb):
41 """Convert duplicity level to the logging module's system, where higher is 42 more severe""" 43 return MAX - verb + 1
44
45 -def LoggerToDupLevel(verb):
46 """Convert logging module level to duplicity's system, where lowere is 47 more severe""" 48 return DupToLoggerLevel(verb)
49
50 -def LevelName(level):
51 level = LoggerToDupLevel(level) 52 if level >= 9: return "DEBUG" 53 elif level >= 5: return "INFO" 54 elif level >= 3: return "NOTICE" 55 elif level >= 1: return "WARNING" 56 else: return "ERROR"
57
58 -def Log(s, verb_level, code=1, extra=None, force_print=False):
59 """Write s to stderr if verbosity level low enough""" 60 global _logger 61 # controlLine is a terrible hack until duplicity depends on Python 2.5 62 # and its logging 'extra' keyword that allows a custom record dictionary. 63 if extra: 64 _logger.controlLine = '%d %s' % (code, extra) 65 else: 66 _logger.controlLine = '%d' % (code) 67 if not s: 68 s = '' # If None is passed, standard logging would render it as 'None' 69 70 if force_print: 71 initial_level = _logger.getEffectiveLevel() 72 _logger.setLevel(DupToLoggerLevel(MAX)) 73 74 _logger.log(DupToLoggerLevel(verb_level), s.decode("utf8", "ignore")) 75 _logger.controlLine = None 76 77 if force_print: 78 _logger.setLevel(initial_level)
79
80 -def Debug(s):
81 """Shortcut used for debug message (verbosity 9).""" 82 Log(s, DEBUG)
83
84 -class InfoCode:
85 """Enumeration class to hold info code values. 86 These values should never change, as frontends rely upon them. 87 Don't use 0 or negative numbers.""" 88 generic = 1 89 progress = 2 90 collection_status = 3 91 diff_file_new = 4 92 diff_file_changed = 5 93 diff_file_deleted = 6 94 patch_file_writing = 7 95 patch_file_patching = 8 96 #file_list = 9 # 9 isn't used anymore. It corresponds to an older syntax for listing files 97 file_list = 10 98 synchronous_upload_begin = 11 99 asynchronous_upload_begin = 12 100 synchronous_upload_done = 13 101 asynchronous_upload_done = 14 102 skipping_socket = 15
103
104 -def Info(s, code=InfoCode.generic, extra=None):
105 """Shortcut used for info messages (verbosity 5).""" 106 Log(s, INFO, code, extra)
107
108 -def Progress(s, current, total=None):
109 """Shortcut used for progress messages (verbosity 5).""" 110 if total: 111 controlLine = '%d %d' % (current, total) 112 else: 113 controlLine = '%d' % current 114 Log(s, INFO, InfoCode.progress, controlLine)
115
116 -def PrintCollectionStatus(col_stats, force_print=False):
117 """Prints a collection status to the log""" 118 Log(str(col_stats), 8, InfoCode.collection_status, 119 '\n' + '\n'.join(col_stats.to_log_info()), force_print)
120
121 -def Notice(s):
122 """Shortcut used for notice messages (verbosity 3, the default).""" 123 Log(s, NOTICE)
124
125 -class WarningCode:
126 """Enumeration class to hold warning code values. 127 These values should never change, as frontends rely upon them. 128 Don't use 0 or negative numbers.""" 129 generic = 1 130 orphaned_sig = 2 131 unnecessary_sig = 3 132 unmatched_sig = 4 133 incomplete_backup = 5 134 orphaned_backup = 6 135 ftp_ncftp_v320 = 7 # moved from error 136 cannot_iterate = 8 137 cannot_stat = 9 138 cannot_read = 10 139 no_sig_for_time = 11
140
141 -def Warn(s, code=WarningCode.generic, extra=None):
142 """Shortcut used for warning messages (verbosity 2)""" 143 Log(s, WARNING, code, extra)
144
145 -class ErrorCode:
146 """Enumeration class to hold error code values. 147 These values should never change, as frontends rely upon them. 148 Don't use 0 or negative numbers. This code is returned by duplicity 149 to indicate which error occurred via both exit code and log.""" 150 generic = 1 # Don't use if possible, please create a new code and use it 151 command_line = 2 152 hostname_mismatch = 3 153 no_manifests = 4 154 mismatched_manifests = 5 155 unreadable_manifests = 6 156 cant_open_filelist = 7 157 bad_url = 8 158 bad_archive_dir = 9 159 bad_sign_key = 10 160 restore_dir_exists = 11 161 verify_dir_doesnt_exist = 12 162 backup_dir_doesnt_exist = 13 163 file_prefix_error = 14 164 globbing_error = 15 165 redundant_inclusion = 16 166 inc_without_sigs = 17 167 no_sigs = 18 168 restore_dir_not_found = 19 169 no_restore_files = 20 170 mismatched_hash = 21 171 unsigned_volume = 22 172 user_error = 23 173 boto_old_style = 24 174 boto_lib_too_old = 25 175 boto_calling_format = 26 176 ftp_ncftp_missing = 27 177 ftp_ncftp_too_old = 28 178 #ftp_ncftp_v320 = 29 # moved to warning 179 exception = 30 180 gpg_failed = 31 181 s3_bucket_not_style = 32 182 not_implemented = 33 183 get_freespace_failed = 34 184 not_enough_freespace = 35 185 get_ulimit_failed = 36 186 maxopen_too_low = 37 187 connection_failed = 38 188 restart_file_not_found = 39 189 gio_not_available = 40 190 source_dir_mismatch = 42 # 41 is reserved for par2 191 ftps_lftp_missing = 43 192 volume_wrong_size = 44 193 enryption_mismatch = 45 194 195 # 50->69 reserved for backend errors 196 backend_error = 50 197 backend_permission_denied = 51 198 backend_not_found = 52 199 backend_no_space = 53
200 201 # Reserve 255 because it is used as an error code for gksu 202
203 -def FatalError(s, code=ErrorCode.generic, extra=None):
204 """Write fatal error message and exit""" 205 Log(s, ERROR, code, extra) 206 shutdown() 207 sys.exit(code)
208
209 -class DupLogRecord(logging.LogRecord):
210 """Custom log record that holds a message code"""
211 - def __init__(self, controlLine, *args, **kwargs):
212 global _logger 213 logging.LogRecord.__init__(self, *args, **kwargs) 214 self.controlLine = controlLine 215 self.levelName = LevelName(self.levelno)
216
217 -class DupLogger(logging.Logger):
218 """Custom logger that creates special code-bearing records""" 219 # controlLine is a terrible hack until duplicity depends on Python 2.5 220 # and its logging 'extra' keyword that allows a custom record dictionary. 221 controlLine = None
222 - def makeRecord(self, name, lvl, fn, lno, msg, args, exc_info, *argv, **kwargs):
223 return DupLogRecord(self.controlLine, name, lvl, fn, lno, msg, args, exc_info)
224
225 -class OutFilter(logging.Filter):
226 """Filter that only allows warning or less important messages"""
227 - def filter(self, record):
228 return record.msg and record.levelno <= DupToLoggerLevel(WARNING)
229
230 -class ErrFilter(logging.Filter):
231 """Filter that only allows messages more important than warnings"""
232 - def filter(self, record):
233 return record.msg and record.levelno > DupToLoggerLevel(WARNING)
234
235 -def setup():
236 """Initialize logging""" 237 global _logger 238 if _logger: 239 return 240 241 logging.setLoggerClass(DupLogger) 242 _logger = logging.getLogger("duplicity") 243 244 # Default verbosity allows notices and above 245 setverbosity(NOTICE) 246 247 # stdout and stderr are for different logging levels 248 outHandler = logging.StreamHandler(sys.stdout) 249 outHandler.addFilter(OutFilter()) 250 _logger.addHandler(outHandler) 251 252 errHandler = logging.StreamHandler(sys.stderr) 253 errHandler.addFilter(ErrFilter()) 254 _logger.addHandler(errHandler)
255
256 -class MachineFormatter(logging.Formatter):
257 """Formatter that creates messages in a syntax easily consumable by other 258 processes."""
259 - def __init__(self):
260 # 'message' will be appended by format() 261 # Note that we use our own, custom-created 'levelName' instead of the 262 # standard 'levelname'. This is because the standard 'levelname' can 263 # be adjusted by any library anywhere in our stack without us knowing. 264 # But we control 'levelName'. 265 logging.Formatter.__init__(self, "%(levelName)s %(controlLine)s")
266
267 - def format(self, record):
268 s = logging.Formatter.format(self, record) 269 270 # Add user-text hint of 'message' back in, with each line prefixed by a 271 # dot, so consumers know it's not part of 'controlLine' 272 if record.message: 273 s += ('\n' + record.message).replace('\n', '\n. ') 274 275 # Add a newline so consumers know the message is over. 276 return s + '\n'
277
278 -class MachineFilter(logging.Filter):
279 """Filter that only allows levels that are consumable by other processes."""
280 - def filter(self, record):
281 # We only want to allow records that have our custom level names 282 return hasattr(record, 'levelName')
283
284 -def add_fd(fd):
285 """Add stream to which to write machine-readable logging""" 286 global _logger 287 handler = logging.StreamHandler(os.fdopen(fd, 'w')) 288 handler.setFormatter(MachineFormatter()) 289 handler.addFilter(MachineFilter()) 290 _logger.addHandler(handler)
291
292 -def add_file(filename):
293 """Add file to which to write machine-readable logging""" 294 global _logger 295 handler = logging.FileHandler(filename) 296 handler.setFormatter(MachineFormatter()) 297 handler.addFilter(MachineFilter()) 298 _logger.addHandler(handler)
299
300 -def setverbosity(verb):
301 """Set the verbosity level""" 302 global _logger 303 _logger.setLevel(DupToLoggerLevel(verb))
304
305 -def getverbosity():
306 """Get the verbosity level""" 307 global _logger 308 return LoggerToDupLevel(_logger.getEffectiveLevel())
309
310 -def shutdown():
311 """Cleanup and flush loggers""" 312 logging.shutdown()
313