54 import logging.handlers
56 from collections
import MutableMapping
57 from logging
import getLevelName
59 from logging
import Logger
60 from logging
import Manager
61 from logging
import PlaceHolder
62 from logging
import LogRecord
64 from logging
import DEBUG
65 from logging
import WARNING
66 from logging
import ERROR
68 from logging
import _srcfile
69 from logging
import _acquireLock
70 from logging
import _releaseLock
74 from concurrent_log_handler
import ConcurrentRotatingFileHandler
76 except( ImportError, ValueError ):
77 from logging
import FileHandler
81 Fall back with the basic file logger when ConcurrentRotatingFileHandler is not available. 84 def __init__(self, output_file, maxBytes=0, backupCount=0):
85 super( ConcurrentRotatingFileHandler, self ).__init__( output_file )
94 from .stderr_replacement
import stderr_replacement
95 from .stdout_replacement
import stdout_replacement
97 changeable_setup_arguments = (
109 EMPTY_KWARG = -sys.maxsize
111 if sys.version_info[0] < 3:
113 from collections
import MutableMapping
116 from collections.abc
import MutableMapping
118 if hasattr(sys,
'_getframe'):
119 currentframe =
lambda level: sys._getframe(level)
122 def currentframe(level):
123 """Return the frame object for the caller's stack frame.""" 127 return sys.exc_info()[level-1].tb_frame.f_back
132 https://docs.python.org/2.6/library/logging.html 134 How to define global function in Python? 135 https://stackoverflow.com/questions/27930038/how-to-define-global-function-in-python 137 How to print list inside python print? 138 https://stackoverflow.com/questions/45427500/how-to-print-list-inside-python-print 141 _file_context_filter =
None 142 _has_file_context_filter =
False 144 def __init__(self, logger_name, logger_level=None):
146 See the factory global function logger.getLogger(). 150 super( Debugger, self ).
__init__( logger_name, logger_level
or "DEBUG" )
170 def output_file(self):
173 return self.
_file.baseFilename
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 184 The root logger is not returned, unless it is already setup with handlers. 193 if not current.propagate:
197 current = current.parent
202 def debug_level(self):
204 Return this logger active debug level. 209 def debug_level(self, debug_level):
211 Set this current logger object active debug level. 214 if isinstance( debug_level, int ):
218 raise ValueError(
"Error: The debug_level `%s` must be an integer!" % debug_level )
221 def _debug_level(self):
223 The same as Debugger::debug_level, return this logger active debug level. 225 This only exists for consistency with the setter Debugger::_debug_level(). 230 def _debug_level(self, debug_level):
232 Set this current logger object and all other relative loggers' active debug level. 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. 237 active = self.
active or self
239 def set_level(logger):
240 logger.debug_level = debug_level
243 active.fix_children( set_level )
248 Return whether the `sys.stderr` handling is enabled or not. 253 def _stderr(self, value):
255 Block modifying the value of `self.stderr`. 257 raise ValueError(
"Error: The stderr attribute is readonly!" )
262 Return whether the `sys.stdout` handling is enabled or not. 267 def _stdout(self, value):
269 Block modifying the value of `self.stdout`. 271 raise ValueError(
"Error: The stdout attribute is readonly!" )
275 Prints the stack trace (traceback) until the current function call. 277 kwargs[
'debug_level'] = 1
278 self.
_log( DEBUG,
"traceback.format_stack():\n%s\n\n",
"".join( traceback.format_stack() ) )
282 Reset all remembered parameters values set on the subsequent calls to `setup()`. 284 Call this if you want to reset to remove all handlers and set all parameters values to 299 Configure the `basic_formatter` used by `basic()` logging function. 301 @param `**kwargs` the same formatting arguments passed to `setup()` 304 basic_arguments.update( kwargs )
307 def _formatter_arguments(self):
332 Prints a clean new line, without any formatter header. 333 @param `count` how many new lines to output 335 for index
in range(count):
336 self.
clean( debug_level,
"" )
340 Prints a clean new line, without any formatter header. 341 @param `count` how many new lines to output 343 for index
in range(count):
344 self.
clean( debug_level,
"" )
346 def __call__(self, debug_level=1, msg=EMPTY_KWARG, *args, **kwargs):
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`. 353 if type( debug_level )
is int:
355 if msg
is EMPTY_KWARG:
358 kwargs[
'debug_level'] = 1
359 self.
_log( DEBUG, debug_level, args, **kwargs )
362 kwargs[
'debug_level'] = debug_level
363 self.
_log( DEBUG, msg, args, **kwargs )
368 kwargs[
'debug_level'] = 1
370 if msg
is EMPTY_KWARG:
371 self.
_log( DEBUG, debug_level, args, **kwargs )
374 self.
_log( DEBUG, debug_level, (msg,) + args, **kwargs )
376 def _fast_clean(self, debug_level=1, msg=EMPTY_KWARG, *args, **kwargs):
379 other = self.
active or self
384 for handler
in other.handlers:
385 old_formatters[handler] = handler.formatter
391 kwargs[
'debug_level'] = debug_level
394 for handler, formatter
in old_formatters.items():
395 handler.formatter = formatter
397 def clean(self, debug_level=1, msg=EMPTY_KWARG, *args, **kwargs):
399 Prints a message without the time prefix as `[plugin_name.py] 11:13:51:0582059` 401 How to insert newline in python logging? 402 https://stackoverflow.com/questions/20111758/how-to-insert-newline-in-python-logging 405 if type( debug_level )
is int:
407 if msg
is EMPTY_KWARG:
410 other = self.
active or self
415 for handler
in other.handlers:
416 old_formatters[handler] = handler.formatter
422 kwargs[
'debug_level'] = 1
425 for handler, formatter
in old_formatters.items():
426 handler.formatter = formatter
429 other = self.
active or self
434 for handler
in other.handlers:
435 old_formatters[handler] = handler.formatter
441 kwargs[
'debug_level'] = debug_level
444 for handler, formatter
in old_formatters.items():
445 handler.formatter = formatter
451 if msg
is EMPTY_KWARG:
452 other = self.
active or self
457 for handler
in other.handlers:
458 old_formatters[handler] = handler.formatter
464 kwargs[
'debug_level'] = 1
467 for handler, formatter
in old_formatters.items():
468 handler.formatter = formatter
471 other = self.
active or self
476 for handler
in other.handlers:
477 old_formatters[handler] = handler.formatter
483 kwargs[
'debug_level'] = 1
484 self.
_log_clean( debug_level, (msg,) + args, kwargs )
486 for handler, formatter
in old_formatters.items():
487 handler.formatter = formatter
489 def _fast_basic(self, debug_level=1, msg=EMPTY_KWARG, *args, **kwargs):
492 other = self.
active or self
497 for handler
in other.handlers:
498 old_formatters[handler] = handler.formatter
504 kwargs[
'debug_level'] = debug_level
505 self.
_log( DEBUG, msg, args, **kwargs )
507 for handler, formatter
in old_formatters.items():
508 handler.formatter = formatter
510 def basic(self, debug_level=1, msg=EMPTY_KWARG, *args, **kwargs):
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 515 The basic logger format can be configured setting the standard formatter with 516 setup() and calling invert() to set the `full_formatter` as 520 if type( debug_level )
is int:
522 if msg
is EMPTY_KWARG:
525 other = self.
active or self
530 for handler
in other.handlers:
531 old_formatters[handler] = handler.formatter
537 kwargs[
'debug_level'] = 1
538 self.
_log( DEBUG, debug_level, args, **kwargs )
540 for handler, formatter
in old_formatters.items():
541 handler.formatter = formatter
544 other = self.
active or self
549 for handler
in other.handlers:
550 old_formatters[handler] = handler.formatter
556 kwargs[
'debug_level'] = debug_level
557 self.
_log( DEBUG, msg, args, **kwargs )
559 for handler, formatter
in old_formatters.items():
560 handler.formatter = formatter
566 if msg
is EMPTY_KWARG:
567 other = self.
active or self
572 for handler
in other.handlers:
573 old_formatters[handler] = handler.formatter
579 kwargs[
'debug_level'] = 1
580 self.
_log( DEBUG, debug_level, args, **kwargs )
582 for handler, formatter
in old_formatters.items():
583 handler.formatter = formatter
586 other = self.
active or self
591 for handler
in other.handlers:
592 old_formatters[handler] = handler.formatter
598 kwargs[
'debug_level'] = 1
599 self.
_log( DEBUG, debug_level, (msg,) + args, **kwargs )
601 for handler, formatter
in old_formatters.items():
602 handler.formatter = formatter
609 Clear the log file contents. 611 @param `delete` if True, the log file will also be removed/deleted and the current file 612 handler will be removed. 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 ) )
620 active._create_file( output_file, active._arguments[
'rotation'], active._arguments[
'mode'],
True, delete )
625 Inverts the default formatter between the preconfigured `basic` and `full_formatter`. 631 Register a exception hook if the logger is capable of logging them to alternate streams. 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. 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() ) )
645 stderr_replacement.lock( self )
647 stderr_replacement.unlock()
650 stdout_replacement.lock( self )
652 stdout_replacement.unlock()
655 self.
exception(
"Could not register the sys.stderr stream handler" )
660 def _disable(self, stream=False, file=False):
662 Delete all automatically setup handlers created by the automatic `setup()`. 664 is_successful =
False 684 def _configure_force(self, force_debug, active):
686 Configure the force debug level feature. 689 active._force_debug = force_debug
692 elif force_debug !=
None:
693 active._force_debug =
None 695 if active._force_debug:
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):
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. 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. 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] `. 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`. 721 Single page cheat-sheet about Python string formatting pyformat.info 722 https://github.com/ulope/pyformat.info 724 Override a method at instance level 725 https://stackoverflow.com/questions/394770/override-a-method-at-instance-level 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. 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. 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 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. 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. 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(). 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. 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. 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(). 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(). 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 783 @param `stream` (default sys.stderr), an file like object to the StreamHandler use 784 to print things when outputting results. 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. 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 )
794 def _setup(self, **kwargs):
796 Allow to pass positional arguments to `setup()`. 798 handlers = kwargs.pop(
'handlers',
False )
800 force_debug = kwargs.pop(
"force",
None )
801 _fix_children = kwargs.pop(
'_fix_children',
False )
803 if kwargs.pop(
'active',
True ):
805 logger = active
or self
813 arguments = logger._arguments
816 value = kwargs[kwarg]
818 if value != EMPTY_KWARG:
820 if value != arguments[kwarg]:
822 arguments[kwarg] = value
824 if kwarg ==
'trimname':
829 or (
not logger._stream \
830 and not logger._file ):
833 logger.fix_children(
lambda logger: logger.removeHandlers() )
835 logger.full_formatter = logger._setup_formatter( logger._arguments )
836 logger._setup_log_handlers()
838 def _setup_log_handlers(self):
845 if arguments[
'file']:
848 sys.stderr.write(
"".join( self.
_get_time_prefix( datetime.datetime.now() ) )
849 +
"Logging to the file %s\n" % output_file )
851 self.
_create_file( output_file, arguments[
'rotation'], arguments[
'mode'] )
852 self.
_disable( stream=arguments[
'delete'] )
859 self.
_stream = logging.StreamHandler( arguments[
'stream'] )
863 self.
exception(
"Could not create the stream handler" )
869 self.
_disable( file=arguments[
'delete'] )
871 def _create_file(self, output_file, rotation, mode, clear=False, delete=False):
873 mode =
'w' if clear
else mode
878 os.remove( output_file )
883 with open( output_file,
'w' )
as file:
887 rotation = rotation * 1024 * 1024
889 backup_count = abs( backup_count )
if isinstance( backup_count, int )
else 2
894 if not isinstance( mode, str ):
895 raise ValueError(
"The mode argument `%s` must be instance of string." % mode )
897 _file = logging.FileHandler( output_file, mode )
903 def _setup_fast_loggers(self):
915 def warn(self, msg, *args, **kwargs):
917 Fix second indirection created by the super().warn() method, by directly calling _log() 920 if self.isEnabledFor( WARNING ):
921 self.
_log( WARNING, msg, args, **kwargs )
925 Fix second indirection created by the super().error() method, by directly calling _log() 927 if self.isEnabledFor(ERROR):
928 self.
_log(ERROR, msg, args, exc_info=
True, **kwargs)
932 def _log(self, level, msg, args, exc_info=None, extra={}, stack_info=False, debug_level=0, **kwargs):
935 debug_level =
"(%d)" % debug_level
if debug_level
else "" 938 if any( setup_arg
in kwargs
for setup_arg
in changeable_setup_arguments ):
939 other = self.
active or self
942 for setup_arg
in changeable_setup_arguments:
943 new_arguments[setup_arg] = kwargs.pop( setup_arg, new_arguments.get( setup_arg ) )
950 for handler
in other.handlers:
951 old_formatters[handler] = handler.formatter
952 handler.formatter = new_formatter
957 super( Debugger, self )._log( level, msg, args, exc_info, extra )
960 for handler, formatter
in old_formatters.items():
961 handler.formatter = formatter
964 super( Debugger, self )._log( level, msg, args, exc_info, extra )
969 def _log(self, level, msg, args, exc_info=None, extra={}, stack_info=False, debug_level=0, **kwargs):
972 debug_level =
"(%d)" % debug_level
if debug_level
else "" 975 if any( setup_arg
in kwargs
for setup_arg
in changeable_setup_arguments ):
976 other = self.
active or self
979 for setup_arg
in changeable_setup_arguments:
980 new_arguments[setup_arg] = kwargs.pop( setup_arg, new_arguments.get( setup_arg ) )
987 for handler
in other.handlers:
988 old_formatters[handler] = handler.formatter
989 handler.formatter = new_formatter
994 super()._log( level, msg, args, exc_info, extra, stack_info )
997 for handler, formatter
in old_formatters.items():
998 handler.formatter = formatter
1001 super()._log( level, msg, args, exc_info, extra, stack_info )
1004 def _log_clean(self, msg, args, kwargs):
1005 record =
CleanLogRecord( self.level, self.name, msg, args, kwargs )
1006 self.handle( record )
1009 def _setup_formatter(cls, arguments):
1012 if arguments[
'formatter']:
1014 if isinstance( arguments[
'formatter'], logging.Formatter ):
1015 return arguments[
'formatter']
1018 raise ValueError(
"Error: The formatter %s must be an instance of logging.Formatter" % arguments[
'formatter'] )
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" )
1029 time_format = cls.
getFormat( arguments,
'time',
"%H:%M:%S",
not msecs
and tick )
1030 msecs =
":" + msecs
if msecs
and arguments[
'time']
else msecs
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 "" 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 )
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 "" 1046 return logging.Formatter(
"{}{}{}{}{}{}{}{}%(message)s".format(
1047 time, msecs, tick, extra_spacing, name, function, levels, separator ), date_format )
1050 def getFormat(arguments, setting, default, next_parameter=""):
1051 value = arguments[setting]
1053 if isinstance( value, str ):
1057 value = default + (
" " if next_parameter
else "" )
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 ]
1073 Override the super() method to correctly set up `sys.stderr/stdout` handlers. 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(). 1090 for other_handler
in self.handlers:
1100 super( Debugger, self ).
addHandler( handler )
1104 Override the super() method to correctly set up `sys.stderr/stdout` handlers. 1107 See also logging::Logger::removeHandler(). 1119 for other_handler
in self.handlers:
1134 Delete all loggers created by `getLogger()` calls. 1139 cls.manager.loggerDict.clear()
1142 cls.
exception(
"Could not delete all registered loggers!")
1152 del self.manager.loggerDict[self.name]
1155 self.
exception(
"Could not delete the logger %s!", self.name)
1163 Delete all handlers registered to the current logger. 1165 if self.
_debugme: sys.stderr.write(
"Removing all handlers from %s...\n" % self.name )
1166 self.
_disable( stream=
True, file=
True )
1168 for handler
in self.handlers:
1173 Return True if the current logger has some stream handler defined. 1176 for handler
in self.handlers:
1179 if "StreamHandler" in str( type( handler ) ):
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. 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. 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. 1199 loggers = Debugger.manager.loggerDict
1200 parent_name = self.name
1201 parent_name_length = len( parent_name )
1203 for logger_name
in loggers:
1204 logger = loggers[logger_name]
1206 if not isinstance( logger, PlaceHolder ):
1209 if logger.parent.name[:parent_name_length] == parent_name:
1210 callable_action( logger )
1215 Reliably detect Windows in Python 1216 https://stackoverflow.com/questions/1387222/reliably-detect-windows-in-python 1218 Convert "D:/User/Downloads/debug.txt" 1219 To "/cygwin/D/User/Downloads/debug.txt" 1220 To "/mnt/D/User/Downloads/debug.txt" 1222 is_absolute = os.path.isabs( file_path )
1223 platform_info = platform.platform(
True ).lower()
1226 and not file_path.startswith(
"/cygdrive/" ) \
1227 and "cygwin" in platform_info:
1232 and not file_path.startswith(
"/mnt/" ) \
1233 and "linux" in platform_info \
1234 and "microsoft" in platform_info:
1237 new_output =
"/mnt/" + new_output[0].lower() + new_output[1:]
1240 new_output = os.path.abspath( file_path )
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 )
1257 Return the main root logger `root_debugger` used by this extension of the standard 1264 representations = []
1267 loggers = [self.root]
1268 loggers_dict = Debugger.manager.loggerDict
1271 total_loggers[0] += 1
1272 current_logger =
"True " if logger == self
else "False" 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] ) ) )
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 ) )
1287 for logger_name
in loggers_dict:
1288 logger = loggers_dict[logger_name]
1290 if isinstance( logger, PlaceHolder ):
1291 place_holders.append( logger )
1294 loggers.append( logger )
1296 loggers.sort( key=
lambda item: item.name, reverse=
True )
1297 loggers.extend( place_holders )
1299 for logger
in loggers:
1302 return "\n%s" %
"\n".join( reversed( representations ) )
1313 def findCaller(self):
1319 rv =
"(unknown file)", 0,
"(unknown function)" 1320 while hasattr(f,
"f_code"):
1322 filename = os.path.normcase(co.co_filename)
1323 if filename == _srcfile:
1326 rv = (co.co_filename, f.f_lineno, co.co_name)
1332 def findCaller(self, stack_info=False):
1338 rv =
"(unknown file)", 0,
"(unknown function)",
None 1339 while hasattr(f,
"f_code"):
1341 filename = os.path.normcase(co.co_filename)
1342 if filename == _srcfile:
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':
1354 rv = (co.co_filename, f.f_lineno, co.co_name, sinfo)
1360 def makeRecord(self, name, level, fn, lno, msg, args, exc_info, func=None, extra=None):
1362 A factory method which can be overridden in subclasses to create 1363 specialized LogRecords. 1366 if extra
is not None:
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]
1375 def makeRecord(self, name, level, fn, lno, msg, args, exc_info, func=None, extra=None, sinfo=None):
1377 A factory method which can be overridden in subclasses to create 1378 specialized LogRecords. 1381 if extra
is not None:
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]
1391 This does allow to replace the standard __call__ implementation by a faster one. 1393 Python does not allow the `__call__` method to be overridden. 1394 https://stackoverflow.com/questions/34261111/patch-call-of-a-function 1397 def __call__(self, debug_level=1, msg=EMPTY_KWARG, *args, **kwargs):
1400 kwargs[
'debug_level'] = debug_level
1401 self.
_log( DEBUG, msg, args, **kwargs )
1406 Creates a LogRecord which concatenates trailing arguments instead of raising an exception. 1409 def _getMessage(self, remaining_arguments):
1411 Use a function to force exception handling not break the while loop where this is used. 1420 if isinstance( args, MutableMapping ):
1421 new_msg = self.
msg % args
1423 if new_msg == self.
msg:
1424 remaining_arguments.append( str( args ) )
1425 remaining_arguments.append( new_msg )
1428 remaining_arguments.append( new_msg )
1431 remaining_arguments.append( self.
msg % args )
1434 remaining_arguments.append( self.
msg )
1440 except Exception
as error:
1441 self.
args = args[:-1]
1444 remaining_arguments.append( str( args[-1] ) )
1446 if len( args ) - 1 > 0:
1450 remaining_arguments.append( self.
msg )
1455 Return the message for this LogRecord. 1457 Return the message for this LogRecord after merging any user-supplied 1458 arguments with the message. 1461 remaining_arguments = []
1462 self.
msg = str( self.
msg )
1465 while self.
_getMessage( remaining_arguments ):
pass 1466 return " ".join( reversed( remaining_arguments ) )
1473 class CleanLogRecord(_SmartLogRecord):
1475 def __init__(self, level, name, msg, args, kwargs):
1477 if 'extra' in kwargs:
1478 for key, value
in kwargs[
'extra'].items(): setattr( self, key, value )
1481 self.levelno = level
1483 self.levelname = getLevelName( level )
1484 self.pathname =
"No Path Name" 1485 self.filename =
"No Filename" 1486 self.module =
"Unknown module" 1488 self.debugLevel =
"" 1489 self.tickDifference = 0.0
1491 self.exc_info =
None 1492 self.exc_text =
None 1493 self.stack_info =
None 1497 self.funcName =
"No Function" 1500 self.relativeCreated = 0
1503 self.threadName =
None 1504 self.processName =
None 1508 return '<CleanLogRecord: %s, %s, %s, %s, "%s">'%(self.name, self.levelno,
1509 self.pathname, self.lineno, self.msg)
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 1516 def filter(self, record):
1517 return not "_duplicated_from_file" in record.__dict__
1521 root =
Debugger(
"root_debugger",
"WARNING" )
1522 Debugger.root = root
1525 Debugger.manager = Manager( root )
1526 Debugger.manager.setLoggerClass( Debugger )
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):
1534 Return a logger with the specified name, creating it if necessary. 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: 1540 @param `logger_name` the name of this logger accordingly with the standard logging.Logger() 1543 @param `level` an integer/string with the enabled log level from logging module 1545 @param `levels` if True, add to the `full_formatter` the log levels. 1547 @param from `file` until `**kwargs` there are the named parameters passed to the Debugger.setup() 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. 1553 @param `debugme` if True, log to the `stderr` logging self debugging messages. 1555 @seealso Debugger::setup() 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 )
1563 def _getLogger(debug_level=127, logger_name=None, **kwargs):
1565 Allow to pass positional arguments to `getLogger()`. 1567 level = kwargs.get(
"level", EMPTY_KWARG )
1568 Debugger._debugme = kwargs.get(
"debugme",
False )
1571 debug_level, logger_name = _get_debug_level( debug_level, logger_name )
1573 except Exception
as error:
1574 sys.stderr.write(
'%s\n\n' % error)
1575 debug_level, logger_name = 127,
"logger" 1577 logger = Debugger.manager.getLogger( logger_name )
1578 logger.debug_level = debug_level
1580 if level != EMPTY_KWARG:
1581 kwargs.pop(
"level" )
1582 logger.setLevel( level )
1584 if kwargs.pop(
"setup",
True ) ==
True:
1585 logger.setup( _fix_children=
True, **kwargs )
1590 def _get_debug_level(debug_level, logger_name):
1594 if isinstance( logger_name, int ):
1595 debug_level, logger_name = logger_name, debug_level
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 ) )
1602 if not isinstance( debug_level, int ):
1605 debug_level = int( debug_level )
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 ) )
1612 if isinstance( debug_level, int ):
1613 logger_name =
"logger" 1616 logger_name = debug_level
1619 return debug_level, logger_name