Using the python logging package, how do I insert additional formatting into multiple log handlers that have their own formatting?

I have one registrar with several handlers that have their own formatting elements. Now I want to add padding, and the padding level is controlled at runtime. I want to receive messages from all handlers to get this indent. I tried to create it as a filter, but found that it looks like I cannot change the contents of the message. Then I tried this as formatting, but I can only have one handler. How to add such an indent without explicitly changing the formatting of each handler?
I have to mention that one of the formats that I have is a class that adds color to the output. This is not a simple format string.


In addition, I am using a configuration file. Ideally, I would like to be able to drive it mostly from there. However, I need to change the indentation formatting state (for example, set the indentation level), but I don’t know how to get to this particular formatting, as there is no logger.getFormatter("by_name") method.
To clarify, I need to access a specific instance of formatting, essentially, to adjust the format on the fly. An instance was created using logging.config from a file. I did not find any access methods that would allow me to get a specific formatter with his name.

+4
source share
3 answers

Here is another hacked but simple one. My messages for all handlers always begin with a message-level line. Just change these darn lines every time you indent:

 # (make a LEVELS dict out of all the logging levels first) def indent(self, step = 1): "Change the current indent level by the step (use negative to decrease)" self._indent_level += step if self._indent_level < 0: self._indent_level = 0 self._indent_str = self._indent_str_base * self._indent_level for lvl in LEVELS: level_name = self._indent_str + LEVELS[lvl] logging.addLevelName(lvl, level_name) 

(see my other answer for the material that surrounds the indent function)
Now the indenter can be an independent class without going into the details of the logging process. As long as the message contains a level line, the indent will be there, even if some things go before it. Not perfect at all, but might work for me.
Does anyone have any more ideas that work for any msg format?

0
source
 #!/usr/bin/env python import logging from random import randint log = logging.getLogger("mylog") log.setLevel(logging.DEBUG) class MyFormatter(logging.Formatter): def __init__(self, fmt): logging.Formatter.__init__(self, fmt) def format(self, record): indent = " " * randint(0, 10) # To show that it works msg = logging.Formatter.format(self, record) return "\n".join([indent + x for x in msg.split("\n")]) # Log to file filehandler = logging.FileHandler("indent.txt", "w") filehandler.setLevel(logging.DEBUG) filehandler.setFormatter(MyFormatter("%(levelname)-10s %(message)s")) log.addHandler(filehandler) # Log to stdout too streamhandler = logging.StreamHandler() streamhandler.setLevel(logging.INFO) streamhandler.setFormatter(MyFormatter("%(message)s")) log.addHandler(streamhandler) # Test it log.debug("Can you show me the dog-kennels, please") log.info("They could grip it by the husk") log.warning("That no ordinary rabbit!") log.error("Nobody expects the spanish inquisition") try: crunchy_frog() except: log.exception("It a real frog") 

result:

  They could grip it by the husk
     That no ordinary rabbit!
           Nobody expects the spanish inquisition
          It a real frog
          Traceback (most recent call last):
            File "./logtest2.py", line 36, in 
              crunchy_frog ()
          NameError: name 'crunchy_frog' is not defined

I'm not sure I understand your second question.

+4
source

Well, here is one way that makes me ALMOST what I need. A subclass of LogRecord to overwrite getMessage to insert an indent register and a subclass for makeRecord with it:

 import logging import logging.config ################################################################################ class IndentingLogger(logging.Logger): """A Logger subclass to add indent on top of any logger output """ ############################################################################ def __init__(self, name = 'root', logging_level = logging.NOTSET): "Constructor to keep indent persistent" logging.Logger.__init__(self, name, logging_level) self.indenter = IndentedRecord("", logging.NOTSET, "", 0, None, None, None, None, None) ############################################################################ def makeRecord(self, name, level, fn, lno, msg, args, exc_info, func=None, extra=None): return self.indenter.set_record(name, level, fn, lno, msg, args, exc_info, func, extra) ################################################################################ class IndentedRecord(logging.LogRecord): """A LogRecord subclass to add indent on top of any logger output """ ######## Class data ######### DEFAULT_INDENT_STR = ' ' ############################################################################ def __init__(self, name, level, fn, lno, msg, args, exc_info, func=None, extra=None): "Constructor" logging.LogRecord.__init__(self, name, level, fn, lno, msg, args, exc_info, func) self._indent_level = 0 self._indent_str_base = IndentedRecord.DEFAULT_INDENT_STR self._indent_str = "" # cache it ############################################################################ def set_record(self, name, level, fn, lno, msg, args, exc_info, func=None, extra=None): "Constructs the base record" logging.LogRecord.__init__(self, name, level, fn, lno, msg, args, exc_info, func) return self ################################################################################ def getMessage(self): "Adds indent on top of the normal getMessage result" # Call base class to get the formatted message message = logging.LogRecord.getMessage(self) # Now insert the indent return self._indent_str + message ################################################################################ def indent(self, step = 1): "Change the current indent level by the step (use negative to decrease)" self._indent_level += step if self._indent_level < 0: self._indent_level = 0 self._indent_str = self._indent_str_base * self._indent_level ################################################################################ def set_indent_str(self, chars): "Change the current indent string" if not isinstance(chars, str): raise ValueError("Argument must be a string. Got %s" % chars) self._indent_str_base = chars logging.config.fileConfig("reporter.conf") logging.setLoggerClass(IndentingLogger) logger = logging.getLogger('root') # will be wrong logger, if without argument logger.debug("debug message") logger.info("info message") logger.indenter.indent(+1) logger.warn("Indented? warn message") logger.indenter.set_indent_str("***") logger.error("Indented? error message: %s", "Oops, I did it again!") logger.indenter.indent(+1) logger.error("Indented? error message: %s", "Oops, I did it again!") logger.indenter.indent(-1) logger.critical("No indent; critical message") 

Result (color in reality):

 Debug: debug message Info: info message Warning: Indented? warn message Error: Indented? error message: Oops, I did it again! Error: ******Indented? error message: Oops, I did it again! Internal Error: ***No indent; critical message 

Somehow the log level line still sneaks forward, so this is not quite what I want. Also, this is inconvenient - too much for such a simple function :(
Best ideas?

0
source

Source: https://habr.com/ru/post/1336241/


All Articles