How to share a file between modules for logging in python - python

I wanted to log messages from different module in python to a file. Also I need to print some messages to console for debugging purpose. I used logger module for this purpose . But logger module will log all the logs with given severity and above to file or console.
I wanted only some messages logged to file and it should not include the messages from the console.
Similarly the console messages should not contain messages logged to file.
My approach would be to have a singleton class which shares file write operation between various modules.
Is there any easier approach than this in python ?
EDIT:
I am new to Python. Sample program I tried
logger = logging.getLogger('simple_example')
logger.setLevel(logging.INFO)
# create file handler which logs even debug messages
fh = logging.FileHandler('spam.log')
fh.setLevel(logging.CRITICAL)
# create console handler with a higher log level
ch = logging.StreamHandler()
ch.setLevel(logging.ERROR)
# create formatter and add it to the handlers
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
ch.setFormatter(formatter)
fh.setFormatter(formatter)
# add the handlers to logger
logger.addHandler(ch)
logger.addHandler(fh)
# 'application' code
logger.debug('debug message')
logger.info('info message')
logger.warn('warn message')
logger.error('error message')
logger.critical('critical message')
Console prints :
2015-02-03 15:36:00,651 - simple_example - ERROR - error message
2015-02-03 15:36:00,651 - simple_example - CRITICAL - critical message
#I don't want critical messages in console.

