Add Blueprint logging support
This commit is contained in:
parent
b68dfd57f1
commit
583dfdd941
59
README.rst
59
README.rst
@ -15,8 +15,16 @@ Flask-Logging-Extras
|
|||||||
|
|
||||||
Flask-Logging-Extras adds additional logging features for Flask applications.
|
Flask-Logging-Extras adds additional logging features for Flask applications.
|
||||||
|
|
||||||
The only feature implemented now is adding extra arguments to the format
|
Extra keywords in the log formatters
|
||||||
string, like this (this example adds the category keyword to the logs:
|
------------------------------------
|
||||||
|
|
||||||
|
Adding extra keywords to the log format message is a bit tedious, as these
|
||||||
|
must be supplied to the logging methods in the `extra` argument as a
|
||||||
|
dictionary.
|
||||||
|
|
||||||
|
Flask-Logging-Extras makes this easier, so you can add such keywords to the
|
||||||
|
logging methods directly. The example adds the category keyword to the logs,
|
||||||
|
and shows how to do it with and without Flask-Logging-Extras:
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
@ -26,9 +34,56 @@ string, like this (this example adds the category keyword to the logs:
|
|||||||
app.config['FLASK_LOGGING_EXTRAS_KEYWORDS'] = {'category': '<unset>'}
|
app.config['FLASK_LOGGING_EXTRAS_KEYWORDS'] = {'category': '<unset>'}
|
||||||
app.logger.init_app(app)
|
app.logger.init_app(app)
|
||||||
|
|
||||||
|
# Without Flask-Logging-Extras
|
||||||
|
current_app.logger.info('this is a the message, as usual',
|
||||||
|
extra={'category': 'fancy-category'})
|
||||||
|
# With Flask-Logging-Extras
|
||||||
current_app.logger.info('this is the message, as usual',
|
current_app.logger.info('this is the message, as usual',
|
||||||
category='fancy-category')
|
category='fancy-category')
|
||||||
|
|
||||||
|
Logging the blueprint name
|
||||||
|
--------------------------
|
||||||
|
|
||||||
|
Although you can always access the blueprint name using `request.blueprint`,
|
||||||
|
adding it to the logs as a new keyword is not so easy.
|
||||||
|
|
||||||
|
With Flask-Logging-Extras you can specify a keyword that will hold the
|
||||||
|
blueprint name in the logs, and specify what value to put there if the log
|
||||||
|
doesn’t originate in a request, or it is not from a blueprint route, but
|
||||||
|
from an app route.
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
fmt = '[%(blueprint)s] %(message)s'
|
||||||
|
# Initialize log handlers as usual, like creating a FileHandler, and
|
||||||
|
# assign fmt to it as a format string
|
||||||
|
app.config['FLASK_LOGGING_EXTRAS_BLUEPRINT'] = (
|
||||||
|
'blueprint',
|
||||||
|
'<APP>',
|
||||||
|
'<NO REQUEST>',
|
||||||
|
)
|
||||||
|
|
||||||
|
bp = Blueprint('bpname', __name__)
|
||||||
|
app.register_blueprint(bp)
|
||||||
|
|
||||||
|
@app.route('/route/1/')
|
||||||
|
def route_1():
|
||||||
|
# This will produce the log message: "[<APP>] Message"
|
||||||
|
current_app.logger.info('Message')
|
||||||
|
|
||||||
|
return 'response 1'
|
||||||
|
|
||||||
|
@bp.route('/route/2/')
|
||||||
|
def route_2():
|
||||||
|
# This will produce the log message: "[bpname] Message"
|
||||||
|
current_app.logger.info('Message')
|
||||||
|
|
||||||
|
return 'response 2'
|
||||||
|
|
||||||
|
def random_function_outside_of_a_request():
|
||||||
|
# This will produce the log message: "[<NO REQUEST>] Message"
|
||||||
|
current_app.logger.info('Message')
|
||||||
|
|
||||||
Installation
|
Installation
|
||||||
------------
|
------------
|
||||||
|
|
||||||
|
@ -32,6 +32,8 @@ no value is present in the message record.
|
|||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
from flask import has_request_context, request
|
||||||
|
|
||||||
__version_info__ = ('0', '0', '1')
|
__version_info__ = ('0', '0', '1')
|
||||||
__version__ = '.'.join(__version_info__)
|
__version__ = '.'.join(__version_info__)
|
||||||
__author__ = 'Gergely Polonkai'
|
__author__ = 'Gergely Polonkai'
|
||||||
@ -40,7 +42,8 @@ __copyright__ = '(c) 2015 GT2'
|
|||||||
|
|
||||||
class FlaskExtraLogger(logging.getLoggerClass()):
|
class FlaskExtraLogger(logging.getLoggerClass()):
|
||||||
"""
|
"""
|
||||||
A logger class that is capable of adding extra keywords to log formatters
|
A logger class that is capable of adding extra keywords to log
|
||||||
|
formatters and logging the blueprint name
|
||||||
|
|
||||||
Usage:
|
Usage:
|
||||||
|
|
||||||
@ -55,8 +58,14 @@ class FlaskExtraLogger(logging.getLoggerClass()):
|
|||||||
|
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
app.config['FLASK_LOGGING_EXTRAS_KEYWORDS'] = {'category': '<unset>'}
|
app.config['FLASK_LOGGING_EXTRAS_KEYWORDS'] = {'category': '<unset>'}
|
||||||
|
app.config['FLASK_LOGGING_EXTRAS_BLUEPRINT'] = ('blueprint',
|
||||||
|
'<APP>',
|
||||||
|
'<NOT REQUEST>')
|
||||||
app.logger.init_app()
|
app.logger.init_app()
|
||||||
|
|
||||||
|
bp = Blueprint('my_blueprint', __name__)
|
||||||
|
app.register_blueprint(bp)
|
||||||
|
|
||||||
formatter = logging.Formatter(
|
formatter = logging.Formatter(
|
||||||
'[%(asctime)s] [%(levelname)s] [%(category)s] %(message)s')
|
'[%(asctime)s] [%(levelname)s] [%(category)s] %(message)s')
|
||||||
handler = logging.FileHandler('app.log', mode='a')
|
handler = logging.FileHandler('app.log', mode='a')
|
||||||
@ -68,9 +77,38 @@ class FlaskExtraLogger(logging.getLoggerClass()):
|
|||||||
app.logger.info('The message', category='my category')
|
app.logger.info('The message', category='my category')
|
||||||
|
|
||||||
# This will produce something like this in app.log:
|
# This will produce something like this in app.log:
|
||||||
# [TIMESTAMP2017-01-16 08:44:48.944] [INFO] [my category] The message
|
# [2017-01-16 08:44:48.944] [INFO] [my category] The message
|
||||||
|
|
||||||
|
formatter = logging.Formatter('[%(blueprint)s] %(message)s')
|
||||||
|
handler = logging.FileHandler('other.log', mode='a')
|
||||||
|
handler.setFormatter(formatter)
|
||||||
|
handler.setLevel(logging.INFO)
|
||||||
|
|
||||||
|
app.logger.addHandler(handler)
|
||||||
|
|
||||||
|
@app.route('/1/')
|
||||||
|
def route_1():
|
||||||
|
# This will produce this log message:
|
||||||
|
# [<APP>] Message
|
||||||
|
current_app.logger.info('Message')
|
||||||
|
|
||||||
|
return ''
|
||||||
|
|
||||||
|
@bp.route('/2')
|
||||||
|
def route_2():
|
||||||
|
# This will produce this log message:
|
||||||
|
# [my blueprint] Message
|
||||||
|
current_app.logger.info('Message')
|
||||||
|
|
||||||
|
return ''
|
||||||
|
|
||||||
|
# This will produce this log message:
|
||||||
|
[<NOT REQUEST>] Message
|
||||||
|
app.logger.info('Message')
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
_RESERVED_KEYWORDS = ('exc_info', 'extra', 'stack_info')
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
if 'app' in kwargs:
|
if 'app' in kwargs:
|
||||||
if kwargs['app'] is not None:
|
if kwargs['app'] is not None:
|
||||||
@ -84,6 +122,9 @@ class FlaskExtraLogger(logging.getLoggerClass()):
|
|||||||
|
|
||||||
self.app = None
|
self.app = None
|
||||||
self._valid_keywords = []
|
self._valid_keywords = []
|
||||||
|
self._blueprint_var = None
|
||||||
|
self._blueprint_app = None
|
||||||
|
self._blueprint_norequest = None
|
||||||
|
|
||||||
super(FlaskExtraLogger, self).__init__(*args, **kwargs)
|
super(FlaskExtraLogger, self).__init__(*args, **kwargs)
|
||||||
|
|
||||||
@ -91,6 +132,13 @@ class FlaskExtraLogger(logging.getLoggerClass()):
|
|||||||
if 'extra' not in kwargs:
|
if 'extra' not in kwargs:
|
||||||
kwargs['extra'] = {}
|
kwargs['extra'] = {}
|
||||||
|
|
||||||
|
# If we were asked to log the blueprint name, add it to the extra list
|
||||||
|
if self._blueprint_var is not None:
|
||||||
|
if has_request_context():
|
||||||
|
kwargs['extra'][self._blueprint_var] = request.blueprint or self._blueprint_app
|
||||||
|
else:
|
||||||
|
kwargs['extra'][self._blueprint_var] = self._blueprint_norequest
|
||||||
|
|
||||||
for kw in self._valid_keywords:
|
for kw in self._valid_keywords:
|
||||||
if kw in kwargs:
|
if kw in kwargs:
|
||||||
kwargs['extra'][kw] = kwargs[kw]
|
kwargs['extra'][kw] = kwargs[kw]
|
||||||
@ -100,6 +148,13 @@ class FlaskExtraLogger(logging.getLoggerClass()):
|
|||||||
|
|
||||||
super(FlaskExtraLogger, self)._log(*args, **kwargs)
|
super(FlaskExtraLogger, self)._log(*args, **kwargs)
|
||||||
|
|
||||||
|
def _check_reserved_word(self, word):
|
||||||
|
if word in self._RESERVED_KEYWORDS:
|
||||||
|
raise ValueError(
|
||||||
|
'"{keyword}" cannot be used as an extra keyword, as it is '
|
||||||
|
'reserved for internal use.'
|
||||||
|
.format(keyword=word))
|
||||||
|
|
||||||
def init_app(self, app):
|
def init_app(self, app):
|
||||||
"""
|
"""
|
||||||
Intialize the logger class with a Flask application
|
Intialize the logger class with a Flask application
|
||||||
@ -118,14 +173,21 @@ class FlaskExtraLogger(logging.getLoggerClass()):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
app.config.setdefault('FLASK_LOGGING_EXTRAS_KEYWORDS', {})
|
app.config.setdefault('FLASK_LOGGING_EXTRAS_KEYWORDS', {})
|
||||||
|
app.config.setdefault('FLASK_LOGGING_EXTRAS_BLUEPRINT',
|
||||||
|
(None, '<app>', '<not a request>'))
|
||||||
|
|
||||||
for kw in app.config['FLASK_LOGGING_EXTRAS_KEYWORDS']:
|
for kw in app.config['FLASK_LOGGING_EXTRAS_KEYWORDS']:
|
||||||
if kw in ['exc_info', 'extra', 'stack_info']:
|
self._check_reserved_word(kw)
|
||||||
raise ValueError(
|
|
||||||
'"{keyword}" member of FLASK_LOGGING_EXTRAS_KEYWORDS is '
|
self._check_reserved_word(
|
||||||
'reserved for internal use.')
|
app.config['FLASK_LOGGING_EXTRAS_BLUEPRINT'][0])
|
||||||
|
|
||||||
self._valid_keywords = app.config['FLASK_LOGGING_EXTRAS_KEYWORDS']
|
self._valid_keywords = app.config['FLASK_LOGGING_EXTRAS_KEYWORDS']
|
||||||
|
(
|
||||||
|
self._blueprint_var,
|
||||||
|
self._blueprint_app,
|
||||||
|
self._blueprint_norequest,
|
||||||
|
) = app.config['FLASK_LOGGING_EXTRAS_BLUEPRINT']
|
||||||
|
|
||||||
|
|
||||||
def register_logger_class(cls=FlaskExtraLogger):
|
def register_logger_class(cls=FlaskExtraLogger):
|
||||||
|
2
setup.py
2
setup.py
@ -8,7 +8,7 @@ Flask-Logging-Extras provides extra logging functionality for Flask apps.
|
|||||||
from setuptools import setup
|
from setuptools import setup
|
||||||
|
|
||||||
setup(name='Flask-Logging-Extras',
|
setup(name='Flask-Logging-Extras',
|
||||||
version='0.0.1',
|
version='0.1.0',
|
||||||
url='https://github.com/gergelypolonkai/flask-logging-extras',
|
url='https://github.com/gergelypolonkai/flask-logging-extras',
|
||||||
license='MIT',
|
license='MIT',
|
||||||
author='Gergely Polonkai',
|
author='Gergely Polonkai',
|
||||||
|
@ -7,7 +7,7 @@ import logging
|
|||||||
import sys
|
import sys
|
||||||
from unittest import TestCase
|
from unittest import TestCase
|
||||||
|
|
||||||
from flask import Flask
|
from flask import Flask, Blueprint, current_app
|
||||||
|
|
||||||
import flask_logging_extras
|
import flask_logging_extras
|
||||||
|
|
||||||
@ -173,3 +173,59 @@ class LoggerKeywordsTestCase(TestCase):
|
|||||||
# string "<unset>" will be assigned.
|
# string "<unset>" will be assigned.
|
||||||
app.logger.info('message')
|
app.logger.info('message')
|
||||||
self.assertEqual('message [<unset>]\n', log_stream.lines[-1])
|
self.assertEqual('message [<unset>]\n', log_stream.lines[-1])
|
||||||
|
|
||||||
|
|
||||||
|
class LoggerBlueprintTestCase(TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
# Register our logger class
|
||||||
|
self.original_logger_class = logging.getLoggerClass()
|
||||||
|
flask_logging_extras.register_logger_class()
|
||||||
|
|
||||||
|
app = Flask('test_app')
|
||||||
|
self.app = app
|
||||||
|
app.config['FLASK_LOGGING_EXTRAS_BLUEPRINT'] = (
|
||||||
|
'bp', '<app>', '<norequest>')
|
||||||
|
app.logger.init_app(app)
|
||||||
|
|
||||||
|
fmt = '%(bp)s %(message)s'
|
||||||
|
self.stream = ListStream()
|
||||||
|
|
||||||
|
formatter = logging.Formatter(fmt)
|
||||||
|
handler = TestingStreamHandler(stream=self.stream)
|
||||||
|
handler.setLevel(logging.DEBUG)
|
||||||
|
handler.setFormatter(formatter)
|
||||||
|
app.logger.addHandler(handler)
|
||||||
|
app.logger.setLevel(logging.DEBUG)
|
||||||
|
|
||||||
|
bp = Blueprint('test_blueprint', 'test_bpg13')
|
||||||
|
|
||||||
|
@app.route('/app')
|
||||||
|
def route_1():
|
||||||
|
current_app.logger.info('Message')
|
||||||
|
|
||||||
|
return ''
|
||||||
|
|
||||||
|
@bp.route('/blueprint')
|
||||||
|
def route_2():
|
||||||
|
current_app.logger.info('Message')
|
||||||
|
|
||||||
|
return ''
|
||||||
|
|
||||||
|
app.register_blueprint(bp)
|
||||||
|
|
||||||
|
self.client = app.test_client()
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
logging.setLoggerClass(self.original_logger_class)
|
||||||
|
|
||||||
|
def test_request_log(self):
|
||||||
|
self.client.get('/app')
|
||||||
|
self.assertEqual('<app> Message\n', self.stream.lines[-1])
|
||||||
|
|
||||||
|
page = self.client.get('/blueprint')
|
||||||
|
self.assertEqual('test_blueprint Message\n', self.stream.lines[-1])
|
||||||
|
|
||||||
|
with self.app.app_context():
|
||||||
|
current_app.logger.info('Message')
|
||||||
|
|
||||||
|
self.assertEqual('<norequest> Message\n', self.stream.lines[-1])
|
||||||
|
Loading…
Reference in New Issue
Block a user