How to prevent Python Tornado from logging to stdout/console? - python-3.x

I need to disable Tornado from logging to STDOUT. I am using Python 3.8 and is running on Ubuntu 18.04. I want my log statements to be handled by a rotating file logger only. The issue is that logged statements are logged into the file and also to console:
import logging
from logging.handlers import RotatingFileHandler
logger = logging.getLogger("ex_logger")
nh = logging.NullHandler()
rfh = RotatingFileHandler(filename="./logs/process.log", mode='a', maxBytes=50000000, backupCount=25, encoding=None, delay=False)
rfh.setLevel(logging.DEBUG)
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
rfh.setFormatter(formatter)
logger.handlers = []
logger.propagete = False
logger.addHandler(rfh)
logging.getLogger("tornado.access").handlers = []
logging.getLogger("tornado.application").handlers = []
logging.getLogger("tornado.general").handlers = []
logging.getLogger("tornado.access").addHandler(nh)
logging.getLogger("tornado.application").addHandler(nh)
logging.getLogger("tornado.general").addHandler(nh)
logging.getLogger("tornado.access").propagate = False
logging.getLogger("tornado.application").propagate = False
logging.getLogger("tornado.general").propagate = False
....
def main():
######
# this message eppears in both the output log file and stdout
######
logger.info(" application init ... ")
asyncio.set_event_loop_policy(tornado.platform.asyncio.AnyThreadEventLoopPolicy())
tornado.options.parse_command_line()
app = Application()
app.listen(options.port)
tornado.ioloop.IOLoop.current().start()
if __name__ == "__main__":
main()

The problem is that from the moment you start up your IDE, logging.getLogger("tornado") may have a StreamHandler attached. This doesn't happen with every IDE but it does happen with Spyder. So that is the one you have to replace by a NullHandler:
import logging
nh = logging.NullHandler()
tornado_logger = logging.getLogger("tornado")
tornado_logger.handlers.clear()
# tornado_logger.handlers = [] # would work instead of .clear() but it's best practice to change the list and not replace it, just in case some other variable name somewhere else still refers to it.
tornado_logger.addHandler(nh)
You don't need to do anything with the children of the "tornado" logger, e.g. "tornado.access", et cetera.

You also need to define a logging policy for the root handler (logging.getLogger("")). Tornado looks at the root handler to decide whether logging has already been configured or needs a default setup.

Related

Python logging does not log when used inside a Pytest fixture

I have a Pytest + Selenium project and I would like to use the logging module.
However, when I set up logging in conftest.py like this
#pytest.fixture(params=["chrome"], scope="class")
def init_driver(request):
start = datetime.now()
logging.basicConfig(filename='.\\test.log', level=logging.INFO)
if request.param == "chrome":
options = ChromeOptions()
options.add_argument("--start-maximized")
web_driver = webdriver.Chrome(ChromeDriverManager().install(), options=options)
if request.param == "firefox":
web_driver = webdriver.Firefox(GeckoDriverManager().install())
request.cls.driver = web_driver
yield
end = datetime.now()
logging.info(f"{end}: --- DURATION: {end - start}")
web_driver.close()
looks like test.log is not created at all and there are no error messages or other indications something went wrong.
How can I make this work?
Two facts first:
logging.basicConfig() only has an effect if no logging configuration was done before invoking it (the target logger has no handlers registered).
pytest registers custom handlers to the root logger to be able to capture log records emitted in your code, so you can test whether your program logging behaviour is correct.
This means that calling logging.basicConfig(filename='.\\test.log', level=logging.INFO) in a fixture will do nothing, since the test run has already started and the root logger has handlers attached by pytest. You thus have two options:
Disable the builtin logging plugin completely. This will stop log records capturing - if you have tests where you are analyzing emitted logs (e.g. using the caplog fixture), those will stop working. Invocation:
$ pytest -p no:logging ...
You can persist the flag in pyproject.toml so it is applied automatically:
[tool.pytest.ini_options]
addopts = "-p no:logging"
Or in pytest.ini:
[pytest]
addopts = -p no:logging
Configure and use live logging. The configuration in pyproject.toml, equivalent to your logging.basicConfig() call:
[tool.pytest.ini_options]
log_file = "test.log"
log_file_level = "INFO"
In pytest.ini:
[pytest]
log_file = test.log
log_file_level = INFO
Of course, the logging.basicConfig() line can be removed from the init_driver fixture in this case.