Here is a script that creates two loggers, use the one you wish to log to a file or stdout. The question is : on which criteria do you choose to log to stdout or file, knowing that (from your question) you don't want the criteria to be the log level (debug, error, critical...)
#!/usr/bin/python
import logging
logger_stdout = logging.getLogger('logger_stdout')
logger_stdout.setLevel(logging.DEBUG)
sh = logging.StreamHandler()
sh.setFormatter(logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s'))
logger_stdout.addHandler(sh)
logger_stdout.debug('stdout debug message')
logger_file = logging.getLogger('logger_file')
logger_file.setLevel(logging.DEBUG)
fh = logging.FileHandler("foo.log")
fh.setFormatter(logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s'))
logger_file.addHandler(fh)
logger_file.debug('file debug message')
And when I run this script:
D:\jrx\jrxpython
λ python essai.py
2015-02-03 11:12:07,210 - logger_stdout - DEBUG - stdout debug message
D:\jrx\jrxpython
λ cat foo.log
2015-02-03 11:12:07,224 - logger_file - DEBUG - file debug message
D:\jrx\jrxpython
λ

CRITICAL is higher than ERROR:
You can also verify yourself:
>>> import logging
>>> print logging.CRITICAL
50
>>> print logging.ERROR
40
>>>
There are two cases in logging:
Logging the same process - you should have several handlers with different logging levels based on how verbose the logs should be. A higher level means less output. That's why DEBUG is the lowest predefined log level - it writes everything for debug purposes.
Logging different processes - you should have several loggers set up, they can be accessed from anywhere in your code using logging.getLogger(name). This gives the same logger every time, so that logger set-up persists through the code and only needs to be executed once.
The first case demonsrates that you can't have an "error but not critical" log, since this is the opposite of how logs should work. You can have a "critical but not error" log, that is less verbose. This is what you probably want.

Related

Python log level cannot be set

Here is my code
logger = logging.getLogger("JarvisAI")
# Create handlers
c_handler = logging.StreamHandler()
f_handler = logging.FileHandler(logname)
c_handler.setLevel(logging.WARNING)
f_handler.setLevel(logging.INFO)
# Create formatters and add it to handlers
c_format = logging.Formatter('%(name)s - %(levelname)s - %(message)s', "%Y-%m-%d %H:%M:%S")
f_format = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s', "%Y-%m-%d %H:%M:%S")
c_handler.setFormatter(c_format)
f_handler.setFormatter(f_format)
# Add handlers to the logging
logger.addHandler(c_handler)
logger.addHandler(f_handler)
Running logger.info("Test") does not produce anything in the logfile.
However logger.warning and other higher log level works fine both in console and file.
Pls help.
The logger itself also has a logging level, and that needs to be below (or above, depending on your point of view) that of the handlers to show the handlers' output:
logger.setLevel(logging.INFO)
(or even debug level: the formatters' levels will prevent debugging info from being output anyway) will do that.
This is also shown in the first code block of the Python logging cookbook. Have a read through it.
The reason you are getting warning and higher log level output, is because warning is the default logging level.

python logging printing empty lines message for print

We are trying to introducing python logging in out existing python project.
As we already have lot more print statement in code, we decided to redirect all print logs to logging file using below statement.
import logging, sys
import os
from logging.handlers import TimedRotatingFileHandler
formatter = logging.Formatter("%(asctime)s - %(pathname)s - %(funcName)s - %(levelname)s - %(message)s")
log_path = '/logs/server.log'
handler = TimedRotatingFileHandler(filename=log_path, when='midnight')
handler.setFormatter(formatter)
log = logging.getLogger()
log.setLevel(logging.DEBUG)
log.addHandler(handler)
sys.stderr.write = log.error
sys.stdout.write = log.info
However, it printing two lines with second as empty logs.
2020-09-21 09:03:05,978 - utils.py - logger - INFO - <_io.TextIOWrapper name='<stderr>' mode='w' encoding='UTF-8'>
2020-09-21 09:03:05,978 - utils.py - logger - INFO -
2020-09-21 09:03:05,978 - utils.py - logger - INFO - 1600693385 Mon Sep 21 09:03:05 2020 19882 Registering functions
2020-09-21 09:03:05,978 - utils.py - logger - INFO -
It is working for logging function like info, error. it only giving issue for print.
It would be great help if someone know cause of it.
In additional detail, we are using gunicorn as server and falcon as rest framework.
You should not modify the write functions of stdout and stderr but add a stream handler for info and for error (one on stdout and the other one on stderr)
You can find an example on SO here: https://stackoverflow.com/a/31459386/14306518

In python I can write a log to console but its not getting written into file

import logging
#Create and configure logger
logging.basicConfig(filename="newfile.txt", format='%(asctime)s %(message)s',filemode='w')
logging.debug("Harmless debug Message")
logging.info("Just an information")
logging.warning("Its a Warning")
logging.error("Did you try to divide by zero")
logging.critical("Internet is down")
In the console its printing all these informations. Its never written to a file. Really thankful to those help me sort out this. Searching in internet from morning tried all the possibilities but still the logs not writing to file
Create an new logger with desired stream & file handlers:
import logger, sys
logger = logging.Logger('AmazeballsLogger')
#Stream/console output
logger.handler = logging.StreamHandler(sys.stdout)
logger.handler.setLevel(logging.WARNING)
formatter = logging.Formatter("%(asctime)s - %(levelname)s - %(message)s")
logger.handler.setFormatter(formatter)
logger.addHandler(self.handler)
#File output
fh = logging.FileHandler("test.log")
fh.setLevel(logging.DEBUG)
fh.setFormatter(logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s"))
logger.addHandler(fh)
And you're ready to take it for a spin:
print(logger.handlers)
logger.critical("critical")
logger.error("error")
logger.warning("warning")
logger.info("info")
logger.debug("debug")
With the following console output only showing WARNING and higher:
[<StreamHandler stdout (WARNING)>, <FileHandler C:\Users\...\test.log (DEBUG)>]
2020-04-13 17:52:57,729 - CRITICAL - critical
2020-04-13 17:52:57,731 - ERROR - error
2020-04-13 17:52:57,734 - WARNING - warning
While test.log contains all levels:
2020-04-13 17:52:57,729 - AmazeballsLogger - CRITICAL - critical
2020-04-13 17:52:57,731 - AmazeballsLogger - ERROR - error
2020-04-13 17:52:57,734 - AmazeballsLogger - WARNING - warning
2020-04-13 17:52:57,736 - AmazeballsLogger - INFO - info
2020-04-13 17:52:57,736 - AmazeballsLogger - DEBUG - debug
The key is to understand how logging handlers work and checking whether they use correct levels (as in the code cells above, just print logger.handlers). Furthermore, overwriting basic configuration is a bad practice that can lead to problems when you run multiple loggers in the same python environment. I recommend this video, it sheds light on stream/file handlers in python logging.
I got this example from logging documentation
import logging
logging.basicConfig(level=logging.DEBUG,
format='%(asctime)s %(levelname)s %(message)s',
filename='myapp.log',
filemode='w')
logging.debug('A debug message')
logging.info('Some information')
logging.warning('A shot across the bows')
and the examples that I have seen on the web they are all creating .logfile. So try changing the extension of file from txt to log

Schedule package: Suppress "Running job Every" messages

I'm using 'schedule' for a current project:
https://pypi.python.org/pypi/schedule
It's great, but I want to suppress the "Running job Every x seconds" log message that gets triggered every time a scheduled task is run. Example of what I mean below:
Is there any way to achieve this? Below is my current logging.basicConfig, I'm quite new to configuring logging beyond the absolute basics, so the solution may lie more with that:
# Define overall logging settings; these log levels/format go to file
logging.basicConfig(level=variables.settings['log_level_file'],
format='%(asctime)s %(name)-12s %(levelname)-8s %(message)s',
filename='logs\log.log')
# Set up Handlers and Formatters; these log levels/format go to console
console = logging.StreamHandler()
console.setLevel(variables.settings['log_level_console'])
formatter = logging.Formatter('%(asctime)s %(name)-12s %(levelname)-8s %(message)s')
console.setFormatter(formatter)
logging.getLogger('').addHandler(console)
As Meloman pointed out, you can directly set the individual 'schedule' logger to a higher level than the INFO default:
logging.getLogger('schedule').setLevel(logging.CRITICAL)

Logging setLevel is being ignored

The below code is copied from the documentation. I am supposed to be able to see all the info logs. But I don't. I am only able to see the warn and above even though I've set setLevel to INFO.
Why is this happening? foo.py:
import logging
logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG)
logger.debug('debug message')
logger.info('info message')
logger.warn('warn message')
logger.error('error message')
logger.critical('critical message')
Output:
workingDirectory$ python foo.py
warn message
error message
critical message
Where did the info and debug messages go??
Replace the line
logger.setLevel(logging.DEBUG)
with
logging.basicConfig(level=logging.DEBUG, format='%(message)s')
and it should work as expected. If you don't configure logging with any handlers (as in your post - you only configure a level for your logger, but no handlers anywhere), you'll get an internal handler "of last resort" which is set to output just the message (with no other formatting) at the WARNING level.
Try running logging.basicConfig() in there. Of note, I see you mention INFO, but use DEBUG. As written, it should show all five messages. Swap out DEBUG with INFO, and you should see four messages.
import logging
logging.basicConfig()
logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG)
logger.debug('debug message')
logger.info('info message')
logger.warn('warn message')
logger.error('error message')
logger.critical('critical message')
edit: Do you have logging set up elsewhere in your code already? Can't reproduce the exact behavior you note with the specific code provided.
As pointed by some users, using:
logging.basicConfig(level=logging.DEBUG, format='%(message)s')
like written in the accepted answer is not a good option because it sets the log level for the root logger, so it may lead to unexpected behaviours (eg. third party libraries may start to log debug messages if you set loglevel=logging.DEBUG)
In my opinion the best solution is to set log level just for your logger, like this:
import logging
logger = logging.getLogger('MyLogger')
handler = logging.StreamHandler()
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
handler.setFormatter(formatter)
logger.addHandler(handler)
logger.setLevel(logging.DEBUG)
Not really intuitive solution, but is necessary if you want to set log level only for 'MyLogger' and leave the root logger untouched.
So, why is logging.basicConfig(level=logging.DEBUG, format='%(message)s') setting the log level globally?
Well, actually it doesn't. As said, it's just changing the configuration of the root logger and, as described in the python documentation:
Loggers should NEVER be instantiated directly, but always through the
module-level function logging.getLogger(name). Multiple calls to
getLogger() with the same name will always return a reference to the
same Logger object.
So, logging.basicConfig is creating a StreamHandler with a default Formatter and adding it to the root logger.
The point is that if any other library is using the "root logger", you're going to set that log level for that library too so it can happen that you start to see debug logs from third party libraries.
This is why I think it's better to create your own logger and set your own formatters and handlers, so you can leave the root logger untouched.
This is technically also an "answer", because it can "solve" the problem. BUT I definitely DO NOT like it. It is not intuitive, and I lost 2+ hours over it.
Before:
import logging
logger = logging.getLogger('foo')
logger.setLevel(logging.INFO)
logger.info('You can not see me')
# Or you can just use the following one-liner in command line.
# $ python -c "import logging; logger = logging.getLogger('foo'); logger.setLevel(logging.INFO); logger.info('You can not see me')"
After:
import logging
logging.debug('invisible magic') # <-- magic
logger = logging.getLogger('foo')
logger.setLevel(logging.INFO)
logger.info('But now you can see me')
# Or you can just use the following one-liner in command line.
$ python -c "import logging; logging.debug('invisible magic'); logger = logging.getLogger('foo'); logger.setLevel(logging.INFO); logger.info('But now you see me')"
PS: Comparing it to the current chosen answer, and #Vinay-Sajip's explanation, I can kind of understand why. But still, I wish it was not working that way.
If you want this to work WITHOUT basicConfig, you have to first set up the lowest possible level you'll log onto the logger. Since the logger sets a minimum threshold, handlers which have a lower threshold but belong to the same logger won't get those lower threshold messages since they're ignored by the logger in the first place. Intuitive, but not obvious.
We start by doing this:
lgr = logging.getLogger(name)
lgr.setLevel(logging.DEBUG)
Then, set up the handlers with the different levels you need, in my case I want DEBUG logging on stdout and INFO logging to a rotating file, so I do the following:
rot_hndlr = RotatingFileHandler('filename.log',
maxBytes=log_size,
backupCount=3)
rot_hndlr.setFormatter(formatter)
rot_hndlr.setLevel(logging.INFO)
lgr.addHandler(rot_hndlr)
stream_hndlr = logging.StreamHandler()
stream_hndlr.setFormatter(stream_formatter)
lgr.addHandler(stream_hndlr)
Then, to test, I do this:
lgr.debug("Hello")
lgr.info("There")
My stdout (console) will look like this:
Hello
There
and my filename.log file will look like this:
There
In short, change the level in logging.basicConfig will influence the global settings.
You should better set level for each logger and the specific handler in the logger.
The following is an example that displays all levels on the console and only records messages >= errors in log_file.log. Notice the level for each handler is different.
import logging
# Define logger
logger = logging.getLogger('test')
# Set level for logger
logger.setLevel(logging.DEBUG)
# Define the handler and formatter for console logging
consoleHandler = logging.StreamHandler() # Define StreamHandler
consoleHandler.setLevel(logging.DEBUG) # Set level
concolsFormatter = logging.Formatter('%(name)s - %(levelname)s - %(message)s') # Define formatter
consoleHandler.setFormatter(concolsFormatter) # Set formatter
logger.addHandler(consoleHandler) # Add handler to logger
# Define the handler and formatter for file logging
log_file = 'log_file'
fileHandler = logging.FileHandler(f'{log_file}.log') # Define FileHandler
fileHandler.setLevel(logging.ERROR) # Set level
fileFormatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') # Define formatter
fileHandler.setFormatter(fileFormatter) # Set formatter
logger.addHandler(fileHandler) # Add handler to logger
# Test
logger.debug('This is a debug')
logger.info('This is an info')
logger.warning('This is a warning')
logger.error('This is an error')
logger.critical('This is a critical')
Console output:
# Test
test - DEBUG - This is a debug
test - INFO - This is an info
test - WARNING - This is a warning
test - ERROR - This is an error
test - CRITICAL - This is a critical
File log_file.log content:
2021-09-22 12:50:50,938 - test - ERROR - This is an error
2021-09-22 12:50:50,938 - test - CRITICAL - This is a critical
To review your logger's level:
logger.level
The result should be one of the following:
10 # DEBUG
20 # INFO
30 # WARNING
40 # ERROR
50 # CRITICAL
To review your handlers's levels:
logger.handlers
[<StreamHandler stderr (DEBUG)>,
<FileHandler ***/log_file.log (ERROR)>]
The accepted answer does not work for me on Win10, Python 3.7.2.
My solution:
logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG)
It's order sensitive.
You have to set the basicConfig of the root logger to DEBUG, then you can set the level of your individual loggers to more restrictive levels.
This is not what I expected. Here is what I had to do:
#!/usr/bin/env python3
import logging
# by default this is WARNING. Leaving it as WARNING here overrides
# whatever setLevel-ing you do later so it seems they are ignored.
logging.basicConfig(level=logging.DEBUG)
l = logging.getLogger(__name__)
l.setLevel(level=logging.INFO)
# if I hadn't called basicConfig with DEBUG level earlier,
# info messages would STILL not be shown despite calling
# setLevel above. However now debug messages will not be shown
# for l because setLevel set it to INFO
l.warning('A warning message will be displayed')
l.info('A friendly info message will be displayed')
l.debug('A friendly debug message will not be displayed')
Most of the answers that I've found for this issue uses the basicConfig of the root logger.
It's not helpful for those who intend to use multiple independent loggers that were not initialised with basicConfig. The use of basicConfig implies that the loglevels of ALL loggers will be changed. It also had the unfortunate side effect of generating duplicate logs.
So I tried over several days experimenting with different ways to manipulate the loglevels and came up with one that finally worked.
The trick was to not only change the log levels of all the handlers but also the all the handlers of the parent of the logger.
def setLevel(self, infoLevel):
# To dynamically reset the loglevel, you need to also change the parent levels as well as all handlers!
self.logger.parent.setLevel(infoLevel)
for handler in self.logger.parent.handlers:
handler.setLevel(infoLevel)
self.logger.setLevel(infoLevel)
for handler in self.logger.handlers:
handler.setLevel(infoLevel)
The inspiration came from the fact that the basicConfig changes the root logger settings, so I was trying to do the same without using basicConfig.
For those that are interested, I did a little Python project on Github that illustrates the different issues with setting loglevel of the logger (it works partially), proves the SLogger (Sample Logger) implementation works, and also illustrates the duplicate log issue with basicConfig when using multiple loggers not initialised with it.
https://github.com/FrancisChung/python-logging-playground
TLDR: If you're only interested in a working sample code for the logger, the implentation is listed below
import logging
CRITICAL = 50
FATAL = CRITICAL
ERROR = 40
WARNING = 30
WARN = WARNING
INFO = 20
DEBUG = 10
NOTSET = 0
class SLogger():
"""
SLogger : Sample Logger class using the standard Python logging Library
Parameters:
name : Name of the Logger
infoLevel : logging level of the Logger (e.g. logging.DEBUG/INFO/WARNING/ERROR)
"""
def __init__(self, name: str, infoLevel=logging.INFO):
try:
if name is None:
raise ValueError("Name argument not specified")
logformat = '%(asctime)s %(levelname)s [%(name)s %(funcName)s] %(message)s'
self.logformat = logformat
self.name = name.upper()
self.logger = logging.getLogger(self.name)
self.logger.setLevel(infoLevel)
self.add_consolehandler(infoLevel, logformat)
except Exception as e:
if self.logger:
self.logger.error(str(e))
def error(self, message):
self.logger.error(message)
def info(self, message):
self.logger.info(message)
def warning(self, message):
self.logger.warning(message)
def debug(self, message):
self.logger.debug(message)
def critical(self, message):
self.logger.critical(message)
def setLevel(self, infoLevel):
# To dynamically reset the loglevel, you need to also change the parent levels as well as all handlers!
self.logger.parent.setLevel(infoLevel)
for handler in self.logger.parent.handlers:
handler.setLevel(infoLevel)
self.logger.setLevel(infoLevel)
for handler in self.logger.handlers:
handler.setLevel(infoLevel)
return self.logger.level
def add_consolehandler(self, infoLevel=logging.INFO,
logformat='%(asctime)s %(levelname)s [%(name)s %(funcName)s] %(message)s'):
sh = logging.StreamHandler()
sh.setLevel(infoLevel)
formatter = logging.Formatter(logformat)
sh.setFormatter(formatter)
self.logger.addHandler(sh)
Create object the right way, e.g. inspired by Google:
import logging
formatter = logging.Formatter('%(asctime)s %(threadName)s: %(message)s')
log = logging.getLogger(__name__)
log.setLevel(logging.DEBUG)
handler = logging.StreamHandler()
handler.setFormatter(formatter)
log.addHandler(handler)
log.debug('debug message')
log.info('info message')
log.warn('warn message')
log.error('error message')
log.critical('critical message')
2022-11-22 23:17:59,342 MainThread: debug message
2022-11-22 23:17:59,342 MainThread: info message
2022-11-22 23:17:59,342 MainThread: warn message
2022-11-22 23:17:59,342 MainThread: error message
2022-11-22 23:17:59,342 MainThread: critical message
As pointed out by #ManuelFedele, logging.basicConfig is not a good solution.
#VinaySajip explained that the setLevel is ignored because the logger is using the internal handler "of last resort", whose level is set to WARNING.
This explanation was also helpful:
The Handler.setLevel() method, just as in logger objects, specifies the lowest severity that will be dispatched to the appropriate destination. Why are there two setLevel() methods? The level set in the logger determines which severity of messages it will pass to its handlers. The level set in each handler determines which messages that handler will send on.
So a good solution is to add a handler to the logger, with the appropriate level:
import logging
logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG)
ch = logging.StreamHandler()
ch.setLevel(logging.DEBUG) # or whatever level should be displayed on the console
logger.addHandler(ch)
Output
>>> logger.debug('debug message')
debug message
>>> logger.info('info message')
info message
>>> logger.warn('warn message')
<stdin>:1: DeprecationWarning: The 'warn' method is deprecated, use 'warning' instead
warn message
>>> logger.error('error message')
error message
>>> logger.critical('critical message')
critical message

Categories

Resources