debug.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425
  1. ## debug.py
  2. ##
  3. ## Copyright (C) 2003 Jacob Lundqvist
  4. ##
  5. ## This program is free software; you can redistribute it and/or modify
  6. ## it under the terms of the GNU Lesser General Public License as published
  7. ## by the Free Software Foundation; either version 2, or (at your option)
  8. ## any later version.
  9. ##
  10. ## This program is distributed in the hope that it will be useful,
  11. ## but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. ## GNU Lesser General Public License for more details.
  14. _version_ = '1.4.0'
  15. """\
  16. Generic debug class
  17. Other modules can always define extra debug flags for local usage, as long as
  18. they make sure they append them to debug_flags
  19. Also its always a good thing to prefix local flags with something, to reduce risk
  20. of coliding flags. Nothing breaks if two flags would be identical, but it might
  21. activate unintended debugging.
  22. flags can be numeric, but that makes analysing harder, on creation its
  23. not obvious what is activated, and when flag_show is given, output isnt
  24. really meaningfull.
  25. This Debug class can either be initialized and used on app level, or used independantly
  26. by the individual classes.
  27. For samples of usage, see samples subdir in distro source, and selftest
  28. in this code
  29. """
  30. import sys
  31. import traceback
  32. import time
  33. import os
  34. from six import ensure_str
  35. import types
  36. if 'TERM' in os.environ:
  37. colors_enabled=True
  38. else:
  39. colors_enabled=False
  40. color_none = chr(27) + "[0m"
  41. color_black = chr(27) + "[30m"
  42. color_red = chr(27) + "[31m"
  43. color_green = chr(27) + "[32m"
  44. color_brown = chr(27) + "[33m"
  45. color_blue = chr(27) + "[34m"
  46. color_magenta = chr(27) + "[35m"
  47. color_cyan = chr(27) + "[36m"
  48. color_light_gray = chr(27) + "[37m"
  49. color_dark_gray = chr(27) + "[30;1m"
  50. color_bright_red = chr(27) + "[31;1m"
  51. color_bright_green = chr(27) + "[32;1m"
  52. color_yellow = chr(27) + "[33;1m"
  53. color_bright_blue = chr(27) + "[34;1m"
  54. color_purple = chr(27) + "[35;1m"
  55. color_bright_cyan = chr(27) + "[36;1m"
  56. color_white = chr(27) + "[37;1m"
  57. """
  58. Define your flags in yor modules like this:
  59. from debug import *
  60. DBG_INIT = 'init' ; debug_flags.append( DBG_INIT )
  61. DBG_CONNECTION = 'connection' ; debug_flags.append( DBG_CONNECTION )
  62. The reason for having a double statement wis so we can validate params
  63. and catch all undefined debug flags
  64. This gives us control over all used flags, and makes it easier to allow
  65. global debugging in your code, just do something like
  66. foo = Debug( debug_flags )
  67. group flags, that is a flag in it self containing multiple flags should be
  68. defined without the debug_flags.append() sequence, since the parts are already
  69. in the list, also they must of course be defined after the flags they depend on ;)
  70. example:
  71. DBG_MULTI = [ DBG_INIT, DBG_CONNECTION ]
  72. NoDebug
  73. -------
  74. To speed code up, typically for product releases or such
  75. use this class instead if you globaly want to disable debugging
  76. """
  77. class NoDebug:
  78. def __init__( self, *args, **kwargs ):
  79. self.debug_flags = []
  80. def show( self, *args, **kwargs):
  81. pass
  82. def Show( self, *args, **kwargs):
  83. pass
  84. def is_active( self, flag ):
  85. pass
  86. colors={}
  87. def active_set( self, active_flags = None ):
  88. return 0
  89. LINE_FEED = '\n'
  90. class Debug:
  91. def __init__( self,
  92. #
  93. # active_flags are those that will trigger output
  94. #
  95. active_flags = None,
  96. #
  97. # Log file should be file object or file namne
  98. #
  99. log_file = sys.stderr,
  100. #
  101. # prefix and sufix can either be set globaly or per call.
  102. # personally I use this to color code debug statements
  103. # with prefix = chr(27) + '[34m'
  104. # sufix = chr(27) + '[37;1m\n'
  105. #
  106. prefix = 'DEBUG: ',
  107. sufix = '\n',
  108. #
  109. # If you want unix style timestamps,
  110. # 0 disables timestamps
  111. # 1 before prefix, good when prefix is a string
  112. # 2 after prefix, good when prefix is a color
  113. #
  114. time_stamp = 0,
  115. #
  116. # flag_show should normaly be of, but can be turned on to get a
  117. # good view of what flags are actually used for calls,
  118. # if it is not None, it should be a string
  119. # flags for current call will be displayed
  120. # with flag_show as separator
  121. # recomended values vould be '-' or ':', but any string goes
  122. #
  123. flag_show = None,
  124. #
  125. # If you dont want to validate flags on each call to
  126. # show(), set this to 0
  127. #
  128. validate_flags = 1,
  129. #
  130. # If you dont want the welcome message, set to 0
  131. # default is to show welcome if any flags are active
  132. welcome = -1
  133. ):
  134. self.debug_flags = []
  135. if welcome == -1:
  136. if active_flags and len(active_flags):
  137. welcome = 1
  138. else:
  139. welcome = 0
  140. self._remove_dupe_flags()
  141. if log_file:
  142. if type( log_file ) is type(''):
  143. try:
  144. self._fh = open(log_file,'w')
  145. except:
  146. print('ERROR: can open %s for writing')
  147. sys.exit(0)
  148. else: ## assume its a stream type object
  149. self._fh = log_file
  150. else:
  151. self._fh = sys.stdout
  152. if time_stamp not in (0,1,2):
  153. msg2 = '%s' % time_stamp
  154. raise Exception('Invalid time_stamp param', msg2)
  155. self.prefix = prefix
  156. self.sufix = sufix
  157. self.time_stamp = time_stamp
  158. self.flag_show = None # must be initialised after possible welcome
  159. self.validate_flags = validate_flags
  160. self.active_set( active_flags )
  161. if welcome:
  162. self.show('')
  163. caller = sys._getframe(1) # used to get name of caller
  164. try:
  165. mod_name= ":%s" % caller.f_locals['__name__']
  166. except:
  167. mod_name = ""
  168. self.show('Debug created for %s%s' % (caller.f_code.co_filename,
  169. mod_name ))
  170. self.show(' flags defined: %s' % ','.join( self.active ))
  171. if type(flag_show) in (type(''), type(None)):
  172. self.flag_show = flag_show
  173. else:
  174. msg2 = '%s' % type(flag_show )
  175. raise Exception('Invalid type for flag_show!', msg2)
  176. def show( self, msg, flag = None, prefix = None, sufix = None,
  177. lf = 0 ):
  178. """
  179. flag can be of folowing types:
  180. None - this msg will always be shown if any debugging is on
  181. flag - will be shown if flag is active
  182. (flag1,flag2,,,) - will be shown if any of the given flags
  183. are active
  184. if prefix / sufix are not given, default ones from init will be used
  185. lf = -1 means strip linefeed if pressent
  186. lf = 1 means add linefeed if not pressent
  187. """
  188. if self.validate_flags:
  189. self._validate_flag( flag )
  190. if not self.is_active(flag):
  191. return
  192. if prefix:
  193. pre = prefix
  194. else:
  195. pre = self.prefix
  196. if sufix:
  197. suf = sufix
  198. else:
  199. suf = self.sufix
  200. if self.time_stamp == 2:
  201. output = '%s%s ' % ( pre,
  202. time.strftime('%b %d %H:%M:%S',
  203. time.localtime(time.time() )),
  204. )
  205. elif self.time_stamp == 1:
  206. output = '%s %s' % ( time.strftime('%b %d %H:%M:%S',
  207. time.localtime(time.time() )),
  208. pre,
  209. )
  210. else:
  211. output = pre
  212. if self.flag_show:
  213. if flag:
  214. output = '%s%s%s' % ( output, flag, self.flag_show )
  215. else:
  216. # this call uses the global default,
  217. # dont print "None", just show the separator
  218. output = '%s %s' % ( output, self.flag_show )
  219. output = '%s%s%s' % ( output, msg, suf )
  220. if lf:
  221. # strip/add lf if needed
  222. last_char = output[-1]
  223. if lf == 1 and last_char != LINE_FEED:
  224. output = output + LINE_FEED
  225. elif lf == -1 and last_char == LINE_FEED:
  226. output = output[:-1]
  227. try:
  228. self._fh.write( output )
  229. except:
  230. # unicode strikes again ;)
  231. s=''
  232. for i in range(len(output)):
  233. if ord(output[i]) < 128:
  234. c = output[i]
  235. else:
  236. c = '?'
  237. s=s+c
  238. self._fh.write( '%s%s%s' % ( pre, s, suf ))
  239. self._fh.flush()
  240. def is_active( self, flag ):
  241. 'If given flag(s) should generate output.'
  242. # try to abort early to quicken code
  243. if not self.active:
  244. return 0
  245. if not flag or flag in self.active:
  246. return 1
  247. else:
  248. # check for multi flag type:
  249. if type( flag ) in ( type(()), type([]) ):
  250. for s in flag:
  251. if s in self.active:
  252. return 1
  253. return 0
  254. def active_set( self, active_flags = None ):
  255. "returns 1 if any flags where actually set, otherwise 0."
  256. r = 0
  257. ok_flags = []
  258. if not active_flags:
  259. #no debuging at all
  260. self.active = []
  261. elif type( active_flags ) in ( tuple, list ):
  262. flags = self._as_one_list( active_flags )
  263. for t in flags:
  264. if t not in self.debug_flags:
  265. sys.stderr.write('Invalid debugflag given: %s\n' % t )
  266. ok_flags.append( t )
  267. self.active = ok_flags
  268. r = 1
  269. else:
  270. # assume comma string
  271. try:
  272. flags = active_flags.split(',')
  273. except:
  274. self.show( '***' )
  275. self.show( '*** Invalid debug param given: %s' % active_flags )
  276. self.show( '*** please correct your param!' )
  277. self.show( '*** due to this, full debuging is enabled' )
  278. self.active = self.debug_flags
  279. for f in flags:
  280. s = f.strip()
  281. ok_flags.append( s )
  282. self.active = ok_flags
  283. self._remove_dupe_flags()
  284. return r
  285. def active_get( self ):
  286. "returns currently active flags."
  287. return self.active
  288. def _as_one_list( self, items ):
  289. """ init param might contain nested lists, typically from group flags.
  290. This code organises lst and remves dupes
  291. """
  292. if type( items ) != type( [] ) and type( items ) != type( () ):
  293. return [ items ]
  294. r = []
  295. for l in items:
  296. if type( l ) == type([]):
  297. lst2 = self._as_one_list( l )
  298. for l2 in lst2:
  299. self._append_unique_str(r, l2 )
  300. elif l == None:
  301. continue
  302. else:
  303. self._append_unique_str(r, l )
  304. return r
  305. def _append_unique_str( self, lst, item ):
  306. """filter out any dupes."""
  307. if type(item) != type(''):
  308. msg2 = '%s' % item
  309. raise Exception('Invalid item type (should be string)',msg2)
  310. if item not in lst:
  311. lst.append( item )
  312. return lst
  313. def _validate_flag( self, flags ):
  314. 'verify that flag is defined.'
  315. if flags:
  316. for f in self._as_one_list( flags ):
  317. if not f in self.debug_flags:
  318. msg2 = '%s' % f
  319. raise Exception('Invalid debugflag given', msg2)
  320. def _remove_dupe_flags( self ):
  321. """
  322. if multiple instances of Debug is used in same app,
  323. some flags might be created multiple time, filter out dupes
  324. """
  325. unique_flags = []
  326. for f in self.debug_flags:
  327. if f not in unique_flags:
  328. unique_flags.append(f)
  329. self.debug_flags = unique_flags
  330. colors={}
  331. def Show(self, flag, msg, prefix=''):
  332. msg=ensure_str(msg,'utf-8')
  333. msg=msg.replace('\r','\\r').replace('\n','\\n').replace('><','>\n <')
  334. if not colors_enabled: pass
  335. elif prefix in self.colors: msg=self.colors[prefix]+msg+color_none
  336. else: msg=color_none+msg
  337. if not colors_enabled: prefixcolor=''
  338. elif flag in self.colors: prefixcolor=self.colors[flag]
  339. else: prefixcolor=color_none
  340. if prefix=='error':
  341. _exception = sys.exc_info()
  342. if _exception[0]:
  343. msg=msg+'\n'+''.join(traceback.format_exception(_exception[0], _exception[1], _exception[2])).rstrip()
  344. prefix= self.prefix+prefixcolor+(flag+' '*12)[:12]+' '+(prefix+' '*6)[:6]
  345. self.show(msg, flag, prefix)
  346. def is_active( self, flag ):
  347. if not self.active: return 0
  348. if not flag or flag in self.active and DBG_ALWAYS not in self.active or flag not in self.active and DBG_ALWAYS in self.active : return 1
  349. return 0
  350. DBG_ALWAYS='always'
  351. ##Uncomment this to effectively disable all debugging and all debugging overhead.
  352. #Debug=NoDebug