'Is it possible/advisable to put a logger into a single package and use it across all packages in a project?

What I want to do:
To create a Python package, and in that package, create a class that instantiates a logger. I'd create an instance of this class in the main function of my project and whenever I instantiate any package or class, I'd either import the logger class that I created or I'd pass a reference of the instance I created in the main function.

Why I want to do this:

  1. Referential integrity of the logger filename and not having to write the same lines of code everywhere to instantiate a logger.
  2. To not have the logger as a global.
  3. To be able to perform file operations before instantiating the logger file. For example, to create a log folder to store the log files before starting the logger.
  4. To be able to load program parameters from a config file (my config file manager will be a separate class in a separate package) and to be able to load logger parameters from the config file.

The code I implemented as of now (which I want to change):
The main file:

import logging
from logging.handlers import RotatingFileHandler
from operatingSystemFunctions import operatingSystemFunc
from diskOperations import fileAndFolderOperations

logFileName = os.path.join('logs', 'logs.log') 
logging.basicConfig(level=logging.INFO, format='%(levelname)s %(asctime)s - %(message)s', datefmt='%d-%b-%y %H:%M:%S')
log = logging.getLogger(__name__)
handler = RotatingFileHandler(logFileName, maxBytes=2000, backupCount=5)#TODO: Shift to config file
handler.formatter = logging.Formatter(fmt='%(levelname)s %(asctime)s - %(message)s', datefmt='%d-%b-%y %H:%M:%S') 
log.addHandler(handler)
log.setLevel(logging.INFO)

if __name__ == '__main__':
    while True:
        log.info("Running")
        #do something

Similarly, the custom packages I've imported, each have their own global instantiations of logger:
operatingSystemFunc for example, is:

import subprocess
from sys import platform #to check which OS the program is running on
from abc import ABC, abstractmethod
import logging
from logging.handlers import RotatingFileHandler

logFileName = os.path.join('logs', 'logs.log')
logging.basicConfig(level=logging.INFO, format='%(levelname)s %(asctime)s - %(message)s', datefmt='%d-%b-%y %H:%M:%S')
log = logging.getLogger(__name__)
handler = RotatingFileHandler(logFileName , maxBytes = 2000 , backupCount = 5)#TODO: Shift to config file
handler.formatter = logging.Formatter(fmt='%(levelname)s %(asctime)s - %(message)s', datefmt='%d-%b-%y %H:%M:%S') #setting this was necessary to get it to write the format to file. Without this, only the message part of the log would get written to the file
log.addHandler(handler)
log.setLevel(logging.INFO)

class AudioNotifier(ABC):#Abstract parent class
    @abstractmethod
    def __init__(self):
        #self.id = "someUniqueNotifierName" #has to be implemented in the child class
        pass

Right now the code throws an error FileNotFoundError: [Errno 2] No such file or directory: '/home/nav/prog/logs/logs.log' because I haven't yet created the logs folder.

One concern I have is that if I implement the logger in a separate package and use it by importing that package into all other packages or by passing a reference to an instance of the class to all other classes, then would it be a bad practice?

Update:
Learning from Pablo's answer, I changed the code to ensure that the RotatingFileHandler was simultaneously able to write to a file. So this is the resulting code:

import logging
from logging.handlers import RotatingFileHandler
from operatingSystemFunctions import operatingSystemFunc
from diskOperations import fileAndFolderOperations

logFileName = 'logs.log'
logging.basicConfig(level=logging.INFO, format='%(levelname)s %(asctime)s - %(message)s', datefmt='%d-%b-%y %H:%M:%S')
#log = logging.getLogger(__name__)
handler = RotatingFileHandler(logFileName, maxBytes=2000, backupCount=2)#TODO: Shift to config file
handler.formatter = logging.Formatter(fmt='%(levelname)s %(asctime)s - %(message)s', datefmt='%d-%b-%y %H:%M:%S') #setting this was necessary to get it to write the format to file. Without this, only the message part of the log would get written to the file
logging.getLogger().addHandler(handler)
logging.getLogger().setLevel(logging.INFO)

if __name__ == '__main__':
    logging.info("\n\n---------------------------------")
    logging.info("program started")
    operatingSystemCheck = operatingSystemFunc.OperatingSystemFunc()
    logging.info("Monitoring ...")

    while True:
        pass

and it's used in the packages like this:

import logging

class OperatingSystemFunc:    
    def __init__(self):
        logging.info("Some message")


Solution 1:[1]

Few considerations, the python logger is already a singleton that runs in his own thread, so only one instance of the logger is ever made. When you call it, or you try to create it in different places, what you are doing is creating it just the first time and calling the same singleton again and again. So the handlers defined at the first place are accessible everywhere without passing them nor importing them.

If you make a instance of the logger once in your main function using a name, later you will only have to use logging.getLogger("your chosen name"), so in most cases there is no necessity to create a class inside a package etc etc. You can use the handlers, that you have defined elsewhere doing that and without directly importing them.

So I would say that what you are proposing is not a good practice, because it is not necessary, maybe with a few exceptions. If you really want save writing "log = logging.getLogger('...')" which is the only thing you will save, then you can definitely write that line in one package and import the "log" instead the logging module and that will be OK. But then I advice to you not putting the logger inside a class, or if in a class, make the instance in the same file and import the instance, not the class.

Solution 2:[2]

its my logger file what i used in my projects:

import logging

class CustomFormatter(logging.Formatter):

    grey = "\x1b[38;21m"
    green = "\x1b[0;30;42m"
    yellow = "\x1b[1;30;43m"
    red = "\x1b[0:30;41m"
    bold_red = "\x1b[1;30;1m"
    reset = "\x1b[0m"

    FORMATS = {
        logging.DEBUG: f"%(asctime)s -  {grey}%(levelname)s{reset} - %(message)s",
        logging.INFO: f"%(asctime)s -  {green}%(levelname)s{reset} - %(message)s",
        logging.WARNING: f"%(asctime)s -  {yellow}%(levelname)s{reset} - %(message)s",
        logging.ERROR: f"%(asctime)s -  {red}%(levelname)s{reset} - %(message)s",
        logging.CRITICAL: f"%(asctime)s -  {bold_red}%(levelname)s{reset} - %(message)s"
    }

    def format(self, record):
        log_fmt = self.FORMATS.get(record.levelno)
        formatter = logging.Formatter(log_fmt)
        return formatter.format(record)


logger = logging.getLogger("core")
logger.setLevel(logging.DEBUG)
ch = logging.StreamHandler()
ch.setLevel(logging.DEBUG)
ch.setFormatter(CustomFormatter())
logger.addHandler(ch)

and put it in

_ init _.py

from .logger import logger as LOGGER

and made an ini file like

log.ini

[loggers]
keys=root

[handlers]
keys=logfile,logconsole

[formatters]
keys=logformatter

[logger_root]
level=INFO
handlers=logfile, logconsole

[formatter_logformatter]
format=[%(asctime)s.%(msecs)03d] %(levelname)s [%(thread)d] - %(message)s

[handler_logfile]
class=handlers.RotatingFileHandler
level=INFO
args=('logs.log','a')
formatter=logformatter

[handler_logconsole]
class=handlers.logging.StreamHandler
level=INFO
args=()
formatter=logformatter

these helped me too much, i hope it will help you

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Ziur Olpa
Solution 2 Ashkan Goleh Pour