Package rrlog
[hide private]
[frames] | no frames]

Source Code for Package rrlog

  1  #Copyright (c) 2007 
  2  #        Ruben Reifenberg, Germany, 07381. 
  3  #    All rights reserved. 
  4  # 
  5  #Redistribution and use in source and binary forms, with or without 
  6  #modification, are permitted provided that the following conditions 
  7  #are met: 
  8  #1. Redistributions of source code must retain the above copyright 
  9  #   notice, this list of conditions and the following disclaimer as 
 10  #   the first lines of this file unmodified. 
 11  #2. Redistributions in binary form must reproduce the above copyright 
 12  #   notice, this list of conditions and the following disclaimer in the 
 13  #   documentation and/or other materials provided with the distribution. 
 14  # 
 15  # THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 
 16  # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 
 17  # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 
 18  # ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 
 19  # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 
 20  # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 
 21  # OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 
 22  # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 
 23  # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 
 24  # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 
 25  # SUCH DAMAGE. 
 26   
 27   
 28  """ 
 29  rrlog - a Rotating Remote LOG. 
 30  @author: Ruben Reifenberg 
 31  """ 
 32   
 33  __version__="0.2.2" 
 34   
 35  import sys,os 
 36  import traceback 
 37  from rrlog.tool import traceToShortStr 
 38  from rrlog.logging23 import handler 
 39   
