Logging in Module - Python - python

In MAIN_SETUP.py program I am importing otherMod2
MAIN_SETUP.py
import logging
import otherMod2
# ----------------------------------------------------------------------
def main():
"""
The main entry point of the application
"""
logger = logging.getLogger("exampleApp")
logger.setLevel(logging.INFO)
# create the logging file handler
fh = logging.FileHandler("new_snake.log")
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
fh.setFormatter(formatter)
# add handler to logger object
logger.addHandler(fh)
logger.info("Program started")
result = otherMod2.add(7, 8)
logger.info("Done!")
if __name__ == "__main__":
main()
otherMod2.py
import logging
module_logger = logging.getLogger("exampleApp.otherMod2")
# ----------------------------------------------------------------------
def add(x, y):
""""""
logger = logging.getLogger("exampleApp.otherMod2.add")
logger.info("added %s and %s to get %s" % (x, y, x + y))
return x + y
If I run the program (MAIN_SETUP.py), new_snake.log file is created, following data is written in the file
2018-12-03 16:21:29,772 - exampleApp - INFO - Program started
2018-12-03 16:21:29,772 - exampleApp.otherMod2.add - INFO - added 7 and 8 to get 15
2018-12-03 16:21:29,772 - exampleApp - INFO - Done!
Question 1 :
In otherMod2.py , we do have below logger, it's just defined and not used . Can we delete it? Will it have any impact if we delete it.
module_logger = logging.getLogger("exampleApp.otherMod2")
Question 2:
There is no handler defined for below logger in otherMod2.py but still its writing in new_snake.log file, how this is possible
logger = logging.getLogger("exampleApp.otherMod2.add")
logger.info("added %s and %s to get %s" % (x, y, x + y))

To answer both questions:
Question 1
You can delete the module-level logger exampleApp.otherMod2, however, you might want to keep it so that other code within the module can use it.
Creating a module-level logger is the recommended approach - typically by calling logger.getLogger(__name__). That sets up a logger using the same hierarchy as the module's package structure.
Question 2
Python loggers are constructed within a hierarchy which is determined by the dots in the logger's name. By default, events logged lower down the hierarchy are also passed to loggers higher up the heirarchy.
So although your exampleApp.otherMod2.add logger has no handler, the event is passed up to the top level exampleApp logger which outputs it to the log file.
This behavior is controlled by the propagate attribute. You can set this to False so that higher level loggers won't receive the events.
logger = logging.getLogger("exampleApp.otherMod2.add")
logger.propagate = False
logger.info("added %s and %s to get %s" % (x, y, x + y))
In that case you would need to attach a handler to exampleApp.otherMod2.add if you wanted it to output anything.

Related

Why I can't use different log levels in Python?