How to send message to only one filehandler from logger class

So I've setup a single logger with multiple handlers in my flask app. From the research I've done this far it seems you can only have one logger in a flask app. So to manage different streams I need to add handlers. My issue with this is that every message goes to all of the handlers. Does anyone know of a way to send a message to a single handler with a Logger instance?
I have a work around but I'm not exactly excited about it, I feel like I'm missing something basic here, but my google-fu is weak today I guess.
import logging
from contextlib import suppress
log_fmt = logging.Formatter(
fmt='%(levelname)s: %(asctime)s - %(name)s - : %(message)s',
datefmt='%m/%d/%Y %H:%M:%S'
)
v = logging.FileHandler(f'log1.log')
v.setFormatter(log_fmt)
v.setLevel(logging.INFO)
v.name = f'log1'
lg = logging.getLogger('log1')
lg.setLevel(logging.INFO)
lg.addHandler(v)
b = logging.FileHandler(f'log2.log')
b.setFormatter(log_fmt)
b.setLevel(logging.INFO)
b.name = f'log2'
# I can write to different files if i create different loggers, but then we run into the flask limitation
#lg2 = logging.getLogger('log2')
#lg2.setLevel(logging.INFO)
lg.addHandler(b)
l1 = logging.getLogger('log1')
#probably there is a better way to handle this but its just an example
def to_handler(msg, name='log1'):
msg = logging.LogRecord(name, logging.INFO, '',0,msg,[],'')
with suppress(IndexError):
[x for x in l1.handlers if getattr(x,'name', '') == name][0].emit(msg)
l1.to_handler = to_handler
# in a client module
import logging
l1 = logging.getLogger('log1')
l1.to_handler('some mildly important information')
when setting up the logger in flask I have to do
app.logger = l1
thats why I'm not sure how I can use multiple loggers

Dynamic filename for file handler

