simplify logging configuration
single default handler and formatter don't remove handlers configure level once using setLevel document logging reorganize logging tests
This commit is contained in:
parent
85fa8aabf5
commit
66b1b752da
13 changed files with 399 additions and 451 deletions
|
|
@ -199,21 +199,6 @@ The following configuration values are used internally by Flask:
|
|||
|
||||
Default: ``timedelta(hours=12)`` (``43200`` seconds)
|
||||
|
||||
.. py:data:: LOGGER_NAME
|
||||
|
||||
The name of the logger that the Flask application sets up. If not set,
|
||||
it will take the import name passed to ``Flask.__init__``.
|
||||
|
||||
Default: ``None``
|
||||
|
||||
.. py:data:: LOGGER_HANDLER_POLICY
|
||||
|
||||
When to activate the application's logger handler. ``'always'`` always
|
||||
enables it, ``'debug'`` only activates it in debug mode, ``'production'``
|
||||
only activates it when not in debug mode, and ``'never'`` never enables it.
|
||||
|
||||
Default: ``'always'``
|
||||
|
||||
.. py:data:: SERVER_NAME
|
||||
|
||||
Inform the application what host and port it is bound to. Required for
|
||||
|
|
@ -329,6 +314,11 @@ The following configuration values are used internally by Flask:
|
|||
``SESSION_REFRESH_EACH_REQUEST``, ``TEMPLATES_AUTO_RELOAD``,
|
||||
``LOGGER_HANDLER_POLICY``, ``EXPLAIN_TEMPLATE_LOADING``
|
||||
|
||||
.. versionchanged:: 1.0
|
||||
|
||||
``LOGGER_NAME`` and ``LOGGER_HANDLER_POLICY`` were removed. See
|
||||
:ref:`logging` for information about configuration.
|
||||
|
||||
Configuring from Files
|
||||
----------------------
|
||||
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ instructions for web development with Flask.
|
|||
templating
|
||||
testing
|
||||
errorhandling
|
||||
logging
|
||||
config
|
||||
signals
|
||||
views
|
||||
|
|
|
|||
|
|
@ -143,213 +143,11 @@ determined.
|
|||
Handlers are prioritized by specificity of the exception classes they are
|
||||
registered for instead of the order they are registered in.
|
||||
|
||||
Error Mails
|
||||
-----------
|
||||
Logging
|
||||
-------
|
||||
|
||||
If the application runs in production mode (which it will do on your
|
||||
server) you might not see any log messages. The reason for that is that
|
||||
Flask by default will just report to the WSGI error stream or stderr
|
||||
(depending on what's available). Where this ends up is sometimes hard to
|
||||
find. Often it's in your webserver's log files.
|
||||
|
||||
I can pretty much promise you however that if you only use a logfile for
|
||||
the application errors you will never look at it except for debugging an
|
||||
issue when a user reported it for you. What you probably want instead is
|
||||
a mail the second the exception happened. Then you get an alert and you
|
||||
can do something about it.
|
||||
|
||||
Flask uses the Python builtin logging system, and it can actually send
|
||||
you mails for errors which is probably what you want. Here is how you can
|
||||
configure the Flask logger to send you mails for exceptions::
|
||||
|
||||
ADMINS = ['yourname@example.com']
|
||||
if not app.debug:
|
||||
import logging
|
||||
from logging.handlers import SMTPHandler
|
||||
mail_handler = SMTPHandler('127.0.0.1',
|
||||
'server-error@example.com',
|
||||
ADMINS, 'YourApplication Failed')
|
||||
mail_handler.setLevel(logging.ERROR)
|
||||
app.logger.addHandler(mail_handler)
|
||||
|
||||
So what just happened? We created a new
|
||||
:class:`~logging.handlers.SMTPHandler` that will send mails with the mail
|
||||
server listening on ``127.0.0.1`` to all the `ADMINS` from the address
|
||||
*server-error@example.com* with the subject "YourApplication Failed". If
|
||||
your mail server requires credentials, these can also be provided. For
|
||||
that check out the documentation for the
|
||||
:class:`~logging.handlers.SMTPHandler`.
|
||||
|
||||
We also tell the handler to only send errors and more critical messages.
|
||||
Because we certainly don't want to get a mail for warnings or other
|
||||
useless logs that might happen during request handling.
|
||||
|
||||
Before you run that in production, please also look at :ref:`logformat` to
|
||||
put more information into that error mail. That will save you from a lot
|
||||
of frustration.
|
||||
|
||||
|
||||
Logging to a File
|
||||
-----------------
|
||||
|
||||
Even if you get mails, you probably also want to log warnings. It's a
|
||||
good idea to keep as much information around that might be required to
|
||||
debug a problem. By default as of Flask 0.11, errors are logged to your
|
||||
webserver's log automatically. Warnings however are not. Please note
|
||||
that Flask itself will not issue any warnings in the core system, so it's
|
||||
your responsibility to warn in the code if something seems odd.
|
||||
|
||||
There are a couple of handlers provided by the logging system out of the
|
||||
box but not all of them are useful for basic error logging. The most
|
||||
interesting are probably the following:
|
||||
|
||||
- :class:`~logging.FileHandler` - logs messages to a file on the
|
||||
filesystem.
|
||||
- :class:`~logging.handlers.RotatingFileHandler` - logs messages to a file
|
||||
on the filesystem and will rotate after a certain number of messages.
|
||||
- :class:`~logging.handlers.NTEventLogHandler` - will log to the system
|
||||
event log of a Windows system. If you are deploying on a Windows box,
|
||||
this is what you want to use.
|
||||
- :class:`~logging.handlers.SysLogHandler` - sends logs to a UNIX
|
||||
syslog.
|
||||
|
||||
Once you picked your log handler, do like you did with the SMTP handler
|
||||
above, just make sure to use a lower setting (I would recommend
|
||||
`WARNING`)::
|
||||
|
||||
if not app.debug:
|
||||
import logging
|
||||
from themodule import TheHandlerYouWant
|
||||
file_handler = TheHandlerYouWant(...)
|
||||
file_handler.setLevel(logging.WARNING)
|
||||
app.logger.addHandler(file_handler)
|
||||
|
||||
.. _logformat:
|
||||
|
||||
Controlling the Log Format
|
||||
--------------------------
|
||||
|
||||
By default a handler will only write the message string into a file or
|
||||
send you that message as mail. A log record stores more information,
|
||||
and it makes a lot of sense to configure your logger to also contain that
|
||||
information so that you have a better idea of why that error happened, and
|
||||
more importantly, where it did.
|
||||
|
||||
A formatter can be instantiated with a format string. Note that
|
||||
tracebacks are appended to the log entry automatically. You don't have to
|
||||
do that in the log formatter format string.
|
||||
|
||||
Here are some example setups:
|
||||
|
||||
Email
|
||||
`````
|
||||
|
||||
::
|
||||
|
||||
from logging import Formatter
|
||||
mail_handler.setFormatter(Formatter('''
|
||||
Message type: %(levelname)s
|
||||
Location: %(pathname)s:%(lineno)d
|
||||
Module: %(module)s
|
||||
Function: %(funcName)s
|
||||
Time: %(asctime)s
|
||||
|
||||
Message:
|
||||
|
||||
%(message)s
|
||||
'''))
|
||||
|
||||
File logging
|
||||
````````````
|
||||
|
||||
::
|
||||
|
||||
from logging import Formatter
|
||||
file_handler.setFormatter(Formatter(
|
||||
'%(asctime)s %(levelname)s: %(message)s '
|
||||
'[in %(pathname)s:%(lineno)d]'
|
||||
))
|
||||
|
||||
|
||||
Complex Log Formatting
|
||||
``````````````````````
|
||||
|
||||
Here is a list of useful formatting variables for the format string. Note
|
||||
that this list is not complete, consult the official documentation of the
|
||||
:mod:`logging` package for a full list.
|
||||
|
||||
.. tabularcolumns:: |p{3cm}|p{12cm}|
|
||||
|
||||
+------------------+----------------------------------------------------+
|
||||
| Format | Description |
|
||||
+==================+====================================================+
|
||||
| ``%(levelname)s``| Text logging level for the message |
|
||||
| | (``'DEBUG'``, ``'INFO'``, ``'WARNING'``, |
|
||||
| | ``'ERROR'``, ``'CRITICAL'``). |
|
||||
+------------------+----------------------------------------------------+
|
||||
| ``%(pathname)s`` | Full pathname of the source file where the |
|
||||
| | logging call was issued (if available). |
|
||||
+------------------+----------------------------------------------------+
|
||||
| ``%(filename)s`` | Filename portion of pathname. |
|
||||
+------------------+----------------------------------------------------+
|
||||
| ``%(module)s`` | Module (name portion of filename). |
|
||||
+------------------+----------------------------------------------------+
|
||||
| ``%(funcName)s`` | Name of function containing the logging call. |
|
||||
+------------------+----------------------------------------------------+
|
||||
| ``%(lineno)d`` | Source line number where the logging call was |
|
||||
| | issued (if available). |
|
||||
+------------------+----------------------------------------------------+
|
||||
| ``%(asctime)s`` | Human-readable time when the |
|
||||
| | :class:`~logging.LogRecord` was created. |
|
||||
| | By default this is of the form |
|
||||
| | ``"2003-07-08 16:49:45,896"`` (the numbers after |
|
||||
| | the comma are millisecond portion of the time). |
|
||||
| | This can be changed by subclassing the formatter |
|
||||
| | and overriding the |
|
||||
| | :meth:`~logging.Formatter.formatTime` method. |
|
||||
+------------------+----------------------------------------------------+
|
||||
| ``%(message)s`` | The logged message, computed as ``msg % args`` |
|
||||
+------------------+----------------------------------------------------+
|
||||
|
||||
If you want to further customize the formatting, you can subclass the
|
||||
formatter. The formatter has three interesting methods:
|
||||
|
||||
:meth:`~logging.Formatter.format`:
|
||||
handles the actual formatting. It is passed a
|
||||
:class:`~logging.LogRecord` object and has to return the formatted
|
||||
string.
|
||||
:meth:`~logging.Formatter.formatTime`:
|
||||
called for `asctime` formatting. If you want a different time format
|
||||
you can override this method.
|
||||
:meth:`~logging.Formatter.formatException`
|
||||
called for exception formatting. It is passed an :attr:`~sys.exc_info`
|
||||
tuple and has to return a string. The default is usually fine, you
|
||||
don't have to override it.
|
||||
|
||||
For more information, head over to the official documentation.
|
||||
|
||||
|
||||
Other Libraries
|
||||
---------------
|
||||
|
||||
So far we only configured the logger your application created itself.
|
||||
Other libraries might log themselves as well. For example, SQLAlchemy uses
|
||||
logging heavily in its core. While there is a method to configure all
|
||||
loggers at once in the :mod:`logging` package, I would not recommend using
|
||||
it. There might be a situation in which you want to have multiple
|
||||
separate applications running side by side in the same Python interpreter
|
||||
and then it becomes impossible to have different logging setups for those.
|
||||
|
||||
Instead, I would recommend figuring out which loggers you are interested
|
||||
in, getting the loggers with the :func:`~logging.getLogger` function and
|
||||
iterating over them to attach handlers::
|
||||
|
||||
from logging import getLogger
|
||||
loggers = [app.logger, getLogger('sqlalchemy'),
|
||||
getLogger('otherlibrary')]
|
||||
for logger in loggers:
|
||||
logger.addHandler(mail_handler)
|
||||
logger.addHandler(file_handler)
|
||||
See :ref:`logging` for information on how to log exceptions, such as by
|
||||
emailing them to admins.
|
||||
|
||||
|
||||
Debugging Application Errors
|
||||
|
|
|
|||
175
docs/logging.rst
Normal file
175
docs/logging.rst
Normal file
|
|
@ -0,0 +1,175 @@
|
|||
.. _logging:
|
||||
|
||||
Logging
|
||||
=======
|
||||
|
||||
Flask uses standard Python :mod:`logging`. All Flask-related messages are
|
||||
logged under the ``'flask'`` logger namespace.
|
||||
:meth:`Flask.logger <flask.Flask.logger>` returns the logger named
|
||||
``'flask.app'``, and can be used to log messages for your application. ::
|
||||
|
||||
@app.route('/login', methods=['POST'])
|
||||
def login():
|
||||
user = get_user(request.form['username'])
|
||||
|
||||
if user.check_password(request.form['password']):
|
||||
login_user(user)
|
||||
app.logger.info('%s logged in successfully', user.username)
|
||||
return redirect(url_for('index'))
|
||||
else:
|
||||
app.logger.info('%s failed to log in', user.username)
|
||||
abort(401)
|
||||
|
||||
|
||||
Basic Configuration
|
||||
-------------------
|
||||
|
||||
When you want to configure logging for your project, you should do it as soon
|
||||
as possible when the program starts. If :meth:`app.logger <flask.Flask.logger>`
|
||||
is accessed before logging is configured, it will add a default handler. If
|
||||
possible, configure logging before creating the application object.
|
||||
|
||||
This example uses :func:`~logging.config.dictConfig` to create a logging
|
||||
configuration similar to Flask's default, except for all logs::
|
||||
|
||||
from logging.config import dictConfig
|
||||
|
||||
dictConfig({
|
||||
'version': 1,
|
||||
'formatters': {'default': {
|
||||
'format': '[%(asctime)s] %(levelname)s in %(module)s: %(message)s',
|
||||
}},
|
||||
'handlers': {'wsgi': {
|
||||
'class': 'logging.StreamHandler',
|
||||
'stream': 'ext://flask.logging.wsgi_errors_stream',
|
||||
'formatter': 'default'
|
||||
}},
|
||||
'root': {
|
||||
'level': 'INFO',
|
||||
'handlers': ['wsgi']
|
||||
}
|
||||
})
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
|
||||
Default Configuration
|
||||
`````````````````````
|
||||
|
||||
If you do not configure logging yourself, Flask will add a
|
||||
:class:`~logging.StreamHandler` to :meth:`app.logger <flask.Flask.logger>`
|
||||
automatically. During requests, it will write to the stream specified by the
|
||||
WSGI server in ``environ['wsgi.errors']`` (which is usually
|
||||
:data:`sys.stderr`). Outside a request, it will log to :data:`sys.stderr`.
|
||||
|
||||
|
||||
Removing the Default Handler
|
||||
````````````````````````````
|
||||
|
||||
If you configured logging after accessing
|
||||
:meth:`app.logger <flask.Flask.logger>`, and need to remove the default
|
||||
handler, you can import and remove it::
|
||||
|
||||
from flask.logging import default_handler
|
||||
|
||||
app.logger.removeHandler(default_handler)
|
||||
|
||||
|
||||
Email Errors to Admins
|
||||
----------------------
|
||||
|
||||
When running the application on a remote server for production, you probably
|
||||
won't be looking at the log messages very often. The WSGI server will probably
|
||||
send log messages to a file, and you'll only check that file if a user tells
|
||||
you something went wrong.
|
||||
|
||||
To be proactive about discovering and fixing bugs, you can configure a
|
||||
:class:`logging.handlers.SMTPHandler` to send an email when errors and higher
|
||||
are logged. ::
|
||||
|
||||
import logging
|
||||
from logging.handlers import SMTPHandler
|
||||
|
||||
mail_handler = SMTPHandler(
|
||||
mailhost='127.0.0.1',
|
||||
fromaddr='server-error@example.com',
|
||||
toaddrs=['admin@example.com'],
|
||||
subject='Application Error'
|
||||
)
|
||||
mail_handler.setLevel(logging.ERROR)
|
||||
mail_handler.setFormatter(logging.Formatter(
|
||||
'[%(asctime)s] %(levelname)s in %(module)s: %(message)s'
|
||||
))
|
||||
|
||||
if not app.debug:
|
||||
app.logger.addHandler(mail_handler)
|
||||
|
||||
This requires that you have an SMTP server set up on the same server. See the
|
||||
Python docs for more information about configuring the handler.
|
||||
|
||||
|
||||
Injecting Request Information
|
||||
-----------------------------
|
||||
|
||||
Seeing more information about the request, such as the IP address, may help
|
||||
debugging some errors. You can subclass :class:`logging.Formatter` to inject
|
||||
your own fields that can be used in messages. You can change the formatter for
|
||||
Flask's default handler, the mail handler defined above, or any other
|
||||
handler. ::
|
||||
|
||||
from flask import request
|
||||
from flask.logging import default_handler
|
||||
|
||||
class RequestFormatter(logging.Formatter):
|
||||
def format(self, record):
|
||||
record.url = request.url
|
||||
record.remote_addr = request.remote_addr
|
||||
return super().format(record)
|
||||
|
||||
formatter = RequestFormatter(
|
||||
'[%(asctime)s] %(remote_addr)s requested %(url)s\n'
|
||||
'%(levelname)s in %(module)s: %(message)s'
|
||||
))
|
||||
default_handler.setFormatter(formatter)
|
||||
mail_handler.setFormatter(formatter)
|
||||
|
||||
|
||||
Other Libraries
|
||||
---------------
|
||||
|
||||
Other libraries may use logging extensively, and you want to see relevant
|
||||
messages from those logs too. The simplest way to do this is to add handlers
|
||||
to the root logger instead of only the app logger. ::
|
||||
|
||||
from flask.logging import default_handler
|
||||
|
||||
root = logging.getLogger()
|
||||
root.addHandler(default_handler)
|
||||
root.addHandler(mail_handler)
|
||||
|
||||
Depending on your project, it may be more useful to configure each logger you
|
||||
care about separately, instead of configuring only the root logger. ::
|
||||
|
||||
for logger in (
|
||||
app.logger,
|
||||
logging.getLogger('sqlalchemy'),
|
||||
logging.getLogger('other_package'),
|
||||
):
|
||||
logger.addHandler(default_handler)
|
||||
logger.addHandler(mail_handler)
|
||||
|
||||
|
||||
Werkzeug
|
||||
````````
|
||||
|
||||
Werkzeug logs basic request/response information to the ``'werkzeug'`` logger.
|
||||
If the root logger has no handlers configured, Werkzeug adds a
|
||||
:class:`~logging.StreamHandler` to its logger.
|
||||
|
||||
|
||||
Flask Extensions
|
||||
````````````````
|
||||
|
||||
Depending on the situation, an extension may choose to log to
|
||||
:meth:`app.logger <flask.Flask.logger>` or its own named logger. Consult each
|
||||
extension's documentation for details.
|
||||
Loading…
Add table
Add a link
Reference in a new issue