Add Blueprint logging support
This commit is contained in:
		
							
								
								
									
										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]) | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user