24

My idea is to make a context logging scheme as showed on the example below:

[ DEBUG] Parsing dialogs files [ DEBUG] ... [DialogGroup_001] [ DEBUG] ...... Indexing dialog xml file [c:\001_dlg.xml] [ DEBUG] ......... dialog [LobbyA] [ DEBUG] ............ speech nodes [3] [ DEBUG] ............... [LobbyA_01] [ DEBUG] ............... [LobbyA_02] [ DEBUG] ............... [LobbyA_03] [ DEBUG] ............ sms nodes [0] [ DEBUG] ......... dialog [LobbyB] [ DEBUG] ............ speech nodes [3] [ DEBUG] ............... [LobbyB_01] [ DEBUG] ............... [LobbyB_02] [ DEBUG] ............... [LobbyB_03] [ DEBUG] ............ sms nodes [0] [ DEBUG] ... [DialogGroup_002] [ DEBUG] ...... Indexing dialog xml file [c:\002_dlg.xml] [ DEBUG] ......... dialog [HighGroundsA] [ DEBUG] ............ speech nodes [3] [ DEBUG] ............... [HighGroundsA_01] [ DEBUG] ............... [HighGroundsA_02] [ DEBUG] ............... [HighGroundsA_03] [ DEBUG] ............ sms nodes [0] 

At this point, I'm using Python's logging module with custom, hand-written prefixes when logging, for example:

(...) log.debug('') log.debug('Parsing dialogs files') for dlg in defDlgList: log.debug('... [{0}]'.format(dlg)) (...) 

It's working quite ok, but there are some subtle problems, for example: when logging from inside functions - they may be called from various scopes and prefix length may vary for each call.

I'm looking for an elegant and invisible way to establish a length of a '...' prefix automatically for each log. I'd rather avoid passing prefix length as a parameter to each function or setting the length using explicit calls, for example:

(...) logWrapper.debug('') logWrapper.debug('Parsing dialogs files') for dlg in defDlgList: logWrapper.nextLogLevelBegin() logWrapper.debug('[{0}]'.format(dlg)) logWrapper.nextLogLevelEnd() (...) 

Is there a way to get the current indentation level from Python's parser or construct a scope sensitive wrapper class for logging?

4 Answers 4

21

Perhaps you can use inspect.getouterframes to find the indentation level:

import inspect import logging logger=logging.getLogger(__name__) def debug(msg): frame,filename,line_number,function_name,lines,index=inspect.getouterframes( inspect.currentframe())[1] line=lines[0] indentation_level=line.find(line.lstrip()) logger.debug('{i} [{m}]'.format( i='.'*indentation_level, m=msg )) def foo(): debug('Hi Mom') for i in range(1): debug("Now we're cookin") if __name__=='__main__': logging.basicConfig(level=logging.DEBUG) foo() 

yields

DEBUG:__main__:.... [Hi Mom] DEBUG:__main__:........ [Now we're cookin] 
Sign up to request clarification or add additional context in comments.

2 Comments

This is what I needed to know! :D:D:D:D Thank You very much!
Combine this with textwrap.indent for great success
12

Combining the previous answers with How do I add custom field to Python log format string? can achieve the same result without needing to provide a custom debug() method (since the same would need to be done for each level info(), error(), etc).

import logging import traceback class CustomAdapter(logging.LoggerAdapter): @staticmethod def indent(): indentation_level = len(traceback.extract_stack()) return indentation_level-4 # Remove logging infrastructure frames def process(self, msg, kwargs): return '{i}{m}'.format(i='\t'*self.indent(), m=msg), kwargs logger = CustomAdapter(logging.getLogger(__name__), {}) logger.debug('A debug message') logger.error('An error message') logger.info('An info message') 

3 Comments

This snipped does not work for me (using Python 2.7). In addition, "logger" is not defined. Can you adjust/add the corresponding lines?
@Mr.EpicFail I updated to fix the missing logger argument. As for python 2.7, I'm not sure what's going on there.
I've found that subclassing LoggerAdapter can lead to problems if you're also subclassing Logger and calling setLoggerClass. I found subclassing Formatter to be a better solution in this case, I found an example of this here: code.activestate.com/recipes/…
11

Searching through the docs, I don't really see a way to get current indentation level. The best you can do, is get the current function nesting level, like this:

len(traceback.extract_stack()); 

Example:

import traceback; def test(): print len(traceback.extract_stack()); print len(traceback.extract_stack()); # prints 1 test(); # prints 2 

1 Comment

This is useful, thank You! I'll combine this with solutions from choosen answer to make nested functions log correct.
1

I created a pypi package for this purpose.
It supports

  • Adding manual indentation to each log via extra parameter. auto
  • Indentation based on parent-child logger hieararchy.
  • Decorator based indentation.

Install it via pip: pip install indented_logger

Usage:

from indented_logger import setup_logging import logging # Configure the logger setup_logging(level=logging.INFO, include_func=True) # Get the logger logger = logging.getLogger(__name__) # Basic logging logger.info('Starting process A') # Manual indentation logger.info('Details of process A', extra={'lvl': 1}) logger.info('Deeper Details of process A', extra={'lvl': 2}) logger.info('Process complete') 

Output:

2024-08-15 12:34:56 - INFO - Starting process A {main} 2024-08-15 12:34:56 - INFO - Details of process A {main} 2024-08-15 12:34:56 - INFO - Deeper Details of process A {main} 2024-08-15 12:34:56 - INFO - Process complete {main} 

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.