How to show the line number from the file invoking the logger, not the logger file itself?

Question:

I have a custom logging class, which has the following format:

log_format = "%(asctime)s.%(msecs)d %(levelname)-8s [%(processName)s] [%(threadName)s] %(filename)s:%(lineno)d --- %(message)s"

My project tree looks something like this:

.
├── exceptions.py
├── logger.py
├── settings.py
└── main.py

In main.py I import my custom Logger from logger.py. On several places I perform logging using the following syntax:

Logger.info("Transcribed audio successfully.")

However when looking at the logs, the filename and lineno params are always referring to my Logger class, not the actual function from main.py which invoked the logging:

2023-02-15 10:48:06,241.241 INFO     [MainProcess] [MainThread] logger.py:38 --- Transcribed audio successfully.

Is there a way to change this? I would like that the log entry states something like:

2023-02-15 10:48:06,241.241 INFO     [MainProcess] [MainThread] main.py:98 --- Transcribed audio successfully.

This is my logger.py file:

import logging
from logging.handlers import RotatingFileHandler


class Logger:
    log_format = "%(asctime)s.%(msecs)d %(levelname)-8s [%(processName)s] [%(threadName)s] %(filename)s:%(lineno)d --- %(message)s"

    @staticmethod
    def setup_single_logger(name, logfile, level):
        handler = RotatingFileHandler(logfile, mode='a', maxBytes=1024 * 1024, backupCount=10)
        handler.setFormatter(logging.Formatter(Logger.log_format))
        logger = logging.getLogger(name)
        logger.setLevel(level)
        logger.addHandler(handler)
        return logger

    @staticmethod
    def setup_logging():
        Logger.info_logger = Logger.setup_single_logger('INFO', '/path/to/your/logfile.log', logging.INFO)

    @staticmethod
    def info(msg, *args, **kwargs):
        Logger.info_logger.info(msg, *args, **kwargs)


Logger.setup_logging()

And an example main.py is:

from logger import Logger

Logger.info("Transcribed audio successfully.")
Asked By: waykiki

||

Answers:

The problem is that you setup and create your logger in logging.py. So when you call Logger.info from main, the actual call of info on the logger object is done inside logger.py. You just created an interface for yourself to the logger object, but the underlying call is still the one specifying the attributes of the message.

What you could do is leave only the setup in logger.py, and let main.py have its own logger object.

So log.py (changed name due to clashes) can be:

import logging
from logging.handlers import RotatingFileHandler

def basic_config():
    log_format = "%(asctime)s.%(msecs)d %(levelname)-8s [%(processName)s] [%(threadName)s] %(filename)s:%(lineno)d --- %(message)s"

    handler = RotatingFileHandler('logfile.log', mode='a', maxBytes=1024 * 1024, backupCount=10)
    handler.setFormatter(logging.Formatter(log_format))

    logging.basicConfig(handlers=(handler,), level=logging.INFO)

(I felt like the use of the class was not necessary)

And main.py:

import log
import logging

log.basic_config()
logger = logging.getLogger(__file__)

logger.info("test")

Now the log file I got from running once with your code and once with mine was:

2023-02-15 13:14:30,275.275 INFO     [MainProcess] [MainThread] log.py:23 --- test
2023-02-15 13:18:51,358.358 INFO     [MainProcess] [MainThread] main.py:7 --- test
Answered By: Tomerikoo