Python Logging (function name, file name, line number) using a single file
The correct answer for this is to use the already provided funcName
variable
import logging
logger = logging.getLogger(__name__)
FORMAT = "[%(filename)s:%(lineno)s - %(funcName)20s() ] %(message)s"
logging.basicConfig(format=FORMAT)
logger.setLevel(logging.DEBUG)
Then anywhere you want, just add:
logger.debug('your message')
Example output from a script I'm working on right now:
[invRegex.py:150 - handleRange() ] ['[A-Z]']
[invRegex.py:155 - handleRepetition() ] [[<__main__.CharacterRangeEmitter object at 0x10ba03050>, '{', '1', '}']]
[invRegex.py:197 - handleMacro() ] ['\\d']
[invRegex.py:155 - handleRepetition() ] [[<__main__.CharacterRangeEmitter object at 0x10ba03950>, '{', '1', '}']]
[invRegex.py:210 - handleSequence() ] [[<__main__.GroupEmitter object at 0x10b9fedd0>, <__main__.GroupEmitter object at 0x10ba03ad0>]]
You have a few marginally related questions here.
I'll start with the easiest: (3). Using logging
you can aggregate all calls to a single log file or other output target: they will be in the order they occurred in the process.
Next up: (2). locals()
provides a dict of the current scope. Thus, in a method that has no other arguments, you have self
in scope, which contains a reference to the current instance. The trick being used that is stumping you is the string formatting using a dict as the RHS of the %
operator. "%(foo)s" % bar
will be replaced by whatever the value of bar["foo"]
is.
Finally, you can use some introspection tricks, similar to those used by pdb
that can log more info:
def autolog(message):
"Automatically log the current function details."
import inspect, logging
# Get the previous frame in the stack, otherwise it would
# be this function!!!
func = inspect.currentframe().f_back.f_code
# Dump the message + the name of this function to the log.
logging.debug("%s: %s in %s:%i" % (
message,
func.co_name,
func.co_filename,
func.co_firstlineno
))
This will log the message passed in, plus the (original) function name, the filename in which the definition appears, and the line in that file. Have a look at inspect - Inspect live objects for more details.
As I mentioned in my comment earlier, you can also drop into a pdb
interactive debugging prompt at any time by inserting the line import pdb; pdb.set_trace()
in, and re-running your program. This enables you to step through the code, inspecting data as you choose.
funcname
, linename
and lineno
provide information about the last function that did the logging.
If you have wrapper of logger (e.g singleton logger), then @synthesizerpatel's answer might not work for you.
To find out the other callers in the call stack you can do:
import logging
import inspect
class Singleton(type):
_instances = {}
def __call__(cls, *args, **kwargs):
if cls not in cls._instances:
cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs)
return cls._instances[cls]
class MyLogger(metaclass=Singleton):
logger = None
def __init__(self):
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s - %(threadName)s - %(message)s",
handlers=[
logging.StreamHandler()
])
self.logger = logging.getLogger(__name__ + '.logger')
@staticmethod
def __get_call_info():
stack = inspect.stack()
# stack[1] gives previous function ('info' in our case)
# stack[2] gives before previous function and so on
fn = stack[2][1]
ln = stack[2][2]
func = stack[2][3]
return fn, func, ln
def info(self, message, *args):
message = "{} - {} at line {}: {}".format(*self.__get_call_info(), message)
self.logger.info(message, *args)
I like the answer given by @synthesizerpatel but I like this format better to include the level name
FORMAT = "[%(asctime)s %(filename)s->%(funcName)s():%(lineno)s]%(levelname)s: %(message)s"
logging.basicConfig(format=FORMAT, level=logging.INFO)
Results in something like:
[2022-01-13 11:48:16,122 main.py->loop():62]INFO: looping