Debug Tools
logger.py
1 #!/usr/bin/env python3
2 # -*- coding: UTF-8 -*-
3 
4 
41 
42 import os
43 import io
44 import sys
45 
46 import timeit
47 import datetime
48 import platform
49 
50 import inspect
51 import traceback
52 
53 import logging
54 import logging.handlers
55 
56 from collections import MutableMapping
57 from logging import getLevelName
58 
59 from logging import Logger
60 from logging import Manager
61 from logging import PlaceHolder
62 from logging import LogRecord
63 
64 from logging import DEBUG
65 from logging import WARNING
66 from logging import ERROR
67 
68 from logging import _srcfile
69 from logging import _acquireLock
70 from logging import _releaseLock
71 
72 
73 try:
74  from concurrent_log_handler import ConcurrentRotatingFileHandler
75 
76 except( ImportError, ValueError ):
77  from logging import FileHandler
78 
79  class ConcurrentRotatingFileHandler(FileHandler):
80  """
81  Fall back with the basic file logger when ConcurrentRotatingFileHandler is not available.
82  """
83 
84  def __init__(self, output_file, maxBytes=0, backupCount=0):
85  super( ConcurrentRotatingFileHandler, self ).__init__( output_file )
86 
87 
88 # Uncoment this temporarily to create update the `stdout_replacement.py` after changes
89 # on `stderr_replacement.py`
90 #
91 # While developing, you can reload your changes to `create_stdout_handler` with:
92 # from .utilities import _create_stdout_handler; _create_stdout_handler();
93 
94 from .stderr_replacement import stderr_replacement
95 from .stdout_replacement import stdout_replacement
96 
97 changeable_setup_arguments = (
98  "date",
99  "levels",
100  "function",
101  "name",
102  "time",
103  "tick",
104  "separator",
105  "msecs",
106 )
107 
108 is_python2 = False
109 EMPTY_KWARG = -sys.maxsize
110 
111 if sys.version_info[0] < 3:
112  is_python2 = True
113  from collections import MutableMapping
114 
115 else:
116  from collections.abc import MutableMapping
117 
118 if hasattr(sys, '_getframe'):
119  currentframe = lambda level: sys._getframe(level)
120 
121 else: #pragma: no cover
122  def currentframe(level):
123  """Return the frame object for the caller's stack frame."""
124  try:
125  raise Exception
126  except Exception:
127  return sys.exc_info()[level-1].tb_frame.f_back
128 
129 
130 class Debugger(Logger):
131  """
132  https://docs.python.org/2.6/library/logging.html
133 
134  How to define global function in Python?
135  https://stackoverflow.com/questions/27930038/how-to-define-global-function-in-python
136 
137  How to print list inside python print?
138  https://stackoverflow.com/questions/45427500/how-to-print-list-inside-python-print
139  """
140  _debugme = False
141  _file_context_filter = None
142  _has_file_context_filter = False
143 
144  def __init__(self, logger_name, logger_level=None):
145  """
146  See the factory global function logger.getLogger().
147  """
148  # What is a clean, pythonic way to have multiple constructors in Python?
149  # https://stackoverflow.com/questions/682504/what-is-a-clean-pythonic-way-to-have-multiple-constructors-in-python
150  super( Debugger, self ).__init__( logger_name, logger_level or "DEBUG" )
151 
152  self._file = None
153  self._stream = None
154 
155  # Initialize the first last tick as the current tick
156  self._last_tick = timeit.default_timer()
157 
158  # Forces this debug_level into all children
159  self._force_debug = None
160  self.trimname = 0
161 
162  # Enable debug messages: (bitwise)
163  # 0 - Disabled debugging
164  # 1 - Errors messages
165  self._frame_level = 4
166  self._debugger_level = 127
167  self._reset()
168 
169  @property
170  def output_file(self):
171 
172  if self._file:
173  return self._file.baseFilename
174 
175  return None
176 
177  @property
178  def active(self):
179  """
180  Works accordingly with super::hasHandlers(), except that this returns the activate
181  logger object if it has some activate handler, or None if there are not loggers with
182  active handlers.
183 
184  The root logger is not returned, unless it is already setup with handlers.
185  """
186  current = self
187 
188  while current:
189 
190  if current.handlers:
191  return current
192 
193  if not current.propagate:
194  break
195 
196  else:
197  current = current.parent
198 
199  return None
200 
201  @property
202  def debug_level(self):
203  """
204  Return this logger active debug level.
205  """
206  return self._debugger_level
207 
208  @debug_level.setter
209  def debug_level(self, debug_level):
210  """
211  Set this current logger object active debug level.
212  """
213 
214  if isinstance( debug_level, int ):
215  self._debugger_level = debug_level
216 
217  else:
218  raise ValueError( "Error: The debug_level `%s` must be an integer!" % debug_level )
219 
220  @property
221  def _debug_level(self):
222  """
223  The same as Debugger::debug_level, return this logger active debug level.
224 
225  This only exists for consistency with the setter Debugger::_debug_level().
226  """
227  return self._debugger_level
228 
229  @_debug_level.setter
230  def _debug_level(self, debug_level):
231  """
232  Set this current logger object and all other relative loggers' active debug level.
233 
234  Useful to set a uniform debug level across all related loggers. A logger is considered
235  related if it is the active parent or some of its children.
236  """
237  active = self.active or self
238 
239  def set_level(logger):
240  logger.debug_level = debug_level
241 
242  set_level( active )
243  active.fix_children( set_level )
244 
245  @property
246  def _stderr(self):
247  """
248  Return whether the `sys.stderr` handling is enabled or not.
249  """
250  return self._arguments['stderr']
251 
252  @_stderr.setter
253  def _stderr(self, value):
254  """
255  Block modifying the value of `self.stderr`.
256  """
257  raise ValueError( "Error: The stderr attribute is readonly!" )
258 
259  @property
260  def _stdout(self):
261  """
262  Return whether the `sys.stdout` handling is enabled or not.
263  """
264  return self._arguments['stdout']
265 
266  @_stdout.setter
267  def _stdout(self, value):
268  """
269  Block modifying the value of `self.stdout`.
270  """
271  raise ValueError( "Error: The stdout attribute is readonly!" )
272 
273  def traceback(self, **kwargs):
274  """
275  Prints the stack trace (traceback) until the current function call.
276  """
277  kwargs['debug_level'] = 1
278  self._log( DEBUG, "traceback.format_stack():\n%s\n\n", "".join( traceback.format_stack() ) )
279 
280  def reset(self):
281  """
282  Reset all remembered parameters values set on the subsequent calls to `setup()`.
283 
284  Call this if you want to reset to remove all handlers and set all parameters values to
285  their default.
286  """
287  self.removeHandlers()
288  self._reset()
289 
290  def _reset(self):
291  self._arguments = self._formatter_arguments()
292  self.full_formatter = self._setup_formatter( self._arguments )
293 
294  self.clean_formatter = logging.Formatter( "", "" )
295  self.setup_basic( function=False, tick=False )
296 
297  def setup_basic(self, **kwargs):
298  """
299  Configure the `basic_formatter` used by `basic()` logging function.
300 
301  @param `**kwargs` the same formatting arguments passed to `setup()`
302  """
303  basic_arguments = self._formatter_arguments()
304  basic_arguments.update( kwargs )
305  self.basic_formatter = self._setup_formatter( basic_arguments )
306 
307  def _formatter_arguments(self):
308  return \
309  {
310  "file": None,
311  "mode": 'a',
312  "delete": True,
313  "date": False,
314  "levels": False,
315  "function": True,
316  "name": True,
317  "time": True,
318  "tick": True,
319  "separator": True,
320  "formatter": None,
321  "rotation": 0,
322  "msecs": True,
323  "stderr": False,
324  "stdout": False,
325  "fast": False,
326  "stream": None,
327  "trimname": 0,
328  }
329 
330  def newline(self, debug_level=1, count=1):
331  """
332  Prints a clean new line, without any formatter header.
333  @param `count` how many new lines to output
334  """
335  for index in range(count):
336  self.clean( debug_level, "" )
337 
338  def new_line(self, debug_level=1, count=1):
339  """
340  Prints a clean new line, without any formatter header.
341  @param `count` how many new lines to output
342  """
343  for index in range(count):
344  self.clean( debug_level, "" )
345 
346  def __call__(self, debug_level=1, msg=EMPTY_KWARG, *args, **kwargs):
347  """
348  Log to the current active handlers its message based on the bitwise `self._debugger_level`
349  value. Note, differently from the standard logging level, each logger object has its own
350  bitwise logging level, instead of all sharing the main `level`.
351  """
352 
353  if type( debug_level ) is int:
354 
355  if msg is EMPTY_KWARG:
356 
357  if self._debugger_level & 1 != 0:
358  kwargs['debug_level'] = 1
359  self._log( DEBUG, debug_level, args, **kwargs )
360 
361  elif self._debugger_level & debug_level != 0:
362  kwargs['debug_level'] = debug_level
363  self._log( DEBUG, msg, args, **kwargs )
364 
365  else:
366 
367  if self._debugger_level & 1 != 0:
368  kwargs['debug_level'] = 1
369 
370  if msg is EMPTY_KWARG:
371  self._log( DEBUG, debug_level, args, **kwargs )
372 
373  else:
374  self._log( DEBUG, debug_level, (msg,) + args, **kwargs )
375 
376  def _fast_clean(self, debug_level=1, msg=EMPTY_KWARG, *args, **kwargs):
377 
378  if self._debugger_level & debug_level != 0:
379  other = self.active or self
380 
381  old_formatters = {}
382  _acquireLock()
383  try:
384  for handler in other.handlers:
385  old_formatters[handler] = handler.formatter
386  handler.formatter = self.clean_formatter
387 
388  finally:
389  _releaseLock()
390 
391  kwargs['debug_level'] = debug_level
392  self._log_clean( msg, args, kwargs )
393 
394  for handler, formatter in old_formatters.items():
395  handler.formatter = formatter
396 
397  def clean(self, debug_level=1, msg=EMPTY_KWARG, *args, **kwargs):
398  """
399  Prints a message without the time prefix as `[plugin_name.py] 11:13:51:0582059`
400 
401  How to insert newline in python logging?
402  https://stackoverflow.com/questions/20111758/how-to-insert-newline-in-python-logging
403  """
404 
405  if type( debug_level ) is int:
406 
407  if msg is EMPTY_KWARG:
408 
409  if self._debugger_level & 1 != 0:
410  other = self.active or self
411 
412  old_formatters = {}
413  _acquireLock()
414  try:
415  for handler in other.handlers:
416  old_formatters[handler] = handler.formatter
417  handler.formatter = self.clean_formatter
418 
419  finally:
420  _releaseLock()
421 
422  kwargs['debug_level'] = 1
423  self._log_clean( debug_level, args, kwargs )
424 
425  for handler, formatter in old_formatters.items():
426  handler.formatter = formatter
427 
428  elif self._debugger_level & debug_level != 0:
429  other = self.active or self
430 
431  old_formatters = {}
432  _acquireLock()
433  try:
434  for handler in other.handlers:
435  old_formatters[handler] = handler.formatter
436  handler.formatter = self.clean_formatter
437 
438  finally:
439  _releaseLock()
440 
441  kwargs['debug_level'] = debug_level
442  self._log_clean( msg, args, kwargs )
443 
444  for handler, formatter in old_formatters.items():
445  handler.formatter = formatter
446 
447  else:
448 
449  if self._debugger_level & 1 != 0:
450 
451  if msg is EMPTY_KWARG:
452  other = self.active or self
453 
454  old_formatters = {}
455  _acquireLock()
456  try:
457  for handler in other.handlers:
458  old_formatters[handler] = handler.formatter
459  handler.formatter = self.clean_formatter
460 
461  finally:
462  _releaseLock()
463 
464  kwargs['debug_level'] = 1
465  self._log_clean( debug_level, args, kwargs )
466 
467  for handler, formatter in old_formatters.items():
468  handler.formatter = formatter
469 
470  else:
471  other = self.active or self
472 
473  old_formatters = {}
474  _acquireLock()
475  try:
476  for handler in other.handlers:
477  old_formatters[handler] = handler.formatter
478  handler.formatter = self.clean_formatter
479 
480  finally:
481  _releaseLock()
482 
483  kwargs['debug_level'] = 1
484  self._log_clean( debug_level, (msg,) + args, kwargs )
485 
486  for handler, formatter in old_formatters.items():
487  handler.formatter = formatter
488 
489  def _fast_basic(self, debug_level=1, msg=EMPTY_KWARG, *args, **kwargs):
490 
491  if self._debugger_level & debug_level != 0:
492  other = self.active or self
493 
494  old_formatters = {}
495  _acquireLock()
496  try:
497  for handler in other.handlers:
498  old_formatters[handler] = handler.formatter
499  handler.formatter = self.basic_formatter
500 
501  finally:
502  _releaseLock()
503 
504  kwargs['debug_level'] = debug_level
505  self._log( DEBUG, msg, args, **kwargs )
506 
507  for handler, formatter in old_formatters.items():
508  handler.formatter = formatter
509 
510  def basic(self, debug_level=1, msg=EMPTY_KWARG, *args, **kwargs):
511  """
512  Prints the bitwise logging message with the standard basic formatter, which uses by
513  default the format: [%(name)s] %(asctime)s:%(msecs)010.6f %(tickDifference).2e %(message)s
514 
515  The basic logger format can be configured setting the standard formatter with
516  setup() and calling invert() to set the `full_formatter` as
517  the basic formatter.
518  """
519 
520  if type( debug_level ) is int:
521 
522  if msg is EMPTY_KWARG:
523 
524  if self._debugger_level & 1 != 0:
525  other = self.active or self
526 
527  old_formatters = {}
528  _acquireLock()
529  try:
530  for handler in other.handlers:
531  old_formatters[handler] = handler.formatter
532  handler.formatter = self.basic_formatter
533 
534  finally:
535  _releaseLock()
536 
537  kwargs['debug_level'] = 1
538  self._log( DEBUG, debug_level, args, **kwargs )
539 
540  for handler, formatter in old_formatters.items():
541  handler.formatter = formatter
542 
543  elif self._debugger_level & debug_level != 0:
544  other = self.active or self
545 
546  old_formatters = {}
547  _acquireLock()
548  try:
549  for handler in other.handlers:
550  old_formatters[handler] = handler.formatter
551  handler.formatter = self.basic_formatter
552 
553  finally:
554  _releaseLock()
555 
556  kwargs['debug_level'] = debug_level
557  self._log( DEBUG, msg, args, **kwargs )
558 
559  for handler, formatter in old_formatters.items():
560  handler.formatter = formatter
561 
562  else:
563 
564  if self._debugger_level & 1 != 0:
565 
566  if msg is EMPTY_KWARG:
567  other = self.active or self
568 
569  old_formatters = {}
570  _acquireLock()
571  try:
572  for handler in other.handlers:
573  old_formatters[handler] = handler.formatter
574  handler.formatter = self.basic_formatter
575 
576  finally:
577  _releaseLock()
578 
579  kwargs['debug_level'] = 1
580  self._log( DEBUG, debug_level, args, **kwargs )
581 
582  for handler, formatter in old_formatters.items():
583  handler.formatter = formatter
584 
585  else:
586  other = self.active or self
587 
588  old_formatters = {}
589  _acquireLock()
590  try:
591  for handler in other.handlers:
592  old_formatters[handler] = handler.formatter
593  handler.formatter = self.basic_formatter
594 
595  finally:
596  _releaseLock()
597 
598  kwargs['debug_level'] = 1
599  self._log( DEBUG, debug_level, (msg,) + args, **kwargs )
600 
601  for handler, formatter in old_formatters.items():
602  handler.formatter = formatter
603 
604  _old_clean = clean
605  _old_basic = basic
606 
607  def clear(self, delete=False):
608  """
609  Clear the log file contents.
610 
611  @param `delete` if True, the log file will also be removed/deleted and the current file
612  handler will be removed.
613  """
614  active = self.active
615 
616  if active and active.output_file:
617  output_file = active.output_file
618  sys.stderr.write( "Cleaning %s (delete=%s) the file: %s\n" % ( self.name, delete, output_file ) )
619 
620  active._create_file( output_file, active._arguments['rotation'], active._arguments['mode'], True, delete )
621  self.handle_stderr( stderr=self._stderr and not delete, stdout=self._stdout and not delete )
622 
623  def invert(self):
624  """
625  Inverts the default formatter between the preconfigured `basic` and `full_formatter`.
626  """
628 
629  def handle_stderr(self, stderr=False, stdout=False):
630  """
631  Register a exception hook if the logger is capable of logging them to alternate streams.
632 
633  @param `stderr` if True, it will enable the logging hook on the sys.stderr.
634  @param `stdout` if True, it will enable the logging hook on the sys.stdout.
635  """
636  _acquireLock()
637 
638  try:
639  if self._debugme:
640  sys.stderr.write( "stderr: %s, stdout: %s" % ( stderr, stdout ) )
641  sys.stderr.write( "sys.stderr: %s, sys.stdout: %s" % ( sys.stderr, sys.stdout ) )
642  sys.stderr.write( "name: %s, hasStreamHandlers: %s" % ( self.name, self.hasStreamHandlers() ) )
643 
644  if stderr:
645  stderr_replacement.lock( self )
646  else:
647  stderr_replacement.unlock()
648 
649  if stdout:
650  stdout_replacement.lock( self )
651  else:
652  stdout_replacement.unlock()
653 
654  except Exception:
655  self.exception( "Could not register the sys.stderr stream handler" )
656 
657  finally:
658  _releaseLock()
659 
660  def _disable(self, stream=False, file=False):
661  """
662  Delete all automatically setup handlers created by the automatic `setup()`.
663  """
664  is_successful = False
665 
666  if stream and self._stream:
667  self.removeHandler( self._stream )
668 
669  is_successful = True
670  self._stream = None
671 
672  if file:
673  self.handle_stderr( stderr=False, stdout=False )
674 
675  if self._file:
676  self.removeHandler( self._file )
677  self._file.close()
678 
679  is_successful = True
680  self._file = None
681 
682  return is_successful
683 
684  def _configure_force(self, force_debug, active):
685  """
686  Configure the force debug level feature.
687  """
688  if force_debug:
689  active._force_debug = force_debug
690  self._debug_level = force_debug
691 
692  elif force_debug != None:
693  active._force_debug = None
694 
695  if active._force_debug:
696  self.debug_level = active._force_debug
697 
698  # print('active._force_debug %s' % active._force_debug)
699  # print('logger.debug_level %s' % logger.debug_level)
700 
701  def setup(self, file=EMPTY_KWARG, mode=EMPTY_KWARG, delete=EMPTY_KWARG, date=EMPTY_KWARG, levels=EMPTY_KWARG,
702  function=EMPTY_KWARG, name=EMPTY_KWARG, time=EMPTY_KWARG, msecs=EMPTY_KWARG, tick=EMPTY_KWARG,
703  separator=EMPTY_KWARG, formatter=EMPTY_KWARG, rotation=EMPTY_KWARG, **kwargs):
704  """
705  If `file` parameter is passed, instead of output the debug to the standard output
706  stream, send it to a file on the file system, which is faster for large outputs.
707 
708  As this function remembers most of its parameters passed from previous calls, you need to
709  explicitly pass `file=None` with `delete=True` if you want to disable the file
710  system output after setting up it to log to a file. See the function Debugger::reset()
711  for the default parameters values used on this setup utility.
712 
713  If the parameters `date`, `levels`, `function`, `name`, `time`, `tick` and `msecs` are
714  nonempty strings, their value will be used to defining their configuration formatting.
715  For example, if you pass name="%(name)s: " the function name will be displayed as
716  `name: `, instead of the default `[name] `.
717 
718  If you change your `sys.stderr` after creating an StreamHandler, you need to pass `handlers=True`
719  to make it to recreate the StreamHandler because of the old reference to `sys.stderr`.
720 
721  Single page cheat-sheet about Python string formatting pyformat.info
722  https://github.com/ulope/pyformat.info
723 
724  Override a method at instance level
725  https://stackoverflow.com/questions/394770/override-a-method-at-instance-level
726 
727  @param `file` a relative or absolute path to the log file. If empty the output
728  will be sent to the standard output stream.
729 
730  @param `mode` the file write mode on the file system (default `a`). It can be `a`
731  to append to the existent file, or `w` to erase the existent file
732  before start. If the parameter `rotation` is set to non zero, then
733  this will be an integer value setting how many backups are going to
734  be keep when doing the file rotation as specified on
735  logging::handlers::RotatingFileHandler documentation.
736 
737  @param `delete` if True (default False), it will delete the other handler before
738  activate the current one, otherwise it will only activate the
739  selected handler. Useful for enabling multiple handlers
740  simultaneously.
741 
742  @param `date` if True, add to the `full_formatter` the date on the format `%Y-%m-%d`.
743  @param `levels` if True, add to the `full_formatter` the log levels.
744  @param `function` if True, add to the `full_formatter` the function name.
745  @param `name` if True, add to the `full_formatter` the logger name.
746  @param `time` if True, add to the `full_formatter` the time on the format `%H:%M:%S:milliseconds.microseconds`.
747  @param `msecs` if True, add to the `full_formatter` the current milliseconds on the format ddd,ddddd.
748  @param `tick` if True, add to the `full_formatter` the time.perf_counter() difference from the last call.
749  @param `separator` if True, add to the `full_formatter` the a ` - ` to the end of the log record header.
750  @param `formatter` if not None, replace this `full_formatter` by the logging.Formatter() provided.
751 
752  @param `rotation` if non zero, creates a RotatingFileHandler with the specified size
753  in Mega Bytes instead of FileHandler when creating a log file by the
754  `file` option. See logging::handlers::RotatingFileHandler for more
755  information. See the parameter `mode` to specify how many files at
756  most should be created by the rotation algorithm.
757 
758  @param `handlers` if True (default False), it will force to create the handlers,
759  even if there are no changes on the current saved default parameters.
760  Its value is not saved between calls to this setup().
761 
762  @param `stderr` if True (default True), it will install a listener to the `sys.stderr`
763  console output. This is useful for logging not handled exceptions.
764 
765  @param `stdout` if True (default False), it will install a listener to the `sys.stdout`
766  console output. This is useful for logging all console output to a file.
767 
768  @param `force` if an integer, set the `debug_level` into all created loggers hierarchy.
769  Its value is not saved between calls to this setup().
770 
771  @param `active` if True (default True), it will search for any other active logger in
772  the current logger hierarchy and do the setup call on him. If no active
773  logger is found, it will do the setup on the current logger object,
774  Its value is not saved between calls to this setup().
775 
776  @param `fast` if True (default False), it will disabled support to log message
777  calls as `log('message')` and `log(333)`, and it force all log
778  message calls to respect the format `log(1, message)`. This tradeoff
779  gains dropping from about 10 seconds to about 7 seconds each
780  10.000.000 log calls, when the logging debug level is set as
781  disabled.
782 
783  @param `stream` (default sys.stderr), an file like object to the StreamHandler use
784  to print things when outputting results.
785 
786  @param `trimname` (default 0), Remove these nth characters from the logger name
787  while creating the log record to print on the screen. Useful to keep
788  several loggers grouped together but hide their parent.
789  """
790  self._setup( file=file, mode=mode, delete=delete, date=date, levels=levels,
791  function=function, name=name, time=time, msecs=msecs, tick=tick,
792  separator=separator, formatter=formatter, rotation=rotation, **kwargs )
793 
794  def _setup(self, **kwargs):
795  """
796  Allow to pass positional arguments to `setup()`.
797  """
798  handlers = kwargs.pop( 'handlers', False )
799 
800  force_debug = kwargs.pop( "force", None )
801  _fix_children = kwargs.pop( '_fix_children', False )
802 
803  if kwargs.pop( 'active', True ):
804  active = self.active
805  logger = active or self
806  self._configure_force( force_debug, logger)
807 
808  else:
809  logger = self
810  self._configure_force( force_debug, self.active or self)
811 
812  has_changes = False
813  arguments = logger._arguments
814 
815  for kwarg in kwargs:
816  value = kwargs[kwarg]
817 
818  if value != EMPTY_KWARG:
819 
820  if value != arguments[kwarg]:
821  has_changes = True
822  arguments[kwarg] = value
823 
824  if kwarg == 'trimname':
825  self.trimname = len( value ) + 1
826 
827  if has_changes \
828  or handlers \
829  or ( not logger._stream \
830  and not logger._file ):
831 
832  if _fix_children:
833  logger.fix_children( lambda logger: logger.removeHandlers() )
834 
835  logger.full_formatter = logger._setup_formatter( logger._arguments )
836  logger._setup_log_handlers()
837 
838  def _setup_log_handlers(self):
839  self._setup_fast_loggers()
840  arguments = self._arguments
841 
842  # import traceback
843  # traceback.print_stack()
844 
845  if arguments['file']:
846  output_file = self.get_debug_file_path( arguments['file'] )
847 
848  sys.stderr.write( "".join( self._get_time_prefix( datetime.datetime.now() ) )
849  + "Logging to the file %s\n" % output_file )
850 
851  self._create_file( output_file, arguments['rotation'], arguments['mode'] )
852  self._disable( stream=arguments['delete'] )
853 
854  else:
855  self._disable( stream=True )
856  _acquireLock()
857 
858  try:
859  self._stream = logging.StreamHandler( arguments['stream'] )
860  self._stream.formatter = self.full_formatter
861 
862  except Exception:
863  self.exception( "Could not create the stream handler" )
864 
865  finally:
866  _releaseLock()
867 
868  self.addHandler( self._stream )
869  self._disable( file=arguments['delete'] )
870 
871  def _create_file(self, output_file, rotation, mode, clear=False, delete=False):
872  backup_count = mode
873  mode = 'w' if clear else mode
874 
875  if self._disable( file=True ):
876 
877  if delete:
878  os.remove( output_file )
879  return
880 
881  elif clear:
882 
883  with open( output_file, 'w' ) as file:
884  file.truncate()
885 
886  if rotation > 0:
887  rotation = rotation * 1024 * 1024
888 
889  backup_count = abs( backup_count ) if isinstance( backup_count, int ) else 2
890  _file = ConcurrentRotatingFileHandler( output_file, maxBytes=rotation, backupCount=backup_count )
891 
892  else:
893 
894  if not isinstance( mode, str ):
895  raise ValueError( "The mode argument `%s` must be instance of string." % mode )
896 
897  _file = logging.FileHandler( output_file, mode )
898 
899  _file.formatter = self.full_formatter
900  self._file = _file
901  self.addHandler( _file )
902 
903  def _setup_fast_loggers(self):
904 
905  if self._arguments['fast']:
906  self.__class__ = FastDebugger
907  self.clean = self._fast_clean
908  self.basic = self._fast_basic
909 
910  else:
911  self.__class__ = Debugger
912  self.clean = self._old_clean
913  self.basic = self._old_basic
914 
915  def warn(self, msg, *args, **kwargs):
916  """
917  Fix second indirection created by the super().warn() method, by directly calling _log()
918  """
919 
920  if self.isEnabledFor( WARNING ):
921  self._log( WARNING, msg, args, **kwargs )
922 
923  def exception(self, msg, *args, **kwargs):
924  """
925  Fix second indirection created by the super().error() method, by directly calling _log()
926  """
927  if self.isEnabledFor(ERROR):
928  self._log(ERROR, msg, args, exc_info=True, **kwargs)
929 
930  if is_python2:
931 
932  def _log(self, level, msg, args, exc_info=None, extra={}, stack_info=False, debug_level=0, **kwargs):
933  self._current_tick = timeit.default_timer()
934 
935  debug_level = "(%d)" % debug_level if debug_level else ""
936  extra.update( {"debugLevel": debug_level, "tickDifference": self._current_tick - self._last_tick} )
937 
938  if any( setup_arg in kwargs for setup_arg in changeable_setup_arguments ):
939  other = self.active or self
940  new_arguments = dict( self._arguments )
941 
942  for setup_arg in changeable_setup_arguments:
943  new_arguments[setup_arg] = kwargs.pop( setup_arg, new_arguments.get( setup_arg ) )
944 
945  new_formatter = self._create_formatter( new_arguments )
946 
947  old_formatters = {}
948  _acquireLock()
949  try:
950  for handler in other.handlers:
951  old_formatters[handler] = handler.formatter
952  handler.formatter = new_formatter
953 
954  finally:
955  _releaseLock()
956 
957  super( Debugger, self )._log( level, msg, args, exc_info, extra )
958  self._last_tick = self._current_tick
959 
960  for handler, formatter in old_formatters.items():
961  handler.formatter = formatter
962 
963  else:
964  super( Debugger, self )._log( level, msg, args, exc_info, extra )
965  self._last_tick = self._current_tick
966 
967  else:
968 
969  def _log(self, level, msg, args, exc_info=None, extra={}, stack_info=False, debug_level=0, **kwargs):
970  self._current_tick = timeit.default_timer()
971 
972  debug_level = "(%d)" % debug_level if debug_level else ""
973  extra.update( {"debugLevel": debug_level, "tickDifference": self._current_tick - self._last_tick} )
974 
975  if any( setup_arg in kwargs for setup_arg in changeable_setup_arguments ):
976  other = self.active or self
977  new_arguments = dict( self._arguments )
978 
979  for setup_arg in changeable_setup_arguments:
980  new_arguments[setup_arg] = kwargs.pop( setup_arg, new_arguments.get( setup_arg ) )
981 
982  new_formatter = self._create_formatter( new_arguments )
983 
984  old_formatters = {}
985  _acquireLock()
986  try:
987  for handler in other.handlers:
988  old_formatters[handler] = handler.formatter
989  handler.formatter = new_formatter
990 
991  finally:
992  _releaseLock()
993 
994  super()._log( level, msg, args, exc_info, extra, stack_info )
995  self._last_tick = self._current_tick
996 
997  for handler, formatter in old_formatters.items():
998  handler.formatter = formatter
999 
1000  else:
1001  super()._log( level, msg, args, exc_info, extra, stack_info )
1002  self._last_tick = self._current_tick
1003 
1004  def _log_clean(self, msg, args, kwargs):
1005  record = CleanLogRecord( self.level, self.name, msg, args, kwargs )
1006  self.handle( record )
1007 
1008  @classmethod
1009  def _setup_formatter(cls, arguments):
1010  # print('arguments', arguments)
1011 
1012  if arguments['formatter']:
1013 
1014  if isinstance( arguments['formatter'], logging.Formatter ):
1015  return arguments['formatter']
1016 
1017  else:
1018  raise ValueError( "Error: The formatter %s must be an instance of logging.Formatter" % arguments['formatter'] )
1019 
1020  else:
1021  return cls._create_formatter( arguments )
1022 
1023  @classmethod
1024  def _create_formatter(cls, arguments):
1025  tick = cls.getFormat( arguments, 'tick', "%(tickDifference).2e" )
1026  msecs = cls.getFormat( arguments, 'msecs', "%(msecs)010.6f", tick )
1027  levels = cls.getFormat( arguments, 'levels', "%(levelname)s%(debugLevel)s" )
1028 
1029  time_format = cls.getFormat( arguments, 'time', "%H:%M:%S", not msecs and tick )
1030  msecs = ":" + msecs if msecs and arguments['time'] else msecs
1031 
1032  date_format = cls.getFormat( arguments, 'date', "%Y-%m-%d", time_format )
1033  date_format += time_format
1034  time = "%(asctime)s" if date_format else ""
1035 
1036  separator_format = cls.getFormat( arguments, 'separator', " - " )
1037  function = cls.getFormat( arguments, 'function', "%(funcName)s:%(lineno)d", levels )
1038  name = cls.getFormat( arguments, 'name', "%(name)s", levels and not function )
1039 
1040  name += "." if name and function else ""
1041  extra_spacing = separator_format if ( name or function or levels ) and ( time or msecs or tick ) else ""
1042  separator = separator_format if time or msecs or tick or name or function or levels else ""
1043 
1044  # print("time '%s', msecs '%s', tick '%s', extra_spacing '%s', name '%s', function '%s', levels '%s', separator '%s' date_format '%s'" % ( time, msecs, tick, extra_spacing, name, function, levels, separator, date_format ) )
1045 
1046  return logging.Formatter( "{}{}{}{}{}{}{}{}%(message)s".format(
1047  time, msecs, tick, extra_spacing, name, function, levels, separator ), date_format )
1048 
1049  @staticmethod
1050  def getFormat(arguments, setting, default, next_parameter=""):
1051  value = arguments[setting]
1052 
1053  if isinstance( value, str ):
1054  return value
1055 
1056  if value:
1057  value = default + ( " " if next_parameter else "" )
1058 
1059  else:
1060  value = ""
1061 
1062  return value
1063 
1064  def _get_time_prefix(self, currentTime):
1065  return [ "[%s]" % self.name,
1066  " %02d" % currentTime.hour,
1067  ":%02d" % currentTime.minute,
1068  ":%02d" % currentTime.second,
1069  ":%07d " % currentTime.microsecond ]
1070 
1071  def addHandler(self, handler):
1072  """
1073  Override the super() method to correctly set up `sys.stderr/stdout` handlers.
1074 
1075  When adding a new handler and either `sys.stderr` or `sys.stdout` is enabled. It is
1076  required to check whether there is also a stream handler enabled, and if so, then, we
1077  need to add a log record filter to avoid infinity log records trying to be displayed.
1078  See also logging::Logger::addHandler().
1079  """
1080 
1081  if self._stderr or self._stdout:
1082 
1083  if self._file and self._stream:
1084  handler.addFilter( self._file_context_filter )
1085 
1086  self._has_file_context_filter = True
1087  _acquireLock()
1088 
1089  try:
1090  for other_handler in self.handlers:
1091  other_handler.addFilter( self._file_context_filter )
1092 
1093  finally:
1094  _releaseLock()
1095 
1096  self.handle_stderr( self._stderr, self._stdout )
1097 
1098  # else: # TODO: Support other this logic also for other handlers and the builtin _stream and _file
1099 
1100  super( Debugger, self ).addHandler( handler )
1101 
1102  def removeHandler(self, handler):
1103  """
1104  Override the super() method to correctly set up `sys.stderr/stdout` handlers.
1105 
1106  When
1107  See also logging::Logger::removeHandler().
1108  """
1109 
1110  if self._has_file_context_filter:
1111 
1112  if not self._stderr and not self._stdout or not self._file or not self._stream:
1113  handler.removeFilter( self._file_context_filter )
1114 
1115  self._has_file_context_filter = False
1116  _acquireLock()
1117 
1118  try:
1119  for other_handler in self.handlers:
1120  other_handler.removeFilter( self._file_context_filter )
1121 
1122  finally:
1123  _releaseLock()
1124 
1125  self.handle_stderr( self._stderr, self._stdout )
1126 
1127  # else: # TODO: Support other this logic also for other handlers and the builtin _stream and _file
1128 
1129  super( Debugger, self ).removeHandler( handler )
1130 
1131  @classmethod
1133  """
1134  Delete all loggers created by `getLogger()` calls.
1135  """
1136  _acquireLock()
1137 
1138  try:
1139  cls.manager.loggerDict.clear()
1140 
1141  except Exception:
1142  cls.exception("Could not delete all registered loggers!")
1143 
1144  finally:
1145  _releaseLock()
1146 
1147  def delete(self):
1148  self.removeHandlers()
1149  _acquireLock()
1150 
1151  try:
1152  del self.manager.loggerDict[self.name]
1153 
1154  except Exception:
1155  self.exception("Could not delete the logger %s!", self.name)
1156 
1157  finally:
1158  _releaseLock()
1159 
1160 
1161  def removeHandlers(self):
1162  """
1163  Delete all handlers registered to the current logger.
1164  """
1165  if self._debugme: sys.stderr.write( "Removing all handlers from %s...\n" % self.name )
1166  self._disable( stream=True, file=True )
1167 
1168  for handler in self.handlers:
1169  self.removeHandler( handler )
1170 
1172  """
1173  Return True if the current logger has some stream handler defined.
1174  """
1175 
1176  for handler in self.handlers:
1177  # print( "Name: %s, handler: %s" % ( self.name, type( handler ) ) )
1178 
1179  if "StreamHandler" in str( type( handler ) ):
1180  return True
1181 
1182  return False
1183 
1184  def fix_children(self, callable_action):
1185  """
1186  When automatically creating loggers, some children logger can be setup before the
1187  parent logger, if the children logger is instantiated on module level and its module
1188  is imported before the parent logger to be setup.
1189 
1190  Then this will cause the the both parent and child logger to be setup and have handlers
1191  outputting data to theirs output stream. Hence, here we fix that by disabling the
1192  children logger when they are setup before the parent logger.
1193 
1194  This method is only called automatically by this module level function `getLogger()`
1195  when is set to automatically setup the logger. If you are not using the automatic setup
1196  you do not need to use this function because you should know what you are doing and how
1197  you should setup your own loggers.
1198  """
1199  loggers = Debugger.manager.loggerDict
1200  parent_name = self.name
1201  parent_name_length = len( parent_name )
1202 
1203  for logger_name in loggers:
1204  logger = loggers[logger_name]
1205 
1206  if not isinstance( logger, PlaceHolder ):
1207 
1208  # i.e., if logger.parent.name.startswith( parent_name )
1209  if logger.parent.name[:parent_name_length] == parent_name:
1210  callable_action( logger )
1211 
1212  @classmethod
1213  def get_debug_file_path(cls, file_path):
1214  """
1215  Reliably detect Windows in Python
1216  https://stackoverflow.com/questions/1387222/reliably-detect-windows-in-python
1217 
1218  Convert "D:/User/Downloads/debug.txt"
1219  To "/cygwin/D/User/Downloads/debug.txt"
1220  To "/mnt/D/User/Downloads/debug.txt"
1221  """
1222  is_absolute = os.path.isabs( file_path )
1223  platform_info = platform.platform( True ).lower()
1224 
1225  if is_absolute \
1226  and not file_path.startswith( "/cygdrive/" ) \
1227  and "cygwin" in platform_info:
1228 
1229  new_output = "/cygdrive/" + cls.remove_windows_driver_letter( file_path )
1230 
1231  elif is_absolute \
1232  and not file_path.startswith( "/mnt/" ) \
1233  and "linux" in platform_info \
1234  and "microsoft" in platform_info:
1235 
1236  new_output = cls.remove_windows_driver_letter( file_path )
1237  new_output = "/mnt/" + new_output[0].lower() + new_output[1:]
1238 
1239  else:
1240  new_output = os.path.abspath( file_path )
1241 
1242  # print( "Debugger, is_absolute: %s" % is_absolute )
1243  # print( "Debugger, new_output: %s" % new_output )
1244  # print( "Debugger, isabs: %s" % str( os.path.isabs( new_output ) ) )
1245  # print( "Debugger, platform_info: %s" % platform_info )
1246  return new_output
1247 
1248  @classmethod
1249  def remove_windows_driver_letter(cls, file_path):
1250  file_path = file_path.replace( ":", "", 1 )
1251  file_path = file_path.replace( "\\", "/", 1 )
1252  return file_path.replace( "\\\\", "/", 1 )
1253 
1254  @classmethod
1255  def getRootLogger(cls):
1256  """
1257  Return the main root logger `root_debugger` used by this extension of the standard
1258  logging module.
1259  """
1260  return cls.root
1261 
1262  def __str__(self):
1263  total_loggers = [0]
1264  representations = []
1265 
1266  place_holders = []
1267  loggers = [self.root]
1268  loggers_dict = Debugger.manager.loggerDict
1269 
1270  def add(logger):
1271  total_loggers[0] += 1
1272  current_logger = "True " if logger == self else "False"
1273 
1274  if isinstance( logger, PlaceHolder ):
1275  representations.append( "%2s. name(%s), %s" %
1276  ( str( total_loggers[0] ), current_logger, "".join(
1277  ["loggerMap(%s): %s" % (item.name, logger.loggerMap[item])
1278  for item in logger.loggerMap] ) ) )
1279 
1280  else:
1281  representations.append( "%2s. _debugger_level: %3d, level: %2s, propagate: %5s, "
1282  "_frame_level: %2d, name(%s): %s, _stream: %s, _file: %s, arguments: %s" %
1283  ( str( total_loggers[0] ), logger._debugger_level, logger.level, logger.propagate,
1284  logger._frame_level, current_logger, logger.name, logger._stream, logger._file,
1285  logger._arguments ) )
1286 
1287  for logger_name in loggers_dict:
1288  logger = loggers_dict[logger_name]
1289 
1290  if isinstance( logger, PlaceHolder ):
1291  place_holders.append( logger )
1292 
1293  else:
1294  loggers.append( logger )
1295 
1296  loggers.sort( key=lambda item: item.name, reverse=True )
1297  loggers.extend( place_holders )
1298 
1299  for logger in loggers:
1300  add( logger )
1301 
1302  return "\n%s" % "\n".join( reversed( representations ) )
1303 
1304  # Copied from the python 3.6.3 and 2.7.14 implementation, only changing the `sys._getframe(3)`
1305  # to `sys._getframe(4)` because due the inheritance, we need to take a higher frame to get
1306  # the correct function name, otherwise the result would always be `__call__`, which is the
1307  # internal function we use here.
1308  #
1309  # Find the stack frame of the caller so that we can note the source file name, line number
1310  # and function name.
1311  if is_python2:
1312 
1313  def findCaller(self):
1314  f = currentframe(self._frame_level)
1315  #On some versions of IronPython, currentframe() returns None if
1316  #IronPython isn't run with -X:Frames.
1317  if f is not None:
1318  f = f.f_back
1319  rv = "(unknown file)", 0, "(unknown function)"
1320  while hasattr(f, "f_code"):
1321  co = f.f_code
1322  filename = os.path.normcase(co.co_filename)
1323  if filename == _srcfile:
1324  f = f.f_back
1325  continue
1326  rv = (co.co_filename, f.f_lineno, co.co_name)
1327  break
1328  return rv
1329 
1330  else:
1331 
1332  def findCaller(self, stack_info=False):
1333  f = currentframe(self._frame_level)
1334  #On some versions of IronPython, currentframe() returns None if
1335  #IronPython isn't run with -X:Frames.
1336  if f is not None:
1337  f = f.f_back
1338  rv = "(unknown file)", 0, "(unknown function)", None
1339  while hasattr(f, "f_code"):
1340  co = f.f_code
1341  filename = os.path.normcase(co.co_filename)
1342  if filename == _srcfile:
1343  f = f.f_back
1344  continue
1345  sinfo = None
1346  if stack_info:
1347  sio = io.StringIO()
1348  sio.write('Stack (most recent call last):\n')
1349  traceback.print_stack(f, file=sio)
1350  sinfo = sio.getvalue()
1351  if sinfo[-1] == '\n':
1352  sinfo = sinfo[:-1]
1353  sio.close()
1354  rv = (co.co_filename, f.f_lineno, co.co_name, sinfo)
1355  break
1356  return rv
1357 
1358  if is_python2:
1359 
1360  def makeRecord(self, name, level, fn, lno, msg, args, exc_info, func=None, extra=None):
1361  """
1362  A factory method which can be overridden in subclasses to create
1363  specialized LogRecords.
1364  """
1365  rv = SmartLogRecord(name[self.trimname:], level, fn, lno, msg, args, exc_info, func)
1366  if extra is not None:
1367  for key in extra:
1368  if (key in ["message", "asctime"]) or (key in rv.__dict__):
1369  raise KeyError("Attempt to overwrite %r in LogRecord" % key)
1370  rv.__dict__[key] = extra[key]
1371  return rv
1372 
1373  else:
1374 
1375  def makeRecord(self, name, level, fn, lno, msg, args, exc_info, func=None, extra=None, sinfo=None):
1376  """
1377  A factory method which can be overridden in subclasses to create
1378  specialized LogRecords.
1379  """
1380  rv = SmartLogRecord(name[self.trimname:], level, fn, lno, msg, args, exc_info, func, sinfo)
1381  if extra is not None:
1382  for key in extra:
1383  if (key in ["message", "asctime"]) or (key in rv.__dict__):
1384  raise KeyError("Attempt to overwrite %r in LogRecord" % key)
1385  rv.__dict__[key] = extra[key]
1386  return rv
1387 
1388 
1390  """
1391  This does allow to replace the standard __call__ implementation by a faster one.
1392 
1393  Python does not allow the `__call__` method to be overridden.
1394  https://stackoverflow.com/questions/34261111/patch-call-of-a-function
1395  """
1396 
1397  def __call__(self, debug_level=1, msg=EMPTY_KWARG, *args, **kwargs):
1398 
1399  if self._debugger_level & debug_level != 0:
1400  kwargs['debug_level'] = debug_level
1401  self._log( DEBUG, msg, args, **kwargs )
1402 
1403 
1404 class _SmartLogRecord(object):
1405  """
1406  Creates a LogRecord which concatenates trailing arguments instead of raising an exception.
1407  """
1408 
1409  def _getMessage(self, remaining_arguments):
1410  """
1411  Use a function to force exception handling not break the while loop where this is used.
1412  """
1413 
1414  try:
1415  args = self.args
1416 
1417  if args:
1418 
1419  # https://stackoverflow.com/questions/53002709/why-var-1-variable-prints-var-instead-of-raising-the-exception-typeer
1420  if isinstance( args, MutableMapping ):
1421  new_msg = self.msg % args
1422 
1423  if new_msg == self.msg:
1424  remaining_arguments.append( str( args ) )
1425  remaining_arguments.append( new_msg )
1426 
1427  else:
1428  remaining_arguments.append( new_msg )
1429 
1430  else:
1431  remaining_arguments.append( self.msg % args )
1432 
1433  else:
1434  remaining_arguments.append( self.msg )
1435 
1436  return False
1437 
1438  # A logging error should not stop the running program
1439  # except (TypeError, ValueError) as error:
1440  except Exception as error:
1441  self.args = args[:-1]
1442 
1443  # print('error', error)
1444  remaining_arguments.append( str( args[-1] ) )
1445 
1446  if len( args ) - 1 > 0:
1447  return True
1448 
1449  else:
1450  remaining_arguments.append( self.msg )
1451  return False
1452 
1453  def getMessage(self):
1454  """
1455  Return the message for this LogRecord.
1456 
1457  Return the message for this LogRecord after merging any user-supplied
1458  arguments with the message.
1459  """
1460  # print('self.msg', self.msg, ', self.args', self.args)
1461  remaining_arguments = []
1462  self.msg = str( self.msg )
1463 
1464  # https://stackoverflow.com/questions/38127563/handle-an-exception-in-a-while-loop
1465  while self._getMessage( remaining_arguments ): pass
1466  return " ".join( reversed( remaining_arguments ) )
1467 
1468 
1470  pass
1471 
1472 
1473 class CleanLogRecord(_SmartLogRecord):
1474 
1475  def __init__(self, level, name, msg, args, kwargs):
1476  # https://stackoverflow.com/questions/9728243/is-self-dict-updatekwargs-good-or-poor-style
1477  if 'extra' in kwargs:
1478  for key, value in kwargs['extra'].items(): setattr( self, key, value )
1479  self.name = name
1480  self.msg = msg
1481  self.levelno = level
1482 
1483  self.levelname = getLevelName( level )
1484  self.pathname = "No Path Name"
1485  self.filename = "No Filename"
1486  self.module = "Unknown module"
1487 
1488  self.debugLevel = ""
1489  self.tickDifference = 0.0
1490 
1491  self.exc_info = None
1492  self.exc_text = None
1493  self.stack_info = None
1494  self.lineno = 0
1495 
1496  self.args = args
1497  self.funcName = "No Function"
1498  self.created = 0
1499  self.msecs = 0
1500  self.relativeCreated = 0
1501 
1502  self.thread = None
1503  self.threadName = None
1504  self.processName = None
1505  self.process = None
1506 
1507  def __str__(self):
1508  return '<CleanLogRecord: %s, %s, %s, %s, "%s">'%(self.name, self.levelno,
1509  self.pathname, self.lineno, self.msg)
1510 
1511 class FileHandlerContextFilter(logging.Filter):
1512  """
1513  This filter avoids duplicated information to be displayed to the StreamHandler log.
1514  https://stackoverflow.com/questions/616645/how-to-duplicate-sys-stdout-to-a-log-file/48970154
1515  """
1516  def filter(self, record):
1517  return not "_duplicated_from_file" in record.__dict__
1518 
1519 
1520 # Setup the alternate debugger, completely independent of the standard logging module Logger class
1521 root = Debugger( "root_debugger", "WARNING" )
1522 Debugger.root = root
1523 Debugger._file_context_filter = FileHandlerContextFilter()
1524 
1525 Debugger.manager = Manager( root )
1526 Debugger.manager.setLoggerClass( Debugger )
1527 
1528 
1529 def getLogger(debug_level=127, logger_name=None,
1530  file=EMPTY_KWARG, mode=EMPTY_KWARG, delete=EMPTY_KWARG, date=EMPTY_KWARG, levels=EMPTY_KWARG,
1531  function=EMPTY_KWARG, name=EMPTY_KWARG, time=EMPTY_KWARG, msecs=EMPTY_KWARG, tick=EMPTY_KWARG,
1532  separator=EMPTY_KWARG, formatter=EMPTY_KWARG, rotation=EMPTY_KWARG, level=EMPTY_KWARG, **kwargs):
1533  """
1534  Return a logger with the specified name, creating it if necessary.
1535 
1536  If no name is specified, return a new logger based on the main logger file name. See also
1537  logging::Manager::getLogger(). It has the same parameters as Debugger::setup() with the addition
1538  of the following parameters:
1539 
1540  @param `logger_name` the name of this logger accordingly with the standard logging.Logger()
1541  documentation.
1542 
1543  @param `level` an integer/string with the enabled log level from logging module
1544 
1545  @param `levels` if True, add to the `full_formatter` the log levels.
1546 
1547  @param from `file` until `**kwargs` there are the named parameters passed to the Debugger.setup()
1548  member function.
1549 
1550  @param `setup` if True (default), ensure there is at least one handler enabled in the hierarchy,
1551  then, the current created active Logger setup method will be called.
1552 
1553  @param `debugme` if True, log to the `stderr` logging self debugging messages.
1554 
1555  @seealso Debugger::setup()
1556  """
1557  return _getLogger( debug_level, logger_name,
1558  file=file, mode=mode, delete=delete, date=date, levels=levels,
1559  function=function, name=name, time=time, msecs=msecs, tick=tick,
1560  separator=separator, formatter=formatter, rotation=rotation, level=level, **kwargs )
1561 
1562 
1563 def _getLogger(debug_level=127, logger_name=None, **kwargs):
1564  """
1565  Allow to pass positional arguments to `getLogger()`.
1566  """
1567  level = kwargs.get( "level", EMPTY_KWARG )
1568  Debugger._debugme = kwargs.get( "debugme", False )
1569 
1570  try:
1571  debug_level, logger_name = _get_debug_level( debug_level, logger_name )
1572 
1573  except Exception as error:
1574  sys.stderr.write('%s\n\n' % error)
1575  debug_level, logger_name = 127, "logger"
1576 
1577  logger = Debugger.manager.getLogger( logger_name )
1578  logger.debug_level = debug_level
1579 
1580  if level != EMPTY_KWARG:
1581  kwargs.pop( "level" )
1582  logger.setLevel( level )
1583 
1584  if kwargs.pop( "setup", True ) == True:
1585  logger.setup( _fix_children=True, **kwargs )
1586 
1587  return logger
1588 
1589 
1590 def _get_debug_level(debug_level, logger_name):
1591 
1592  if logger_name:
1593 
1594  if isinstance( logger_name, int ):
1595  debug_level, logger_name = logger_name, debug_level
1596 
1597  if isinstance( logger_name, int ):
1598  raise ValueError( "The variable `logger_name` must be an instance of string, instead of `%s`." % str( logger_name ) )
1599 
1600  else:
1601 
1602  if not isinstance( debug_level, int ):
1603 
1604  try:
1605  debug_level = int( debug_level )
1606 
1607  except Exception as error:
1608  raise ValueError( "The variable `debug_level` must be an instance of int, instead of `%s` (%s)." % ( debug_level, error ) )
1609 
1610  else:
1611 
1612  if isinstance( debug_level, int ):
1613  logger_name = "logger"
1614 
1615  else:
1616  logger_name = debug_level
1617  debug_level = 127
1618 
1619  return debug_level, logger_name
1620 
def exception(self, msg, args, kwargs)
Definition: logger.py:923
def _setup_fast_loggers(self)
Definition: logger.py:903
def _create_file(self, output_file, rotation, mode, clear=False, delete=False)
Definition: logger.py:871
def _fast_clean(self, debug_level=1, msg=EMPTY_KWARG, args, kwargs)
Definition: logger.py:376
def handle_stderr(self, stderr=False, stdout=False)
Definition: logger.py:629
def fix_children(self, callable_action)
Definition: logger.py:1184
def __call__(self, debug_level=1, msg=EMPTY_KWARG, args, kwargs)
Definition: logger.py:346
def _get_time_prefix(self, currentTime)
Definition: logger.py:1064
def addHandler(self, handler)
Definition: logger.py:1071
def new_line(self, debug_level=1, count=1)
Definition: logger.py:338
def __init__(self, logger_name, logger_level=None)
Definition: logger.py:144
def _create_formatter(cls, arguments)
Definition: logger.py:1024
def warn(self, msg, args, kwargs)
Definition: logger.py:915
def _disable(self, stream=False, file=False)
Definition: logger.py:660
def _setup(self, kwargs)
Definition: logger.py:794
def removeHandler(self, handler)
Definition: logger.py:1102
def clear(self, delete=False)
Definition: logger.py:607
def _getMessage(self, remaining_arguments)
Definition: logger.py:1409
def setup(self, file=EMPTY_KWARG, mode=EMPTY_KWARG, delete=EMPTY_KWARG, date=EMPTY_KWARG, levels=EMPTY_KWARG, function=EMPTY_KWARG, name=EMPTY_KWARG, time=EMPTY_KWARG, msecs=EMPTY_KWARG, tick=EMPTY_KWARG, separator=EMPTY_KWARG, formatter=EMPTY_KWARG, rotation=EMPTY_KWARG, kwargs)
Definition: logger.py:703
def newline(self, debug_level=1, count=1)
Definition: logger.py:330
def getFormat(arguments, setting, default, next_parameter="")
Definition: logger.py:1050
def get_debug_file_path(cls, file_path)
Definition: logger.py:1213
def _log(self, level, msg, args, exc_info=None, extra={}, stack_info=False, debug_level=0, kwargs)
Definition: logger.py:932
def setup_basic(self, kwargs)
Definition: logger.py:297
def _formatter_arguments(self)
Definition: logger.py:307
def makeRecord(self, name, level, fn, lno, msg, args, exc_info, func=None, extra=None)
Definition: logger.py:1360
def remove_windows_driver_letter(cls, file_path)
Definition: logger.py:1249
def traceback(self, kwargs)
Definition: logger.py:273
def _setup_formatter(cls, arguments)
Definition: logger.py:1009
def _fast_basic(self, debug_level=1, msg=EMPTY_KWARG, args, kwargs)
Definition: logger.py:489
def _log_clean(self, msg, args, kwargs)
Definition: logger.py:1004
def _configure_force(self, force_debug, active)
Definition: logger.py:684