Filename and Line Number of Python Script

Debugging: Get filename and line number from which a function is called?

The function inspect.stack() returns a list of frame records, starting with the caller and moving out, which you can use to get the information you want:

from inspect import getframeinfo, stack

def debuginfo(message):
caller = getframeinfo(stack()[1][0])
print("%s:%d - %s" % (caller.filename, caller.lineno, message)) # python3 syntax print

def grr(arg):
debuginfo(arg) # <-- stack()[1][0] for this line

grr("aargh") # <-- stack()[2][0] for this line

Output:

example.py:8 - aargh

How to log source file name and line number in Python

Sure, check formatters in logging docs. Specifically the lineno and pathname variables.

%(pathname)s Full pathname of the source file where the logging call was issued(if available).

%(filename)s Filename portion of pathname.

%(module)s Module (name portion of filename).

%(funcName)s Name of function containing the logging call.

%(lineno)d Source line number where the logging call was issued (if available).

Looks something like this:

formatter = logging.Formatter('[%(asctime)s] p%(process)s {%(pathname)s:%(lineno)d} %(levelname)s - %(message)s','%m-%d %H:%M:%S')

C++/embedded Python: Can I retrieve Python file name and line number when a C++ function is called from Python

You will need PyTraceBack_Here.

You can take a look at a traceback object's implementation here

Here is an example printig the traceback created by PyTraceBack_Here

#include <Python.h>

PyObject * mymodule_meth_test(PyObject * self) {
PyTraceBack_Here(PyEval_GetFrame());
PyObject * exc;
PyObject * val;
PyObject * tb;
PyErr_Fetch(&exc, &val, &tb);
PyTraceBack_Print(tb, PySys_GetObject("stderr"));
Py_RETURN_NONE;
}

PyMethodDef module_methods[] = {
{"test", (PyCFunction)mymodule_meth_test, METH_NOARGS, NULL},
{},
};

PyModuleDef module_def = {PyModuleDef_HEAD_INIT, "mymodule", NULL, -1, module_methods};

extern "C" PyObject * PyInit_mymodule() {
PyObject * module = PyModule_Create(&module_def);
return module;
}

From the tb object you should be able to extract the filename and line number.
It is an ordinary PyObject you can pass it to a python script or inspect it.

Here is how to extract the values without taking care of the refcounts:

    int line = PyLong_AsLong(PyObject_GetAttrString(PyObject_GetAttrString(tb, "tb_frame"), "f_lineno"));
const char * filename = PyUnicode_AsUTF8(PyObject_GetAttrString(PyObject_GetAttrString(PyObject_GetAttrString(tb, "tb_frame"), "f_code"), "co_filename"));

python logging using filename and line number

Yes it is possible, althrough I'm quite sure the implementation I'm posting here is not 100% safe and sure there is a better / more elegant implementation, take this as a hint.

To get the filename and line# of the caller you can use the inspect module, to add those custom infos to your logs you can add a custom Filter:

log.py

import logging
from logging.handlers import RotatingFileHandler
from inspect import getframeinfo, stack

class CallerFilter(logging.Filter):
""" This class adds some context to the log record instance """
file = ''
line_n = ''

def filter(self, record):
record.file = self.file
record.line_n = self.line_n
return True

def caller_reader(f):
"""This wrapper updates the context with the callor infos"""
def wrapper(self, *args):
caller = getframeinfo(stack()[1][0])
self._filter.file = caller.filename
self._filter.line_n = caller.lineno
return f(self, *args)
return wrapper

class Log:

def __init__(self, path):
self.LOGGER = logging.getLogger('Open-Capture')
if self.LOGGER.hasHandlers():
self.LOGGER.handlers.clear() # Clear the handlers to avoid double logs
logFile = RotatingFileHandler(path, mode='a', maxBytes=5 * 1024 * 1024,
backupCount=2, encoding=None, delay=0)
formatter = logging.Formatter('[%(threadName)-14s] [%(file)s:%(line_n)-15s] %(asctime)s %(levelname)s %(message)s', datefmt='%d-%m-%Y %H:%M:%S')
logFile.setFormatter(formatter)
self.LOGGER.addHandler(logFile)
# Here we add the Filter, think of it as a context
self._filter = CallerFilter()
self.LOGGER.addFilter(self._filter)
self.LOGGER.setLevel(logging.DEBUG)

@caller_reader
def info(self, msg):
self.LOGGER.info(msg)

@caller_reader
def error(self, msg):
self.LOGGER.error(msg)

script_file.py

from log import Log

log = Log('l.log')
log.info('LOG MESSAGE')
log.error('LOG MESSAGE 2 ')
log.info('LOG MESSAGE 2 ')

output:

[MainThread    ] [script_file.py:4              ] 17-01-2020 16:34:36 INFO LOG MESSAGE
[MainThread ] [script_file.py:5 ] 17-01-2020 16:34:36 ERROR LOG MESSAGE 2
[MainThread ] [script_file.py:6 ] 17-01-2020 16:34:36 INFO LOG MESSAGE 2

How to determine file, function and line number?

There is a module named inspect which provides these information.

Example usage:

import inspect

def PrintFrame():
callerframerecord = inspect.stack()[1] # 0 represents this line
# 1 represents line at caller
frame = callerframerecord[0]
info = inspect.getframeinfo(frame)
print(info.filename) # __FILE__ -> Test.py
print(info.function) # __FUNCTION__ -> Main
print(info.lineno) # __LINE__ -> 13

def Main():
PrintFrame() # for this line

Main()

However, please remember that there is an easier way to obtain the name of the currently executing file:

print(__file__)


Related Topics



Leave a reply



Submit