I want to use different log levels. For example I need to log actions as INFO if everyting is OK, and WARNING when the required action is NOT proceeded OK (or ERROR, FATAl etc. it doesn't matter right now). How can I reach it? Basically I need just 2 levels, but unfortunately if I am trying to use 2 levels then my log file created as empty. Below is my code but it doesn't work and log file created empty.
class Logger:
#staticmethod
def info_logger(logLevel=log.INFO):
logger_name = inspect.stack()[1][3]
logger = log.getLogger(logger_name)
logger.setLevel(logLevel)
fh = log.FileHandler(log_file)
formatter = log.Formatter("%(asctime)s - %(levelname)s - %(message)s", datefmt='%m/%d/%Y %I: %M: %S %p')
fh.setFormatter(formatter)
logger.addHandler(fh)
return logger
#staticmethod
def warning_logger(logLevel=log.WARNING):
logger_name = inspect.stack()[1][3]
logger = log.getLogger(logger_name)
logger.setLevel(logLevel)
fh = log.FileHandler(error_log_file)
formatter = log.Formatter("%(asctime)s - %(levelname)s - %(message)s", datefmt='%m/%d/%Y %I: %M: %S %p')
fh.setFormatter(formatter)
logger.addHandler(fh)
return logger

abbreviate or shorten the logger name to initials with python logging

I'm trying to achieve a behavior available in log4j, which is described in this SO Q&A
Instead of the logger producing the following output (notice the full module name)
DEBUG aproject.bpackage.cclass:filename.py:79 log message 1
I'd like to only have the initials of the logger name:
DEBUG a.b.c:filename.py:79 log message 1
Trying to figure out the correct format string with the manual, but no mention of initials for the logger name
Note that loggers are initialized with the module name as convention:
logger = logging.getLogger(__name__)
There's no out-of-the-box functionality for this, but you can achieve the desired result using something like this:
import logging
class CustomFormatter(logging.Formatter):
def format(self, record):
saved_name = record.name # save and restore for other formatters if desired
parts = saved_name.split('.')
# import pdb; pdb.set_trace()
record.name = '.'.join(p[0] for p in parts)
result = super().format(record)
record.name = saved_name
return result
h = logging.StreamHandler()
f = CustomFormatter('%(name)-6s %(message)s')
h.setFormatter(f)
root = logging.getLogger()
root.addHandler(h)
root.setLevel(logging.DEBUG)
logging.getLogger('alpha.beta.gamma').debug('foo')
logging.getLogger('delta.epsilon.zeta').debug('bar')
logging.getLogger('eta.theta').debug('baz')
When run, the above script outputs
a.b.g foo
d.e.z bar
e.t baz

Logging printing twice only in some parts of the code

I have been using a custom formatter for logging to the terminal in my code. Lately I have been changing stuff in the code an I can't find why now in some parts of the code the log is printed twice.
This is the code for the custom formatter:
import logging
class MyFormatter(logging.Formatter):
debug_format = "[%(levelname)s] (%(module)s::%(funcName)s::%(lineno)d) %(message)s"
normal_format = "[%(levelname)s] %(message)s"
blue = "\x1b[36;21m"
grey = "\x1b[38;21m"
yellow = "\x1b[33;21m"
red = "\x1b[31;21m"
bold_red = "\x1b[31;1m"
reset = "\x1b[0m"
def __init__(self):
super().__init__(fmt="%(levelno)d: %(msg)s", datefmt=None, style="%")
def format(self, record):
# Save the original format configured by the user
# when the logger formatter was instantiated
format_orig = self._style._fmt
# Replace the original format with one customized by logging level
if record.levelno == logging.DEBUG:
self._style._fmt = MyFormatter.debug_format
format = MyFormatter.debug_format
else:
self._style._fmt = MyFormatter.normal_format
format = MyFormatter.normal_format
self.FORMATS = {
logging.DEBUG: MyFormatter.grey + format + MyFormatter.reset,
logging.INFO: MyFormatter.blue + format + MyFormatter.reset,
logging.WARNING: MyFormatter.yellow + format + MyFormatter.reset,
logging.ERROR: MyFormatter.red + format + MyFormatter.reset,
logging.CRITICAL: MyFormatter.bold_red + format + MyFormatter.reset,
}
log_fmt = self.FORMATS.get(record.levelno)
# Restore the original format configured by the user
self._style._fmt = format_orig
formatter = logging.Formatter(log_fmt)
return formatter.format(record)
This is how I create my logger:
from src.logs import set_logger, logging
logger = set_logger(__name__, logging.DEBUG)
This is set_logger function code:
import logging
from .custom_formatter import MyFormatter
def set_logger(module_name: str, level=logging.DEBUG) -> logging.Logger:
logger = logging.getLogger(module_name)
logger.setLevel(level)
stream_handler = logging.StreamHandler()
formatter = MyFormatter()
stream_handler.setFormatter(formatter)
logger.addHandler(stream_handler)
return logger
Now when I call this logger from main for example or at the top of a module which is imported, there is no problem, and it logs perfectly only once. However when calling the logger from inside a function in the same module it is printed twice.
I have notice by debugging that what is doing is going to the end of the format method in MyFormatter class and then it returns again to this format method, I have no clue what is going on here. Do you have any ideas on what could be happening?
PD: Also if I also call a print when the logger prints twice I only get one print, so that code runs only once for sure.
Thanks for your time!
Andrés
In set_logger(), it calls addHandler() but the logger (or an ancestor logger) will already have a handler, which you're not removing, so you'll have multiple handlers.
Have a look at the docs for Logger.propagate: https://docs.python.org/3/library/logging.html#logging.Logger.propagate

Color logging using logging module in Python

Let's simplify this. My goal is to make color output in terminal using logging module in Python. I want info has a green prefix, warnings have a yellow prefix and errors have a red prefix. To make it simple let's use *** as the prefix i.e.
*** log text
*** another message with another prefix color
What I have done so far
# declaration of function (global scope)
log = None
warn = None
error = None
def build_log_funcs():
# why I initialize it inside the function ?
# because script doesnt have to know about logging method
# the function just provide log functions
logger = logging.getLogger(__name__)
logger.setLevel(logging.INFO)
sh = logging.StreamHandler()
# LOG_FORMAT just global variable with pattern including %(levelmarks)s
# it will be replaced with ** with proper color
formatter = logging.Formatter(LOG_FORMAT)
sh.setFormatter(formatter)
logger.addHandler(sh)
def make_log_func(func, color, is_exit = False):
color_string = "\x1b[{};1m***\x1b[0m".format(color)
def newfunc(*args, **kwargs):
func(*args, extra={'levelmarks':color_string}, **kwargs)
if is_exit:
sys.exit(-1)
return newfunc
# 32, 33, 31 are color codes
log = make_log_func(logger.info, 32)
warn = make_log_func(logger.warning, 33)
error = make_log_func(logger.error, 31, is_exit = True)
return log, warn, error
And use it as
log, warn, error = build_log_funcs()
It works but what I don't like: (from small to big problems)
I hide the capabilities of logging module. For example enabling/disabling debug messages
I should use global declaration of functions before their initialization because I can't call a function before its declaration.
It's too difficult to read and maintain the code. I believe that everything should be as simple as possible.
Why don't I just make simple log, warn, simple function? I don't know. logging is the very comprehensive module so may be I will need its features in the future.
My question is how would you solve this problem? May be there is a simple obvious way which I don't know.
Thanks Dominic Kexel for this link. I saw this but did not pay attention to the answer.
The following code is more or less suitable for me
def setup_logger(logger):
logger.setLevel(logging.DEBUG)
sh = logging.StreamHandler()
formatter = logging.Formatter(LOG_FORMAT)
sh.setFormatter(formatter)
def decorate_emit(fn):
# add methods we need to the class
def new(*args):
levelno = args[0].levelno
if(levelno >= logging.CRITICAL):
color = '\x1b[31;1m'
elif(levelno >= logging.ERROR):
color = '\x1b[31;1m'
elif(levelno >= logging.WARNING):
color = '\x1b[33;1m'
elif(levelno >= logging.INFO):
color = '\x1b[32;1m'
elif(levelno >= logging.DEBUG):
color = '\x1b[35;1m'
else:
color = '\x1b[0m'
# add colored *** in the beginning of the message
args[0].msg = "{0}***\x1b[0m {1}".format(color, args[0].msg)
# new feature i like: bolder each args of message
args[0].args = tuple('\x1b[1m' + arg + '\x1b[0m' for arg in args[0].args)
return fn(*args)
return new
sh.emit = decorate_emit(sh.emit)
logger.addHandler(sh)
There is one flaw in this: I can't control the position of *** in the pattern but as I said it's suitable.

Create custom logging function for pythons logging module

I have been trying for some time to figure out how to create a custom function for the python logging module. My goal is, with the usual function such as logging.debug(...) a log message over several channels, such as Telegram or MQTT, to publishing. So my idea is to add extra arguments to the normal log methode. For example logging.debug ("a log", telegram=True, mqtt=False) and maybe other arguments. All I find is the inheritance of the class logging.StreamingHandler and then using the method emit, but this only passes the argument record. So how can I implement my problem in a meaningful way? Do I have a thinking error or the wrong approach?
I solved my problem by creating a interface for the logging module.
A small view on my code:
# ulogging.py
import logging
import telegram
def uloggingConfig(*args, **kwargs):
logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG)
# general logging config section
fmt = kwargs.pop("fmt", "%(asctime)s %(levelname)s %(message)s")
datefmt = kwargs.pop("datefmt", "%m.%d.%Y %I:%M:%S %p")
streamHandler = logging.StreamHandler()
streamHandler.setLevel(logging.DEBUG)
formater = logging.Formatter(fmt=fmt, datefmt=datefmt)
streamHandler.setFormatter(formater)
logger.addHandler(streamHandler)
# telegram config section
telegramBot = kwargs.pop("teleBot", None)
telegramChatID = kwargs.pop("teleChatID", None)
telegramLevel = kwargs.pop("teleLevel", logging.INFO)
telegramFmt = kwargs.pop("telefmt", "%(message)s")
telegramDatefmt = kwargs.pop("teledatefmt", None)
if telegramBot is not None and telegramChatID is not None:
telegramStream = TelegramStream(telegramBot, telegramChatID)
formater = logging.Formatter(fmt=telegramFmt, datefmt=telegramDatefmt)
telegramStream.setFormatter(formater)
telegramStream.setLevel(telegramLevel)
logger.addHandler(telegramStream)
elif (telegramBot is not None and telegramChatID is None) or (telegramBot is None and telegramChatID is not None):
raise KeyError("teleBot and teleChatID have to be both given")
if kwargs:
keys = ', '.join(kwargs.keys())
raise ValueError('Unrecognised argument(s): %s' % keys)
return logger
def getLogger():
return logging.getLogger(__name__)
class TelegramStream(logging.StreamHandler):
def __init__(self, telegramBot, telegramChatID):
logging.StreamHandler.__init__(self)
self._bot = telegramBot
self._chatID = telegramChatID
def emit(self, record):
if record.levelname == "DEBUG":
self._bot.send_message(self._chatID, record.levelname + ": " + record.msg)
else:
self._bot.send_message(self._chatID, record.msg)
With the uloggingConfig() method I can now pass all settings for the different handlers (at the moment only for telegram, further handlers should follow). The uloggingConfig() method then takes over the configuration and returns a logger with which log messages can be created as usual.
A simple example:
logger = ulogging.uloggingConfig(fmt="format, datefmt="dateformat, teleBot=telegramBot, teleChatID=telegramChatID, teleLevel=logging.DEBUG)
logger.debug("A log message")

Categories