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. | ||||
|  | ||||
| The only feature implemented now is adding extra arguments to the format | ||||
| string, like this (this example adds the category keyword to the logs: | ||||
| Extra keywords in the log formatters | ||||
| ------------------------------------ | ||||
|  | ||||
| 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 | ||||
|  | ||||
| @@ -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.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', | ||||
|                            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 | ||||
| ------------ | ||||
|  | ||||
|   | ||||
| @@ -32,6 +32,8 @@ no value is present in the message record. | ||||
|  | ||||
| import logging | ||||
|  | ||||
| from flask import has_request_context, request | ||||
|  | ||||
| __version_info__ = ('0', '0', '1') | ||||
| __version__ = '.'.join(__version_info__) | ||||
| __author__ = 'Gergely Polonkai' | ||||
| @@ -40,7 +42,8 @@ __copyright__ = '(c) 2015 GT2' | ||||
|  | ||||
| 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: | ||||
|  | ||||
| @@ -55,8 +58,14 @@ class FlaskExtraLogger(logging.getLoggerClass()): | ||||
|  | ||||
|        app = Flask(__name__) | ||||
|        app.config['FLASK_LOGGING_EXTRAS_KEYWORDS'] = {'category': '<unset>'} | ||||
|        app.config['FLASK_LOGGING_EXTRAS_BLUEPRINT'] = ('blueprint', | ||||
|                                                        '<APP>', | ||||
|                                                        '<NOT REQUEST>') | ||||
|        app.logger.init_app() | ||||
|  | ||||
|        bp = Blueprint('my_blueprint', __name__) | ||||
|        app.register_blueprint(bp) | ||||
|  | ||||
|        formatter = logging.Formatter( | ||||
|            '[%(asctime)s] [%(levelname)s] [%(category)s] %(message)s') | ||||
|        handler = logging.FileHandler('app.log', mode='a') | ||||
| @@ -68,9 +77,38 @@ class FlaskExtraLogger(logging.getLoggerClass()): | ||||
|        app.logger.info('The message', category='my category') | ||||
|  | ||||
|        # 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): | ||||
|         if 'app' in kwargs: | ||||
|             if kwargs['app'] is not None: | ||||
| @@ -84,6 +122,9 @@ class FlaskExtraLogger(logging.getLoggerClass()): | ||||
|  | ||||
|         self.app = None | ||||
|         self._valid_keywords = [] | ||||
|         self._blueprint_var = None | ||||
|         self._blueprint_app = None | ||||
|         self._blueprint_norequest = None | ||||
|  | ||||
|         super(FlaskExtraLogger, self).__init__(*args, **kwargs) | ||||
|  | ||||
| @@ -91,6 +132,13 @@ class FlaskExtraLogger(logging.getLoggerClass()): | ||||
|         if 'extra' not in kwargs: | ||||
|             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: | ||||
|             if kw in kwargs: | ||||
|                 kwargs['extra'][kw] = kwargs[kw] | ||||
| @@ -100,6 +148,13 @@ class FlaskExtraLogger(logging.getLoggerClass()): | ||||
|  | ||||
|         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): | ||||
|         """ | ||||
|         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_BLUEPRINT', | ||||
|                               (None, '<app>', '<not a request>')) | ||||
|  | ||||
|         for kw in app.config['FLASK_LOGGING_EXTRAS_KEYWORDS']: | ||||
|             if kw in ['exc_info', 'extra', 'stack_info']: | ||||
|                 raise ValueError( | ||||
|                     '"{keyword}" member of FLASK_LOGGING_EXTRAS_KEYWORDS is ' | ||||
|                     'reserved for internal use.') | ||||
|             self._check_reserved_word(kw) | ||||
|  | ||||
|         self._check_reserved_word( | ||||
|             app.config['FLASK_LOGGING_EXTRAS_BLUEPRINT'][0]) | ||||
|  | ||||
|         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): | ||||
|   | ||||
							
								
								
									
										2
									
								
								setup.py
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								setup.py
									
									
									
									
									
								
							| @@ -8,7 +8,7 @@ Flask-Logging-Extras provides extra logging functionality for Flask apps. | ||||
| from setuptools import setup | ||||
|  | ||||
| setup(name='Flask-Logging-Extras', | ||||
|       version='0.0.1', | ||||
|       version='0.1.0', | ||||
|       url='https://github.com/gergelypolonkai/flask-logging-extras', | ||||
|       license='MIT', | ||||
|       author='Gergely Polonkai', | ||||
|   | ||||
| @@ -7,7 +7,7 @@ import logging | ||||
| import sys | ||||
| from unittest import TestCase | ||||
|  | ||||
| from flask import Flask | ||||
| from flask import Flask, Blueprint, current_app | ||||
|  | ||||
| import flask_logging_extras | ||||
|  | ||||
| @@ -173,3 +173,59 @@ class LoggerKeywordsTestCase(TestCase): | ||||
|         # string "<unset>" will be assigned. | ||||
|         app.logger.info('message') | ||||
|         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