First, I retrieve the code context of the caller:
import inspect
context = inspect.getframeinfo(frame.f_back).code_context
This gives me a list of code lines; I ignore all but the first of those lines. Then I use a regular expression to get the whitespace at the start of this line; if you are using tabs, you have to replace them with the appropriate amount of spaces first:
import re
indent = re.compile("^ *")
firstline = context[0]
firstline = firstline.replace("\t", " ")
match = indent.match(firstline)
For testing purposes, I replace the spaces with dots, so I can see what's going on, and construct my prefix:
white = "." * match.span(0)[1] # Change "." to " "!
Now I can use this to modify my message msg I sent to my logger:
do_some_logging(white + msg)
It would be nice if I could simply wrap an existing logger with a decorator that turns this logger into a whitespace-aware logger. Suppose I have, for testing purposes, a really primitive logger that simply prints messages to stdout:
def pseudo_logger(msg):
"""Pseudo-logging function."""
print(msg)
This logger has a first parameter for the logging message, and potentially some other positional parameters and keywords. I want to write a decorater for such a function:
from functools import wraps
def indented(func):
"""Turns the logger func(msg) into an indented logger."""
@wraps(func)
def wrapped(msg, *args, **kwargs):
# ... compute white ...
func(white + msg, *args, **kwargs)
return wrapped
Now I can get a new logger as, for example:
new_logger = intented(pseudo_logger)
Putting it all together gives:
from functools import wraps
import inspect
import re
def indented(func):
"""Turns the logger func(msg) into an indented logger."""
indent = re.compile("^ *")
@wraps(func)
def wrapped(msg, *args, **kwargs):
frame = inspect.currentframe()
context = inspect.getframeinfo(frame.f_back).code_context
firstline = context[0]
match = indent.match(firstline)
white = "." * match.span(0)[1] # Change "." to " "!
func(white + msg, *args, **kwargs)
return wrapped
@indented
def pseudo_logger(msg):
"""Pseudo-logging function."""
print(msg)
def main():
pseudo_logger("This is an indented message!")
if True:
pseudo_logger("Another message, but more indented!")
pseudo_logger("This "
"will ignore the indention of the second "
"or third line.")
if __name__ == "__main__":
main()
I would be hesitant to use this in production code, though. Using code inspection like this is brittle, and depending on where you call this, it might lead to unexpected effects.