I try to use two different loggers to handle different log levels. For example, I want info message store in a file and don't log error message. Error messages of some special functions will email to the user.
I write a simple program to test the logging module.
Code:
import logging
def_logger = logging.getLogger("debuglogger")
def_logger.setLevel(logging.DEBUG)
maillogger = logging.getLogger("mail")
maillogger.setLevel(logging.ERROR)
mailhandler = logging.StreamHandler()
mailhandler.setLevel(logging.ERROR)
mailhandler.setFormatter(logging.Formatter('Error: %(asctime)s - %(name)s - %(levelname)s - %(message)s'))
maillogger.addHandler(mailhandler)
print(def_logger.getEffectiveLevel())
print(maillogger.getEffectiveLevel())
def_logger.info("info 1")
maillogger.info("info 2")
def_logger.error("error 1")
maillogger.error("error 2")
Output:
Output result
I can see the level of them is correct, but both of them act like the level is ERROR.
How can I correctly configure them?
Answer: Base on blues advice, I added a handler and it solved my problem.
Here is the modified code:
import logging
def_logger = logging.getLogger("debuglogger")
def_logger.setLevel(logging.DEBUG)
def_logger.addHandler(logging.StreamHandler()) #added a handler here
maillogger = logging.getLogger("mail")
maillogger.setLevel(logging.ERROR)
mailhandler = logging.StreamHandler()
mailhandler.setLevel(logging.ERROR)
mailhandler.setFormatter(logging.Formatter('Error: %(asctime)s - %(name)s - %(levelname)s - %(message)s'))
maillogger.addHandler(mailhandler)
print(def_logger.getEffectiveLevel())
print(maillogger.getEffectiveLevel())
def_logger.info("info 1")
maillogger.info("info 2")
def_logger.error("error 1")
maillogger.error("error 2")
Neither the def_logger nor any of its parents have a handler attached to it. So what happens is that the logging module falls back to logging.lastResort which by default is a StreamHandler with level Warning. That is the reason why the info message doesn't appear, while the error does. So to solve your problem attach a handler to the def_logger.
Note: In your scenario the only parent both loggers have is the default root handler.
You could add a filter for each logger or handler to process only records of interest
import logging
def filter_info(record):
return True if record.levelno == logging.INFO else False
def filter_error(record):
return True if record.levelno >= logging.ERROR else False
# define debug logger
def_logger = logging.getLogger("debuglogger")
def_logger.setLevel(logging.DEBUG)
def_logger.addFilter(filter_info) # add filter directly to this logger since you didn't define any handler
# define handler for mail logger
mail_handler = logging.StreamHandler()
mail_handler.setLevel(logging.ERROR)
mail_handler.setFormatter(logging.Formatter('Error: %(asctime)s - %(name)s - %(levelname)s - %(message)s'))
mail_handler.addFilter(filter_error) # add filter to handler
mail_logger = logging.getLogger("mail")
mail_logger.setLevel(logging.ERROR)
mail_logger.addHandler(mail_handler)
# test
def_logger.info("info 1")
mail_logger.info("info 2")
def_logger.error("error 1")
mail_logger.error("error 2")
If filter return True, the log record is processed. Otherwise, it is skipped.
Note:
Filter attached to a logger won't be called for log record that is generated by descendant loggers. For example, if you add a filter to logger A, it won't be called for records that are generated by logger A.B nor A.B.C.
Filter attached to a handler is consulted before an event is emitted by that handler.
This means that you just need one logger and add two handlers with different filters attached.
import logging
def filter_info(record):
return True if record.levelno == logging.INFO else False
def filter_error(record):
return True if record.levelno >= logging.ERROR else False
# define your logger
logger = logging.getLogger("myapp")
logger.setLevel(logging.DEBUG)
# define handler for file
file_handler = logging.FileHandler('path_to_log.txt')
file_handler.level = logging.INFO
file_handler.addFilter(filter_info) # add filter to handler
# define handler for mail
mail_handler = logging.StreamHandler()
mail_handler.setLevel(logging.ERROR)
mail_handler.setFormatter(logging.Formatter('Error: %(asctime)s - %(name)s - %(levelname)s - %(message)s'))
mail_handler.addFilter(filter_error) # add filter to handler
logger.addHandler(file_handler)
logger.addHandler(mail_handler)
# test
logger.info("info 1")
logger.info("info 2")
logger.error("error 1")
logger.error("error 2")
Related
I have a logger function from logging package that after I call it, I can send the message through logging level.
I would like to send this message also to another function, which is a Telegram function called SendTelegramMsg().
How can I get the message after I call the funcion setup_logger send a message through logger.info("Start") for example, and then send this exatcly same message to SendTelegramMsg() function which is inside setup_logger function?
My currently setup_logger function:
# Define the logging level and the file name
def setup_logger(telegram_integration=False):
"""To setup as many loggers as you want"""
filename = os.path.join(os.path.sep, pathlib.Path(__file__).parent.resolve(), 'logs', str(dt.date.today()) + '.log')
formatter = logging.Formatter('%(levelname)s: %(asctime)s: %(message)s', datefmt='%m/%d/%Y %H:%M:%S')
level = logging.DEBUG
handler = logging.FileHandler(filename, 'a')
handler.setFormatter(formatter)
consolehandler = logging.StreamHandler()
consolehandler.setFormatter(formatter)
logger = logging.getLogger('logs')
if logger.hasHandlers():
# Logger is already configured, remove all handlers
logger.handlers = []
else:
logger.setLevel(level)
logger.addHandler(handler)
logger.addHandler(consolehandler)
#if telegram_integration == True:
#SendTelegramMsg(message goes here)
return logger
After I call the function setup_logger():
logger = setup_logger()
logger.info("Start")
The output:
INFO: 01/06/2022 11:07:12: Start
How am I able to get this message and send to SendTelegramMsg() if I enable the integration to True?
Implement a custom logging.Handler:
class TelegramHandler(logging.Handler):
def emit(self, record):
message = self.format(record)
SendTelegramMsg(message)
# SendTelegramMsg(message, record.levelno) # Passing level
# SendTelegramMsg(message, record.levelname) # Passing level name
Add the handler:
def setup_logger(telegram_integration=False):
# ...
if telegram_integration:
telegram_handler = TelegramHandler()
logger.addHandler(telegram_handler)
return logger
Usage, no change:
logger = setup_logger()
logger.info("Start")
Picking up the idea suggested by #gold_cy: You implement a custom logging.Handler. Some hints for that:
for the handler to be able to send message via a bot, you may want to pass the bot to the handlers __init__ so that you have it available later
emit must be implemented by you. Here you'll want to call format which gives you a formatted version of the log record. You can then use that message to send it via the bot
Maybe having a look at the implementation of StreamHandler and FileHandler is helpful as well
#Defining a global flag
tlInt=False
# Define the logging level and the file name
def setup_logger(telegram_integration=False):
"""To setup as many loggers as you want"""
filename = os.path.join(os.path.sep, pathlib.Path(__file__).parent.resolve(), 'logs', str(dt.date.today()) + '.log')
formatter = logging.Formatter('%(levelname)s: %(asctime)s: %(message)s', datefmt='%m/%d/%Y %H:%M:%S')
level = logging.DEBUG
handler = logging.FileHandler(filename, 'a')
handler.setFormatter(formatter)
consolehandler = logging.StreamHandler()
consolehandler.setFormatter(formatter)
logger = logging.getLogger('logs')
if logger.hasHandlers():
# Logger is already configured, remove all handlers
logger.handlers = []
else:
logger.setLevel(level)
logger.addHandler(handler)
logger.addHandler(consolehandler)
if telegram_integration == True:
global tlInt
tlInt=True
return logger
#Logic : If telegram integration is true, it will call SendTelegramMsg to send the message to the app based on the level; and if it is false, it will save the message in local file based on the level
def GenerateLog(logger,levelFlag,data):
global tlInt
if tlInt == True:
SendTelegramMsg(levelFlag,data)
else:
if levelFlag == "warning":
logger.warning(data)
elif levelFlag == "error":
logger.error(data)
elif levelFlag == "debug":
logger.debug(data)
elif levelFlag == "critical":
logger.critical(data)
else:
logger.info(data)
#You can used the same logic in SendTelegramMsg which used in GenerateLog for deciding the level
def SendTelegramMsg(levelFlag,data):
#your code goes here
logger=setup_logger()
GenerateLog(logger,'warning','Start')
Hello Stack Overflow users,
i'm writing a simple code and i got a "AttributeError" exception
log_level = 'INFO' # DEBUG - INFO - WARNING - ERROR - CRITICAL
logging.basicConfig(
format=('%(asctime)s > [%(levelname)s] in %(funcName)s on line '
'%(lineno)d > %(message)s'), level = logging.log_level, \
filename='logs.log', filemode='w', encoding='utf-8')
Sorry for asking a suck stupid question.
Thank you in advance.
Not very clear on your requirement, but the below approach can be a neat solution
def get_logger(cls, logger_name, create_file=False):
# create logger
log = logging.getLogger(logger_name)
log.setLevel(level=logging.INFO)
# create formatter and add it to the handlers
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
if create_file:
# create file handler for logger.
fh = logging.FileHandler('my_log.log')
fh.setLevel(level=logging.DEBUG)
fh.setFormatter(formatter)
# reate console handler for logger.
ch = logging.StreamHandler()
ch.setLevel(level=logging.DEBUG)
ch.setFormatter(formatter)
# add handlers to logger.
if create_file:
log.addHandler(fh)
log.addHandler(ch)
return log
I have made a log file. Now i want to print a Headers in log file. SO that one can identify what's that column is for.
I have provided the code i have written.
def logger(type_of_message, msg, ticket_no): #This will create records/log in app.log file. And can be used for debuging too.
log_file = str(datetime.utcnow().strftime('%d_%m_%Y')) + '.log'
if(type_of_message == 'INFO' or 'Info'):
logger = logging.LoggerAdapter(logging.getLogger(__name__), {'ticket_no': '%s' % (ticket_no)})
logging.basicConfig(filename = log_file, filemode = 'a', level = logging.INFO, format = '%(ticket_no)s - %(asctime)s - %(name)s - %(levelname)s - %(message)s')
logger.info(msg)
elif(type_of_message == 'ERROR' or 'Error'):
logger = logging.LoggerAdapter(logging.getLogger(__name__), {'ticket_no': '%s'%(ticket_no)})
logging.basicConfig(filename = 'app.log', filemode = 'a', level = logging.ERROR, format = '%(ticket_no)s - %(asctime)s - %(name)s - %(levelname)s - %(message)s')
logger.info(msg)
I want to print it like this:
TICKET NO DATE TIME NAME USER MESSAGE
INC0010265 - 2019-06-25 20:41:54,286 - log - INFO - Mail Send Succesfully
INC0010265 - 2019-06-25 20:41:56,271 - log - INFO - INC0010265 Ticket Update Succesfully
INC0010265 - 2019-06-25 20:41:56,271 - log - INFO - -----Ticket Closed-----
This is not 100% what you're asking for but is closest I can think of.
First of all, you shouldn't call basicConfig more than once for the same program. It actually simply does nothing...
Also, you kinda losing the logging power by defining your own function that checks the message type. It looks funny to call logger.info for an error message. What you should do instead is use Handlers. Check out this code:
import logging
logger = logging.getLogger(__name__)
log_file = str(datetime.utcnow().strftime('%d_%m_%Y')) + '.log'
error_handler = logging.FileHandler("app.log")
error_handler.setLevel(logging.ERROR)
info_handler = logging.FileHandler(log_file)
info_handler.setLevel(logging.INFO)
logger.setLevel(logging.INFO)
logger.addHandler(info_handler)
logger.addHandler(error_handler)
header_formatter = logging.Fomatter('%(message)s')
default_formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
error_handler.setFormatter(header_formatter)
info_handler.setFormatter(header_formatter)
logger.error("DATE\t\tTIME\t\tNAME\t\tUSER\t\tTICKET NO\tMESSAGE")
error_handler.setFormatter(default_formatter)
info_handler.setFormatter(default_formatter )
A few explanations:
We are using two handlers: error_handler and info_handler to direct different types of messages to different files. NOTE that in this way error messages will be also in the info file.
We set the logger's level to the minimal between them (in this case INFO) and add to it our handlers.
Then we use special formatters to the header and regular messages. We first assign to both handlers the header formatter. We then print the header (using logger.error so it will go to both logs). And then set the handlers with the regular formatter.
All messages logged from this point on will be in the regular format.
2nd NOTE: I couldn't find a way to add a field to the fomat as what you tried to acheive for ticket_no. As you can see I moved the column to be before the messgae so simply add the ticke_no in the start of your message. What you can do is for example:
def log_msg(logger, type_of_message, msg, ticket_no):
msg = "{}\t{}".format(ticket_no, msg)
if(type_of_message == 'INFO' or 'Info'):
logger.info(msg)
elif(type_of_message == 'ERROR' or 'Error'):
logger.error(msg)
I have a python program that utilizes multiprocessing to increase efficiency, and a function that creates a logger for each process. The logger function looks like this:
import logging
import os
def create_logger(app_name):
"""Create a logging interface"""
# create a logger
if logging in os.environ:
logging_string = os.environ["logging"]
if logging_string == "DEBUG":
logging_level = loggin.DEBUG
else if logging_string == "INFO":
logging_level = logging.INFO
else if logging_string == "WARNING":
logging_level = logging.WARNING
else if logging_string == "ERROR":
logging_level = logging.ERROR
else if logging_string == "CRITICAL":
logging_level = logging.CRITICAL
else:
logging_level = logging.INFO
logger = logging.getLogger(app_name)
logger.setLevel(logging_level)
# Console handler for error output
console_handler = logging.StreamHandler()
console_handler.setLevel(logging_level)
# Formatter to make everything look nice
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
console_handler.setFormatter(formatter)
# Add the handlers to the logger
logger.addHandler(console_handler)
return logger
And my processing functions look like this:
import custom_logging
def do_capture(data_dict_access):
"""Process data"""
# Custom logging
LOGGER = custom_logging.create_logger("processor")
LOGGER.debug("Doing stuff...")
However, no matter what the logging environment variable is set to, I still receive debug log messages in the console. Why is my logging level not taking effect, surely the calls to setLevel() should stop the debug messages from being logged?
Here is an easy way to create a logger object:
import logging
import os
def create_logger(app_name):
"""Create a logging interface"""
logging_level = os.getenv('logging', logging.INFO)
logging.basicConfig(
level=logging_level,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
logger = logging.getLogger(app_name)
return logger
Discussion
There is no need to convert from "DEBUG" to logging.DEBUG, the logging module understands these strings.
Use basicConfig to ease the pain of setting up a logger. You don't need to create handler, set format, set level, ... This should work for most cases.
Update
I found out why your code does not work, besides the else if. Consider your line:
if logging in os.environ:
On this line loggging without quote refers to the logging library package. What you want is:
if 'logging' in os.environ:
I encountered such a problem and couldn't solve it. I used python's logger to log info, logger level set to logging.DEBUG. I used gunicorn to log
info at the same time. Normally, the error message goes to python's logger, and the link messages and other messages written by logger.info or logger.debug goes to the log file of gunicorn. However with one application it doesn't behave so. The messages output by logger.info also goes to python's logger. The problem is, I only want to see error messages in python's logger, all the other messages would be seen from gunicorn's logger. Can anyone give me a clue where I might do wrong in this situation?
thx in advance,
alex
The following is my config:
LOGGER_LEVEL = logging.DEBUG
LOGGER_ROOT_NAME = "root"
LOGGER_ROOT_HANLDERS = [logging.StreamHandler, logging.FileHandler]
LOGGER_ROOT_LEVEL = LOGGER_LEVEL
LOGGER_ROOT_FORMAT = "[%(asctime)s %(levelname)s %(name)s %(funcName)s:%(lineno)d] %(message)s"
LOGGER_LEVEL = logging.ERROR
LOGGER_FILE_PATH = "/data/log/web/"
Code:
def config_root_logger(self):
formatter = logging.Formatter(self.config.LOGGER_ROOT_FORMAT)
logger = logging.getLogger()
logger.setLevel(self.config.LOGGER_ROOT_LEVEL)
filename = os.path.join(self.config.LOGGER_FILE_PATH, "secondordersrv.log")
handler = logging.FileHandler(filename)
handler.setFormatter(formatter)
logger.addHandler(handler)
# 测试环境配置再增加console的日志记录
self._add_test_handler(logger, formatter)
def _add_test_handler(self, logger, formatter):
# 测试环境配置再增加console的日志记录
if self.config.RUN_MODE == 'test':
handler = logging.StreamHandler()
handler.setFormatter(formatter)
logger.addHandler(handler)
My gunicorn config looks like this:
errorlog = '/data/log/web/%s.log' % APP_NAME
loglevel = 'info'
accesslog = '-'
You did not set the level of your handler.
After handler.setFormatter(formatter), add the following line:
handler.setLevel(self.config.LOGGER_LEVEL)