I am trying to set up a logger that will write a new timestamped log file every time the application is run in a specific directory.
for example what I am trying to do is
timestampFilename = time.strftime("runtimelog%b_%d_%Y_%H:%M:%S.txt")
fh = logging.FileHandler(r'C:\my\folder\logs\'+timestampFilename, mode='w')
An example tweeked from the Logging Cookbook:
import logging
import os
from datetime import datetime
# create logger with 'spam_application'
logger = logging.getLogger('MYAPP')
logger.setLevel(logging.DEBUG)
# create file handler which logs in a specific directory
logdir = '.'
if 'APP_LOG_DIR' in os.environ:
logdir = os.environ['APP_LOG_DIR']
logfile = datetime.now().strftime("run_%b_%d_%Y_%H_%M_%S.log")
fh = logging.FileHandler(os.path.join(logdir, logfile))
fh.setLevel(logging.DEBUG)
# create formatter and add it to the handlers
formatter = logging.Formatter('[%(asctime)s][%(name)s][%(levelname)s] %(message)s')
fh.setFormatter(formatter)
# add the handlers to the logger
logger.addHandler(fh)
logger.debug("my first log line")
The log directory may be configured with the environment variable APP_LOG_DIR and path names are built in a platform independent way thanks to os.path.

Why don't my lower level logs write to the file, but the error and above do?

I created a small function to setup logging, with a filehandler for 'everything', and smtphandler for error and above. Error logs write to the log file and send correctly to email, but debug, info, notset don't, even though setlevel is set to 0 for filehandler. Why's that? Code below
#logsetup.py
import logging
import logging.handlers
def _setup_logger(name, log_file):
"""Function to setup logger"""
logger = logging.getLogger(name)
#Create Formatters
file_formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
mail_formatter = logging.Formatter('%(name)s - %(message)s')
#Create handler, set formatting, set log level
file_handler_obj = logging.FileHandler(log_file)
file_handler_obj.setFormatter(file_formatter)
file_handler_obj.setLevel(0)
#Create handler, set formatting, set log level
smtp_handler_obj = logging.handlers.SMTPHandler(mailhost=('smtp.gmail.com', 587),
fromaddr='mymail#example.com',
toaddrs='mymail#example.com',
subject='Error in Script',
credentials=('mymail#example.com', 'pwexample'), #username, password
secure=())
smtp_handler_obj.setFormatter(mail_formatter)
smtp_handler_obj.setLevel(logging.ERROR)
# add the handlers to logger
logger.addHandler(smtp_handler_obj)
logger.addHandler(file_handler_obj)
return logger
#mytest.py
import time
import logsetup
if __name__ == '__main__':
TEST_SETTINGS = config_funcs._get_config('TEST_SETTINGS')
logtime = time.strftime('%Y%m%d') # -%H%M%S")
log = logsetup._setup_logger('TEST', TEST_SETTINGS['logging_dir'] + 'Py_Log_%s.log' % logtime)
log.error('Writes to log file and sends email')
log.debug('Supposed to write to log file, does nothing.')
Apparently, logging needs it's own logging level aside from the handlers. Setting logger.setLevel(logging.DEBUG) right before returning logger causes it to work correctly. Documentation says
When a logger is created, the level is set to NOTSET (which causes all
messages to be processed when the logger is the root logger, or
delegation to the parent when the logger is a non-root logger). Note
that the root logger is created with level WARNING.
Which means that if the handlers are lower level than the root logger (which ERROR is not, but DEBUG is) then the handlers which I guess is a child because I'm getting a named logger? Not quite sure on the why of it, but that does 'fix' it, in case anyone comes to this later.

Python logging printing double to console

It seems that on a couple machines I'm getting double output like this:
INFO LED NOTIFICATION STARTED
INFO:output_logger:LED NOTIFICATION STARTED
This is the function I'm using:
def setup_logger(name, log_file, level=logging.INFO, ContentFormat='%(asctime)s %(levelname)s %(message)s', DateTimeFormat="%Y-%m-%d %H:%M:%S", CreateConsoleLogger=False):
"""Function setup as many loggers as you want"""
logger = logging.getLogger(name)
logger.setLevel(level)
if CreateConsoleLogger:
# create console handler
handler = logging.StreamHandler()
handler.setLevel(level)
formatter = logging.Formatter("%(levelname)s %(message)s")
handler.setFormatter(formatter)
logger.addHandler(handler)
# create a file handler
handler = RotatingFileHandler(log_file, maxBytes=2000000, backupCount=5)
handler.setLevel(level)
formatter = logging.Formatter(ContentFormat, DateTimeFormat)
handler.setFormatter(formatter)
logger.addHandler(handler)
return logger
This is how I'm creating the logger:
output_logger = setup_logger('output_logger', 'log/autofy.log', level=logging.DEBUG, CreateConsoleLogger=True)
And this is how I call it:
output_logger.info("LED NOTIFICATION STARTED")
On a most of computers I just see the same message printed to the console that's saved to the file as expected ("INFO LED NOTIFICATION STARTED"), but on other computers it's doing this weird double output thing. My code is exactly the same from one computer to another, so any ideas what could be causing this on some computers and not others?
EDIT
I'm writing the script using notepad++ and running it in a terminal window on an Ubuntu 16.04 machine. I'm using python3.
Try adding this to your code:
logging._get_logger().propagate = False

Resources