40 -class SilentErrorHandler(object):
41 """ 42 Use this as errorHandler if you want log errors 43 to disappear silently (e.g.in case of a Log Server Crash). 44 This is not recommended, of course 45 But often better than an exception 46 in the log() call inside the application. 47 """
48 - def handleException(self, e):
49 pass
50 51
52 -class StdoutErrorHandler(object):
53 """ 54 Use this as errorHandler if you want to see 55 Errors (in case of a Log Server Crash) 56 in standard out. 57 This is not good in case of a CGI web app, because 58 the printout may appear in the web page. 59 """
60 - def handleException(self, e):
61 print "Logging Error: %s"%(e) 62 print traceToShortStr()
63 64
65 -class StderrErrorHandler(object):
66 """ 67 Use this as errorHandler if you want to see 68 Errors (in case of a Log Server Crash) 69 in standard error out. 70 """
71 - def handleException(self, e):
72 sys.stderr.write("Logging Error: %s\n"%(e)) 73 sys.stderr.write(traceToShortStr())
74 75 76
77 -class Log(object):
78 """ 79 Instances of this are callable and represent the runtime interface for the application. 80 @ivar stackMax: See L{__init__}, can be modified anytime. 81 @ivar traceOffset: See L{__init__}, can be modified anytime. 82 """ 83 #todo: consider add a tsFormat param here! If given, the client takes the ts, and also overrides the server ts format.
84 - def __init__(self, 85 server, 86 on = None, 87 traceOffset=0, 88 msgCountLimit = 1000000, #exclusive! Highest number is 1 less 89 stackMax=1, 90 errorHandler=None, 91 catsEnable=None, 92 catsDisable=None, 93 seFilesExclude=None, 94 name=None, 95 extractStack=True, 96 ):
97 """ 98 @param catsEnable: None or list of cat strings, e.g.("E","W",""). 99 (Remember, the empty cat "" is the default.) 100 If a list is specified, only these cats are logged, all other calls are ignored. 101 If None, all cats are enabled. 102 @param catsDisable: All cats in this list are ignored. Only one of the 103 parameters catsEnable and catsDisable can be specified, not both same time. 104 @param on: DONT USE ANYMORE. Use catsEnable=() to switch off the log. 105 @param traceOffset: Adjusts the start point of the logged stack trace. 106 Default == 0. Increase to 1 if you have wrapped the log, otherwise, all stack paths 107 will start with your wrapper function. Too high values are silently adjusted to the highest value possible. 108 @param stackMax: 0==show no stacktrace, 1==log one stack line (the line where log(...) was called), 109 2==log two stack lines, and so on. 110 Default: 1 (==log callers line and nothing else.) 111 See also: extractStack parameter. 112 @param errorHandler: Gets any Exception 113 that occurs while logging. 114 (e.g.a connection failure to the log server) 115 If handler is None: No handling, your application 116 will receive any exception occurring inside the log() call. 117 For convenience, you may provide one of these strings: 118 "stderr","stdout","silent" (case-insensitive). 119 These strings are translated 120 into the appropriate built-in "~ErrorHandler" class. 121 @param seFilesExclude: Stack Extraction-Excluded Files. 122 None (==nothing to exclude), or a callable that returns True/False when given a filename. 123 When True, the file does NOT appear in the call path. 124 None is acepted instead of False too, just to enable pragmatic ideas like 125 seFilesExclude=re.compile("foo/bar.*").search 126 (which exludes all foo/bar* files). 127 Note: The callers file name (cfn) is never excluded this way, 128 only the rest of the trace may be "censored". 129 @param name: str, defaults to class name. For individual use (to identify the log object). The log itself does not evaluate it but use it with its __repr__/__str__ method. 130 @param extractStack: If False, stack extraction is disabled. This improves performance (can be more than twice), but any stack related functionality will not work (e.g.line indention to visualize call hierarchy). 131 """ 132 if name is None: name = self.__class__.__name__ 133 assert on is None, "Parameter on is to be removed. Use catsEnable=() to switch off the log." 134 self._on = True 135 assert (catsEnable is None) or (catsDisable is None), "Can't use both catsEnable and catsDisable same time" 136 if catsEnable is not None: 137 assert hasattr(catsEnable,"__iter__") and (not isinstance(catsEnable,str)),"catsEnable must be None or list of str" 138 if catsDisable is not None: 139 assert hasattr(catsDisable,"__iter__") and (not isinstance(catsDisable,str)),"catsDisable must be None or list of str" 140 141 self._msgCount = 0 142 self.traceOffset = traceOffset 143 self._server = server 144 self._id = self._server.addClient() # my unique client id 145 self.msgCountLimit = msgCountLimit 146 self.stackMax = stackMax 147 if str(errorHandler).lower()=="stderr": errorHandler=StderrErrorHandler() 148 elif str(errorHandler).lower()=="stdout": errorHandler=StdoutErrorHandler() 149 elif str(errorHandler).lower()=="silent": errorHandler=SilentErrorHandler() 150 elif isinstance(errorHandler,str): raise TypeError("probably mistyped str value for errorHandler:%s"%(errorHandler)) 151 self._errorHandler = errorHandler 152 self.catsEnable = catsEnable 153 self.catsDisable = catsDisable 154 self._seFilesExclude = seFilesExclude 155 self.name = name 156 self._extractStack = extractStack
157 158
159 - def logging23_handler(self):
160 """ 161 Note that, contrary to the Python logging, the rrlog is not designed/tested for threading. 162 @return: a Handler for the Python >=2.3 standard logging framework. 163 """ 164 return handler(self)
165 166 167 168 # def _extract_stack_limit(self, depth): 169 # """ 170 # We cannot limit extract_stack if we eventually have 171 # to remove some lines because of seFilesExclude. 172 # Otherwise, we can optimize by using my stackMax as limit. 173 # @return: limit parameter for extract_stack, or None (no limit) 174 # """ 175 # if self._seFilesExclude is None: 176 # return self.stackMax+depth 177 # else: 178 # return None 179
180 - def _getCallPath(self,tb,depth):
181 """ 182 @return: path,cfuncname where path = ( (filename,lineno), ...), len >=0 183 cfuncname is "" if no traceback available, 184 None if no cfuncname exists (log call at module level) 185 @param depth:int,log-internal call depth when the traceback was taken. 186 """ 187 if len(tb) == 0: return (),"" # return ((None,-1),),None 188 # tb = traceback.extract_stack(limit=self._extract_stack_limit(depth)) #limit >=0, 0==no lines etc. 189 calldepth = -1-depth # -1 is current. Ignore current and all previous log-internal calls. 190 try: 191 line = tb[calldepth] 192 except IndexError: 193 # This happens - in the following case: 194 # Log is created with traceOffset == 1 195 # because a module uses a log() function for later log() calls. 196 # But creation itself is in a place not deep enough 197 # and logging of this Log creation may fail here. 198 calldepth += 1 199 try: 200 line = tb[calldepth] 201 except IndexError: 202 # impossible depth was given: sadly, we should not fail here :-( 203 # Legal Scenario: traceOffset is given a high value to adjust for standard logging 204 # another Python version theoretically can refactor the logging framework to have shorter tracebacks 205 # And the application still should not fail because of that. 206 calldepth = len(tb)-1 207 line = tb[calldepth] 208 depth = calldepth # - 1 # -1, um line nicht nochmal zu sehen 209 210 path = [] 211 cfuncname = None 212 stackRest = self.stackMax 213 while stackRest > 0: 214 try: 215 line = tb[depth] 216 except IndexError: break 217 else: 218 if (self._seFilesExclude is None)\ 219 or not (self._seFilesExclude(line[0])): 220 stackRest -= 1 221 path.append( (line[0],line[1]) ) 222 if cfuncname is None: 223 cfuncname = line[2] # special treatment: don't add it to every path item, we want it for the first only 224 else: 225 # add a "None" line indicating an item is omitted 226 path.append( (None,None) ) 227 depth -= 1 228 return path,cfuncname
229 230 231 232
233 - def _createServerData(self,tb,depth,message,cat,special):
234 """ 235 @return: Tuple for the log server 236 @param depth:int,log-internal call depth when the traceback was taken. 237 """ 238 self._msgCount += 1 239 if self._msgCount == self.msgCountLimit: 240 self._msgCount = 1 241 try: 242 ospid = os.getpid() # need to get it always. When saving it, a process could fork me after that. 243 except Exception,e: 244 import warnings 245 warnings.warn("log: could not obtain process id from your operating system. You'll see -1 there.") 246 ospid = -1 247 248 249 path,cfuncname = self._getCallPath(tb,depth) 250 return ( 251 ospid, 252 self._id, 253 self._msgCount, 254 message, 255 special, 256 cat, 257 path, 258 len(tb), 259 cfuncname, 260 )
261 262
263 - def log(self, *args, **kwargs):
264 """ 265 REMOVED. There's no log.log() anymore. 266 Use log() instead (i.e. see L{__call__} 267 @raise AssertionError: Always 268 """ 269 assert False, "log.log() was for compatibility with pre 0.1.0 versions. Use log()."
270
271 - def on(self): self._on = True
272 - def off(self): self._on = False
273
274 - def __call__(self,message,cat="",special=None,traceDepth=1,**kwargs):
275 """ 276 You can provide any kwargs you like. This is sugar for convenience, 277 all kwargs are put into the "special" dict. Same restrictions, see below. 278 Note: kwargs items override items of special dict silently. 279 @param message: String to be logged 280 @param cat: application specific category, e.g. "E"=Error,"W"=Warning. Default is "". 281 @param special: dict-like object (only the items() method is required, delivering a (k,v)-iter); or None. 282 The items of the "special" dict appear in the Jobs at the server side. Custom observers, 283 filters and writers may use these data. 284 Type restrictions: only {str: str|int} is allowed for the dict items (!) 285 @param traceDepth: Adjusts the starting point of stacktrace logging. 286 Default=1. Typically, you may increase that if you call the log with a wrapping function which should not appear in the logged stacktrace. 287 This adjusts a single call only. See traceOffset in L{__init__} for a global adjustment. 288 @return: log-client specific message number,starting with 1 (not unique at server side, if multiple log clients are used.) 289 """ 290 if (self.catsEnable is not None) and (cat not in self.catsEnable): 291 return 292 elif (self.catsDisable is not None) and (cat in self.catsDisable): 293 return 294 if not self._on: return 295 if len(kwargs) > 0: 296 # add these items into special 297 if special is None: special = kwargs 298 else: special.update(kwargs) 299 300 if self._extractStack: 301 tb = traceback.extract_stack() 302 else: 303 tb = () 304 sd = self._createServerData( 305 tb, 306 traceDepth+self.traceOffset, 307 message, 308 cat, 309 special, 310 ) 311 try: 312 self._server.log(*sd) 313 except Exception,e: 314 if self._errorHandler is not None: 315 self._errorHandler.handleException(e) 316 else: 317 raise # Exception(str(e)+traceToShortStr()) 318 else: 319 return sd[0]
320
321 - def __repr__(self):
322 def on():return {True:"on",False:"Off"}[self._on] 323 return "%s(%s)"%(self.name,on())
324