I recently set up logging in my Flask app such that it would log to files and stdout. I was inserting logging statements like
current_app.logger.info('Logging message')
into routes so that I could see logging messages when I ran pytest tests on the routes. This worked (logged to the files and console) for a little while, but at some point it stopped logging to the console while continuing to log to the log files.
I'm not sure what could have caused logging to stdout to stop. The only thing that comes to mind is that I attempted to add a logging statement to the test module at one point. This caused an error, which may have had something to do with the logger not being configured in the app defined by my application factory pytest fixture. However, I've since deleted this logging statement from the test module, and I'm still not getting the console logs that I got previously.
Can anyone see any reason why logging to the console would have stopped?
My logging configuration:
__init__.py:
def create_app(test_config=None):
app = Flask('project_name', instance_relative_config=True)
...
if not app.debug:
if not os.path.exists('logs'):
os.mkdir('logs')
file_handler = RotatingFileHandler('logs/trivia.log', maxBytes=10240, backupCount=10)
file_handler.setFormatter(logging.Formatter(
'%(asctime)s %(levelname)s: %(message)s [in %(pathname)s:%(lineno)d]'))
file_handler.setLevel(logging.INFO)
app.logger.addHandler(file_handler)
stream_handler = StreamHandler(sys.stdout)
stream_handler.setFormatter(logging.Formatter(
'%(asctime)s %(levelname)s: %(message)s [in %(pathname)s:%(lineno)d]'
))
stream_handler.setLevel(logging.INFO)
app.logger.addHandler(stream_handler)
app.logger.setLevel(logging.INFO)
routes.py
#bp.route('/add', methods=['POST'])
def add_question():
current_app.logger.info("Received request to /add")
...
This happened because logging messages only get displayed in the console when a pytest test fails. My tests were all passing for a while, so I wasn't seeing the logging messages.
Previously I used this logging pattern
log = logging.getLogger(__name__)
log.setLevel(logging.DEBUG)
fh = logging.FileHandler("logs.log", 'w', encoding="utf-8")
formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
fh.setFormatter(formatter)
log.addHandler(fh)
And my log file had these messages:
2019-08-21 11:08:08,271 - INFO - Started
2019-08-21 11:08:08,271 - INFO - Connecting to Google Sheets...
2019-08-21 11:08:11,857 - INFO - Successfuly connected to Google Sheet
2019-08-21 11:08:11,869 - ERROR - Not found: 'TG'
2019-08-21 11:08:11,869 - DEBUG - Getting values from Sheets...
2019-08-21 11:08:12,452 - DEBUG - Got new event row: "Flex - Flex"
2019-08-21 11:08:12,453 - DEBUG - Done. Values:
...
It looks ugly and I changed it to this:
logging.basicConfig(
level = logging.DEBUG,
format = '%(asctime)s - %(levelname)s - %(message)s',
filename = 'logs.log', filemode = 'w'
)
log = logging.getLogger()
Now my log file looks like that
2019-08-21 11:14:02,374 - INFO - Started
2019-08-21 11:14:02,374 - INFO - Connecting to Google Sheets...
2019-08-21 11:14:02,406 - DEBUG - [b'eyJ0eX...jcifQ', b'eyJ...NvbSJ9', b'f7BQ...dE2w']
2019-08-21 11:14:02,407 - INFO - Refreshing access_token
2019-08-21 11:14:03,448 - DEBUG - Starting new HTTPS connection (1): www.googleapis.com:443
2019-08-21 11:14:04,447 - DEBUG - https://www.googleapis.com:443 "GET /drive/v3/files?q=mimeType%3D%27application%2Fvnd.google-apps.spreadsheet%27&pageSize=1000&supportsTeamDrives=True&includeTeamDriveItems=True HTTP/1.1" 200 None
2019-08-21 11:14:04,450 - DEBUG - Starting new HTTPS connection (1): sheets.googleapis.com:443
2019-08-21 11:14:05,782 - DEBUG - https://sheets.googleapis.com:443 "GET /v4/spreadsheets/1q6...cTI?includeGridData=false HTTP/1.1" 200 None
2019-08-21 11:14:05,899 - INFO - Successfuly connected to Google Sheet
2019-08-21 11:14:05,901 - ERROR - Not found: 'TG'
2019-08-21 11:14:05,902 - DEBUG - Getting values from Sheets...
2019-08-21 11:14:06,426 - DEBUG - https://sheets.googleapis.com:443 "GET /v4/spreadsheets/1q6...cTI/values/%D0%9B%D0%B8%D1%81%D1%821 HTTP/1.1" 200 None
2019-08-21 11:14:06,543 - DEBUG - Got new event row: xxx
2019-08-21 11:14:06,544 - DEBUG - Done. Values: xxx
2019-08-21 11:14:06,544 - DEBUG - Getting line...
2019-08-21 11:14:06,550 - DEBUG - Starting new HTTPS connection (1): api.site.com:443
2019-08-21 11:14:07,521 - DEBUG - https://api.site.com:443 "GET /v1/fix...?Id=33 HTTP/1.1" 200 6739
I receiving some requests debug logs that I didn't use in my code
How to turn it off?
I found that is because of the requests module
The reason for all the log messages from the requests module is because of the below piece of code,
logging.basicConfig(
level = logging.DEBUG # This sets the root logger to DEBUG
)
logging.basicConfig changes the logger configuration of the root logger present in your program
Since you've used requests module here, requests uses urllib3 which prints those debug messages.
Fix:
You can use your first logger initialisation code to configure your logger else, you can also use the below code,
logging.basicConfig(
format = '%(asctime)s - %(levelname)s - %(message)s',
filename = 'logs.log', filemode = 'w'
)
logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG) # Here you are changing the level of your logger alone
You can try the following code. In this case you don't change the basic config of logging module. You can config only your instance of logging.getLogger. If you use this implementation, it shouldn't have effect to other modules. Furthermore, you can handle the console and file separately so this logger can be more configurable.
Code:
import logging
# Create a custom logger
logger = logging.getLogger(__name__)
# Create handlers
c_handler = logging.StreamHandler()
f_handler = logging.FileHandler("logs.log", "w", encoding="utf-8")
c_handler.setLevel(logging.INFO)
f_handler.setLevel(logging.DEBUG)
# Create formatters and add it to handlers
c_format = logging.Formatter("%(asctime)s - %(levelname)s - %(message)s")
f_format = logging.Formatter("%(asctime)s - %(levelname)s - %(message)s")
c_handler.setFormatter(c_format)
f_handler.setFormatter(f_format)
# Add handlers to the logger
logger.addHandler(c_handler)
logger.addHandler(f_handler)
logger.warning("This is a warning")
logger.error("This is an error")
Output:
>>> python test.py
2019-08-21 08:55:10,579 - WARNING - This is a warning
2019-08-21 08:55:10,580 - ERROR - This is an error
As stated in #noufel13's answer, the reason for the additional log messages showing up is that you set the root logger's level to DEBUG and add a handler to it.
By default, importing logging instantiates a RootLogger object with the level WARNING and no handlers attached.
Importing requests instantiates a bunch of library loggers, in particular
urllib3.util.retry, urllib3.util, urllib3, urllib3.connection, urllib3.response, urllib3.connectionpool, urllib3.poolmanager and requests.
This is done so that deveolpers using these libraries can easily enable debug output to test and troubleshoot their code, or just have logging output without modifying 3rd party code, just by configuring the root logger accordingly.
All these loggers are created with default values, the relevant being level NOTSET, propagate True and disabled False.
Level NOTSET (emphasis mine):
[...] 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
propagate True:
[...] events logged to this logger will be passed to the handlers of higher level (ancestor) loggers [...] The constructor sets this attribute to True
disabled False:
disabled is an undocumented attribute of the Logger class
These loggers always exist when you import requests in your program. Also, they always emit their log messages.
These messsages are not visible by default because all of these loggers only have a NullHandler attached ...
This handler does nothing. It's intended to be used to avoid the
"No handlers could be found for logger XXX" one-off warning. This is
important for library code, which may contain code to log events. If a user
of the library does not configure logging, the one-off warning might be
produced; to avoid this, the library developer simply needs to instantiate
a NullHandler and add it to the top-level logger of the library module or
package.
... and because their ultimate ancestor, the root logger, also comes without any handlers and is set to level WARNING per default.
basicConfig only affects the root logger. The way you use it, a specific Formatter is created and attached to a newly instantiated FileHandler that, in turn, is attached to the root logger. Also, the root logger's level is set to DEBUG.
Now, all the messages from the requests and urllib3 loggers ending up at the root logger after traversing the hierarchy, are logged to the file handled by the root's FileHandler.
How to stop that
As per your original recipe, and as outlined by #milanbalazs, continue to create and configure dedicated loggers for your intended pupose and leave the root logger alone.
You state, that you find programmatical configuration rather ugly; I somewhat agree.
The logging.config module offers various ways for you to configure your logging.
For example, you could use dictConfig() and provide the configuration via a dictionary that you could potentially import from another, dedicated module.
The following configures a logger named test the same way your basicConfig() approach does for the root logger:
import logging.config
cfg_dict = {
"version": 1,
"formatters": {
"default": {
"format": '%(asctime)s - %(levelname)s - %(message)s',
}
},
"handlers": {
"file": {
"class": "logging.FileHandler",
"formatter": "default",
"filename": "logs.log",
"mode": "w",
}
},
"loggers": {
"test": {
"level": "DEBUG",
"handlers": ["file"],
}
}
}
logging.config.dictConfig(cfg_dict)
log = logging.getLogger("test")
The logging.config module futher supports configuration files via fileConfig() and a socket listener for configuration data in the suitable formats for dict and file configuration.
If you'd rather configure and use the root logger, you can either disable all "unwanted" loggers or instruct them not to propagate.
import logging
import requests
for logger in logging.Logger.manager.loggerDict.values():
logger.propagate = False
# -- OR --
logger.disabled = True
logging.basicConfig(
level=logging.DEBUG,
format='%(asctime)s - %(levelname)s - %(message)s',
filename='logs.log',
filemode='w'
)
log = logging.getLogger()
Note: Both, the Manager's loggerDict and the Logger's disabled attributes are undocumented. While they are not explicitly marked as an internal implementation detail via Python's convention (i.e. leading underscore name), I would hesitate to consider them part of the official logging API and thus expect them to be potential subject to change.
In a Flask application, I would like to configure two differents file loggers:
One for HTTP access logs (access.log), which will log stuff like:
1.2.3.4 - [11/Jun/2018 09:51:13] "GET /some/path HTTP/1.1" 200 -
One for application logs (my_app.log), which will keep logs defined by me in my code when I'm using current_app.logger.info('some message'):
2018-06-08 15:08:50,083 - flask.app - INFO - some message
How should my configuration looks like to achieve this ? Here is what I tried, without success:
# content of "run.py" :
app = Flask(__name__)
app.logger.removeHandler(default_handler)
# Define 'my_app.log' :
handler = logging.FileHandler('my_app.log')
handler.setLevel(logging.INFO)
formatter = logging.Formatter(app.config['LOGGING_FORMAT'])
handler.setFormatter(formatter)
app.logger.addHandler(handler)
# Define 'access.log' :
access_handler = logging.getLogger('werkzeug')
access_handler = logging.FileHandler('access.log')
access_handler.setLevel(logging.DEBUG)
access_handler.setFormatter(app.config['LOGGING_FORMAT'])
app.logger.addHandler(access_handler)
# Then register my blueprints:
app.register_blueprint(some_blueprint, url_prefix='/')
....
And I run it with python3 run.py. With this config, the only logged stuff are the HTTP access logs in the my_app.log file.
What's wrong with my config ?
I'm building a python application with also a web interface, with Flask web framework.
It runs on Flask internal server in debug/dev mode and in production mode it runs on tornado as wsgi container.
This is how i've set up my logger:
log_formatter = logging.Formatter('%(asctime)s [%(levelname)s] %(message)s')
file_handler = logging.handlers.RotatingFileHandler(LOG_FILE, maxBytes=5 * 1024 * 1024, backupCount=10)
file_handler.setFormatter(log_formatter)
console_handler = logging.StreamHandler(sys.stdout)
console_handler.setFormatter(log_formatter)
log = logging.getLogger('myAppLogger')
log.addHandler(file_handler)
log.addHandler(console_handler)
To add my logger to the Flask app i tried this:
app = Flask('system.web.server')
app.logger_name = 'myAppLogger'
But the log still going to the Flask default log handler, and in addition, I didn't found how to customize the log handlers also for the Tornado web server.
Any help is much appreciated,
thanks in advance
AFAIK, you can't change the default logger in Flask. You can, however, add your handlers to the default logger:
app = Flask('system.web.server')
app.logger.addHandler(file_handler)
app.logger.addHandler(console_handler)
Regarding my comment above - "Why would you want to run Flask in tornado ...", ignore that. If you are not seeing any performance hit, then clearly there's no need to change your setup.
If, however, in future you'd like to migrate to a multithreaded container, you can look into uwsgi or gunicorn.
I managed to do it, multiple handler, each doing their thing, so that ERROR log will not show on the INFO log as well and end up with duplicate info grrr:
app.py
import logging
from logging.handlers import RotatingFileHandler
app = Flask(__name__)
# Set format that both loggers will use:
formatter = logging.Formatter("[%(asctime)s] {%(pathname)s:%(lineno)d} %(levelname)s - %(message)s")
# Set logger A for known errors
log_handler = RotatingFileHandler('errors.log', maxBytes=10000, backupCount=1)
log_handler.setFormatter(formatter)
log_handler.setLevel(logging.INFO)
a = logging.getLogger('errors')
a.addHandler(log_handler)
# Set logger B for bad exceptions
exceptions_handler = RotatingFileHandler('exceptions.log', maxBytes=10000, backupCount=1)
exceptions_handler.setFormatter(formatter)
exceptions_handler.setLevel(logging.ERROR)
b = logging.getLogger('exceptions')
b.addHandler(exceptions_handler)
...
whatever_file_where_you_want_to_log.py
import logging
import traceback
# Will output known error messages to 'errors.log'
logging.getLogger('errors').error("Cannot connect to database, timeout")
# Will output the full stack trace to 'exceptions.log', when trouble hits the fan
logging.getLogger('exceptions').error(traceback.format_exc())
I am building an applicatrion with Flask and Celery and I am trying to send my application logs to Papertrail. This works fine for my regular (synchronous) application logs. The configuration looks like this:
import logging
from logging.handlers import SysLogHandler
import socket
class ContextFilter(logging.Filter):
hostname = socket.gethostname()
def filter(self, record):
record.hostname = ContextFilter.hostname
return True
f = ContextFilter()
logger = logging.getLogger()
logger.setLevel(logging.INFO)
logger.addFilter(f)
formatter = logging.Formatter(
"%(asctime)s - %(name)s - %(levelname)s - %(message)s"
)
syslog = SysLogHandler(address=('<myapp>.papertrailapp.com', <port>))
syslog.setFormatter(formatter)
logger.addHandler(syslog)
I have tried adding this logger to Celery tasks but all I see is output in sdout and nothing in Papertrail. Does Celery do something to get around the normal logging flow?
I realize Celery has a task-specific logger but I cannot find any documentation on how this could be configured with Celery.
If I read this correctly, the secret is to call the function redirect_stdouts_to_logger to send stdout to your SysLogHandler instance. Celery's docs have more.