Compare commits
	
		
			12 Commits
		
	
	
		
			blueprint-
			...
			master
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 45b397f74e | |||
| ddb469a8d2 | |||
| 7469083f0b | |||
| 63895ea46e | |||
| f1459dac40 | |||
| e2e2937615 | |||
| 07dc35cec2 | |||
| 8195d11c8c | |||
| a51fbc33ea | |||
| 41bcdc4b0b | |||
| b9c3128315 | |||
| fbebc6ca26 | 
@@ -1,12 +1,11 @@
 | 
			
		||||
language: python
 | 
			
		||||
python:
 | 
			
		||||
    - "3.5"
 | 
			
		||||
    - "3.6"
 | 
			
		||||
sudo: false
 | 
			
		||||
env:
 | 
			
		||||
    - TOXENV=py27
 | 
			
		||||
    - TOXENV=py33
 | 
			
		||||
    - TOXENV=py34
 | 
			
		||||
    - TOXENV=py35
 | 
			
		||||
    - TOXENV=py36
 | 
			
		||||
install:
 | 
			
		||||
    - pip install -U pip
 | 
			
		||||
    - pip install -U Flask tox coverage codecov
 | 
			
		||||
 
 | 
			
		||||
@@ -1 +1 @@
 | 
			
		||||
Flask-Sphinx-Themes==1.0.1
 | 
			
		||||
Flask-Sphinx-Themes==1.0.2
 | 
			
		||||
 
 | 
			
		||||
@@ -60,9 +60,9 @@ author = 'Gergely Polonkai'
 | 
			
		||||
# built documents.
 | 
			
		||||
#
 | 
			
		||||
# The short X.Y version.
 | 
			
		||||
version = '0.1'
 | 
			
		||||
version = '1.0'
 | 
			
		||||
# The full version, including alpha/beta/rc tags.
 | 
			
		||||
release = '0.1.1'
 | 
			
		||||
release = '1.0.0'
 | 
			
		||||
 | 
			
		||||
# The language for content autogenerated by Sphinx. Refer to documentation
 | 
			
		||||
# for a list of supported languages.
 | 
			
		||||
 
 | 
			
		||||
@@ -32,17 +32,17 @@ no value is present in the message record.
 | 
			
		||||
 | 
			
		||||
import logging
 | 
			
		||||
 | 
			
		||||
from flask import has_request_context, request
 | 
			
		||||
from flask import has_request_context, request, current_app, has_app_context
 | 
			
		||||
 | 
			
		||||
__version_info__ = ('0', '0', '1')
 | 
			
		||||
__version_info__ = ('1', '0', '0')
 | 
			
		||||
__version__ = '.'.join(__version_info__)
 | 
			
		||||
__author__ = 'Gergely Polonkai'
 | 
			
		||||
__license__ = 'MIT'
 | 
			
		||||
__copyright__ = '(c) 2015 GT2'
 | 
			
		||||
__copyright__ = '(c) 2015-2018 Benchmarked.games'
 | 
			
		||||
 | 
			
		||||
class FlaskExtraLogger(logging.getLoggerClass()):
 | 
			
		||||
    """
 | 
			
		||||
    A logger class that is capable of adding extra keywords to log
 | 
			
		||||
 | 
			
		||||
class FlaskExtraLoggerFormatter(logging.Formatter):
 | 
			
		||||
    """A log formatter class that is capable of adding extra keywords to log
 | 
			
		||||
    formatters and logging the blueprint name
 | 
			
		||||
 | 
			
		||||
    Usage:
 | 
			
		||||
@@ -50,174 +50,92 @@ class FlaskExtraLogger(logging.getLoggerClass()):
 | 
			
		||||
    .. code-block:: python
 | 
			
		||||
 | 
			
		||||
       import logging
 | 
			
		||||
       from logging.config import dictConfig
 | 
			
		||||
 | 
			
		||||
       from flask_logging_extras import register_logger_class
 | 
			
		||||
 | 
			
		||||
       # This must be done before the app is initialized!
 | 
			
		||||
       register_logger_class(cls=FlaskExtraLogger)
 | 
			
		||||
       dictConfig({
 | 
			
		||||
           'formatters': {
 | 
			
		||||
               'extras': {
 | 
			
		||||
                   'format': '[%(asctime)s] [%(levelname)s] [%(category)s] [%(blueprint)s] %(message)s',
 | 
			
		||||
               },
 | 
			
		||||
           },
 | 
			
		||||
           'handlers': {
 | 
			
		||||
               'extras_handler': {
 | 
			
		||||
                   'class': 'logging.FileHandler',
 | 
			
		||||
                   'args': ('app.log', 'a'),
 | 
			
		||||
                   'formatter': 'extras',
 | 
			
		||||
                   'level': 'INFO',
 | 
			
		||||
               },
 | 
			
		||||
           },
 | 
			
		||||
           'loggers': {
 | 
			
		||||
               'my_app': {
 | 
			
		||||
                   'handlers': ['extras_handler'],
 | 
			
		||||
               }
 | 
			
		||||
           },
 | 
			
		||||
       })
 | 
			
		||||
 | 
			
		||||
       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')
 | 
			
		||||
       handler.setFormatter(formatter)
 | 
			
		||||
       handler.setLevel(logging.INFO)
 | 
			
		||||
 | 
			
		||||
       app.logger.addHandler(handler)
 | 
			
		||||
 | 
			
		||||
       app.logger.info('The message', category='my category')
 | 
			
		||||
       logger = logging.getLogger('my_app')
 | 
			
		||||
 | 
			
		||||
       # This will produce something like this in app.log:
 | 
			
		||||
       # [2017-01-16 08:44:48.944] [INFO] [my category] The message
 | 
			
		||||
       # [2018-05-02 12:44:48.944] [INFO] [my category] [<NOT REQUEST>] The message
 | 
			
		||||
       logger.info('The message', extra=dict(category='my category'))
 | 
			
		||||
 | 
			
		||||
       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/')
 | 
			
		||||
       @app.route('/1')
 | 
			
		||||
       def route_1():
 | 
			
		||||
           # This will produce this log message:
 | 
			
		||||
           # [<APP>] Message
 | 
			
		||||
           current_app.logger.info('Message')
 | 
			
		||||
           # This will produce a log message like this:
 | 
			
		||||
           # [2018-05-02 12:44:48.944] [INFO] [<unset>] [<APP>] Message
 | 
			
		||||
           logger.info('Message')
 | 
			
		||||
 | 
			
		||||
           return ''
 | 
			
		||||
 | 
			
		||||
       @bp.route('/2')
 | 
			
		||||
       def route_2():
 | 
			
		||||
           # This will produce this log message:
 | 
			
		||||
           # [my blueprint] Message
 | 
			
		||||
           current_app.logger.info('Message')
 | 
			
		||||
           # This will produce a log message like this:
 | 
			
		||||
           # [2018-05-02 12:44:48.944] [INFO] [<unset>] [my_blueprint] Message
 | 
			
		||||
           logger.info('Message')
 | 
			
		||||
 | 
			
		||||
           return ''
 | 
			
		||||
 | 
			
		||||
       # This will produce this log message:
 | 
			
		||||
       [<NOT REQUEST>] Message
 | 
			
		||||
       app.logger.info('Message')
 | 
			
		||||
       # This will produce a log message like this:
 | 
			
		||||
       # [2018-05-02 12:44:48.944] [INFO] [<unset>] [<NOT REQUEST>] Message
 | 
			
		||||
       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:
 | 
			
		||||
                raise TypeError(
 | 
			
		||||
                    "Cannot initialise {classname} with an app.  See the"
 | 
			
		||||
                    "documentation of Flask-Logging-Extras for more info."
 | 
			
		||||
                    .format(classname=self.__class__.__name__))
 | 
			
		||||
            else:
 | 
			
		||||
                # If app is None, treat it as if it wasn’t there
 | 
			
		||||
                del(kwargs['app'])
 | 
			
		||||
 | 
			
		||||
        self.app = None
 | 
			
		||||
        self._valid_keywords = []
 | 
			
		||||
        self._blueprint_var = None
 | 
			
		||||
        self._blueprint_app = None
 | 
			
		||||
        self._blueprint_norequest = None
 | 
			
		||||
 | 
			
		||||
        super(FlaskExtraLogger, self).__init__(*args, **kwargs)
 | 
			
		||||
 | 
			
		||||
    def _log(self, *args, **kwargs):
 | 
			
		||||
        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]
 | 
			
		||||
                del(kwargs[kw])
 | 
			
		||||
            else:
 | 
			
		||||
                kwargs['extra'][kw] = self._valid_keywords[kw]
 | 
			
		||||
 | 
			
		||||
        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
 | 
			
		||||
 | 
			
		||||
        The class reads its necessary configuration from the config of this
 | 
			
		||||
        application.
 | 
			
		||||
 | 
			
		||||
        If the application doesn’t call this, or doesn’t have the
 | 
			
		||||
        `FLASK_LOGGING_EXTRAS_KEYWORDS` in its config, no extra
 | 
			
		||||
        functionality will be added.
 | 
			
		||||
 | 
			
		||||
        :param app: a Flask application
 | 
			
		||||
        :type app: Flask
 | 
			
		||||
        :raises ValueError: if the app tries to register a keyword that is
 | 
			
		||||
                             reserved for internal use
 | 
			
		||||
    def _collect_keywords(self, record):
 | 
			
		||||
        """Collect all valid keywords and add them to the log record if not present
 | 
			
		||||
        """
 | 
			
		||||
 | 
			
		||||
        app.config.setdefault('FLASK_LOGGING_EXTRAS_KEYWORDS', {})
 | 
			
		||||
        app.config.setdefault('FLASK_LOGGING_EXTRAS_BLUEPRINT',
 | 
			
		||||
                              (None, '<app>', '<not a request>'))
 | 
			
		||||
        # We assume we do have an active app context here
 | 
			
		||||
        defaults = current_app.config.get('FLASK_LOGGING_EXTRAS_KEYWORDS', {})
 | 
			
		||||
 | 
			
		||||
        for kw in app.config['FLASK_LOGGING_EXTRAS_KEYWORDS']:
 | 
			
		||||
            self._check_reserved_word(kw)
 | 
			
		||||
        for keyword, default in defaults.items():
 | 
			
		||||
            if keyword not in record.__dict__:
 | 
			
		||||
                setattr(record, keyword, default)
 | 
			
		||||
 | 
			
		||||
        self._check_reserved_word(
 | 
			
		||||
            app.config['FLASK_LOGGING_EXTRAS_BLUEPRINT'][0])
 | 
			
		||||
    def format(self, record):
 | 
			
		||||
        bp_var, bp_app, bp_noreq = ('blueprint', '<app>', '<not a request>')
 | 
			
		||||
        blueprint = None
 | 
			
		||||
 | 
			
		||||
        self._valid_keywords = app.config['FLASK_LOGGING_EXTRAS_KEYWORDS']
 | 
			
		||||
        (
 | 
			
		||||
            self._blueprint_var,
 | 
			
		||||
            self._blueprint_app,
 | 
			
		||||
            self._blueprint_norequest,
 | 
			
		||||
        ) = app.config['FLASK_LOGGING_EXTRAS_BLUEPRINT']
 | 
			
		||||
        if has_app_context():
 | 
			
		||||
            self._collect_keywords(record)
 | 
			
		||||
 | 
			
		||||
            bp_var, bp_app, bp_noreq = current_app.config.get('FLASK_LOGGING_EXTRAS_BLUEPRINT',
 | 
			
		||||
                                                              (bp_var, bp_app, bp_noreq))
 | 
			
		||||
 | 
			
		||||
def register_logger_class(cls=FlaskExtraLogger):
 | 
			
		||||
    """
 | 
			
		||||
    Register a new logger class
 | 
			
		||||
            if bp_var and has_request_context():
 | 
			
		||||
                blueprint = request.blueprint or bp_app
 | 
			
		||||
 | 
			
		||||
    It is effectively a wrapper around `logging.setLoggerClass()`, with an
 | 
			
		||||
    added check to make sure the class can be used as a logger.
 | 
			
		||||
        if bp_var and bp_var not in record.__dict__:
 | 
			
		||||
            blueprint = blueprint or bp_noreq
 | 
			
		||||
 | 
			
		||||
    To use the extra features of the logger class in a Flask app, you must
 | 
			
		||||
    call it before the app is instantiated.
 | 
			
		||||
            setattr(record, bp_var, blueprint)
 | 
			
		||||
 | 
			
		||||
    This function returns the logger class that was the default before
 | 
			
		||||
    calling.  This might be useful if you only want to use `cls` in the
 | 
			
		||||
    Flask app object, but not anywhere else in your code.  In this case,
 | 
			
		||||
    simply call `register_logger_class()` again with the return value from
 | 
			
		||||
    the first invocation.
 | 
			
		||||
 | 
			
		||||
    :param cls: a logger class to register as the default one
 | 
			
		||||
    :type cls: class(logging.Logger)
 | 
			
		||||
    :returns: the old default logger class
 | 
			
		||||
    :rtype: class
 | 
			
		||||
    :raises TypeError: if the class is not a subclass of `logging.Logger`
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    if not issubclass(cls, logging.Logger):
 | 
			
		||||
        raise TypeError(
 | 
			
		||||
            "The logger class must be a subclass of logging.Logger!")
 | 
			
		||||
 | 
			
		||||
    old_class = logging.getLoggerClass()
 | 
			
		||||
    logging.setLoggerClass(cls)
 | 
			
		||||
 | 
			
		||||
    return old_class
 | 
			
		||||
        return super(FlaskExtraLoggerFormatter, self).format(record)
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										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.1.0',
 | 
			
		||||
      version='1.0.0',
 | 
			
		||||
      url='https://github.com/gergelypolonkai/flask-logging-extras',
 | 
			
		||||
      license='MIT',
 | 
			
		||||
      author='Gergely Polonkai',
 | 
			
		||||
 
 | 
			
		||||
@@ -1,9 +1,9 @@
 | 
			
		||||
# -*- coding: utf-8 -*-
 | 
			
		||||
"""
 | 
			
		||||
Unit tests for Flask-Logging-Extras
 | 
			
		||||
"""Unit tests for Flask-Logging-Extras
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
import logging
 | 
			
		||||
from logging.config import dictConfig
 | 
			
		||||
import sys
 | 
			
		||||
from unittest import TestCase
 | 
			
		||||
 | 
			
		||||
@@ -11,203 +11,124 @@ from flask import Flask, Blueprint, current_app
 | 
			
		||||
 | 
			
		||||
import flask_logging_extras
 | 
			
		||||
 | 
			
		||||
class ListStream(object):
 | 
			
		||||
    """
 | 
			
		||||
    Primitive stream that stores its input lines in a list
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
class ListHandler(logging.StreamHandler):
 | 
			
		||||
    def __init__(self, *args, **kwargs):
 | 
			
		||||
        self.lines = []
 | 
			
		||||
        super(ListHandler, self).__init__(*args, **kwargs)
 | 
			
		||||
 | 
			
		||||
        super(ListStream, self).__init__(*args, **kwargs)
 | 
			
		||||
        self.logs = []
 | 
			
		||||
 | 
			
		||||
    def write(self, data):
 | 
			
		||||
        if len(self.lines) == 0 or self.lines[-1].endswith('\n'):
 | 
			
		||||
            self.lines.append(data)
 | 
			
		||||
        else:
 | 
			
		||||
            self.lines[-1] += data
 | 
			
		||||
    def emit(self, record):
 | 
			
		||||
        try:
 | 
			
		||||
            msg = self.format(record)
 | 
			
		||||
        except (KeyError, IndexError) as exc:
 | 
			
		||||
            msg = (exc.__class__.__name__, str(exc))
 | 
			
		||||
 | 
			
		||||
        self.logs.append(msg)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TestingStreamHandler(logging.StreamHandler):
 | 
			
		||||
    def handleError(self, record):
 | 
			
		||||
        exc, exc_msg, trace = sys.exc_info()
 | 
			
		||||
def configure_loggers(extra_var):
 | 
			
		||||
    dictConfig({
 | 
			
		||||
        'version': 1,
 | 
			
		||||
        'disable_existing_loggers': True,
 | 
			
		||||
        'formatters': {
 | 
			
		||||
            'selftest': {
 | 
			
		||||
                'class': 'flask_logging_extras.FlaskExtraLoggerFormatter',
 | 
			
		||||
                'format': '%(message)s %({extra_var})s'.format(extra_var=extra_var),
 | 
			
		||||
            },
 | 
			
		||||
        },
 | 
			
		||||
        'handlers': {
 | 
			
		||||
            'selftest': {
 | 
			
		||||
                'class': 'test_logger_keywords.ListHandler',
 | 
			
		||||
                'formatter': 'selftest',
 | 
			
		||||
            },
 | 
			
		||||
        },
 | 
			
		||||
        'loggers': {
 | 
			
		||||
            'selftest': {
 | 
			
		||||
                'handlers': ['selftest'],
 | 
			
		||||
                'level': 'INFO',
 | 
			
		||||
            },
 | 
			
		||||
        },
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
        super(logging.StreamHandler, self).handleError(record)
 | 
			
		||||
    formatter = flask_logging_extras.FlaskExtraLoggerFormatter(
 | 
			
		||||
        fmt='%(message)s %({extra_var})s'.format(extra_var=extra_var))
 | 
			
		||||
 | 
			
		||||
        raise(exc(exc_msg))
 | 
			
		||||
    logger = logging.getLogger('selftest')
 | 
			
		||||
    handlers = [handler for handler in logger.handlers if isinstance(handler, ListHandler)]
 | 
			
		||||
    handler = handlers[0]
 | 
			
		||||
    # TODO: Why doesn’t this happen automatically in Python 2.7?
 | 
			
		||||
    handler.setFormatter(formatter)
 | 
			
		||||
 | 
			
		||||
    return logger, handler
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class LoggerKeywordsTestCase(TestCase):
 | 
			
		||||
    def setUp(self):
 | 
			
		||||
        self.original_logger_class = logging.getLoggerClass()
 | 
			
		||||
        self.logger, self.handler = configure_loggers('extra_keyword')
 | 
			
		||||
        self.app = Flask('test_app')
 | 
			
		||||
        self.app.config['FLASK_LOGGING_EXTRAS_KEYWORDS'] = {
 | 
			
		||||
            'extra_keyword': 'placeholder',
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    def tearDown(self):
 | 
			
		||||
        logging.setLoggerClass(self.original_logger_class)
 | 
			
		||||
        # Make sure we don’t try to log the current blueprint for this test case
 | 
			
		||||
        self.app.config['FLASK_LOGGING_EXTRAS_BLUEPRINT'] = (None, '', '')
 | 
			
		||||
 | 
			
		||||
    def test_logger_registration(self):
 | 
			
		||||
        class NotLoggerClass(object):
 | 
			
		||||
            pass
 | 
			
		||||
    def test_keywords_no_app_ctx(self):
 | 
			
		||||
        """With the current formatter, the last log line must be a just the message
 | 
			
		||||
        """
 | 
			
		||||
 | 
			
		||||
        with self.assertRaises(TypeError):
 | 
			
		||||
            flask_logging_extras.register_logger_class(cls=NotLoggerClass)
 | 
			
		||||
        self.logger.info('message')
 | 
			
		||||
        self.assertIn(('KeyError', "'extra_keyword'"), self.handler.logs)
 | 
			
		||||
 | 
			
		||||
        original_logging_class = logging.getLoggerClass()
 | 
			
		||||
 | 
			
		||||
        old_class = flask_logging_extras.register_logger_class()
 | 
			
		||||
 | 
			
		||||
        # If no class is specified, FlaskExtraLogger should be registered
 | 
			
		||||
        self.assertEqual(logging.getLoggerClass(),
 | 
			
		||||
                         flask_logging_extras.FlaskExtraLogger)
 | 
			
		||||
        # The return value of this function should be the old default
 | 
			
		||||
        self.assertEqual(original_logging_class, old_class)
 | 
			
		||||
 | 
			
		||||
        class MyLogger(logging.Logger):
 | 
			
		||||
            pass
 | 
			
		||||
 | 
			
		||||
        # Calling register_logger_class() with any subclass of
 | 
			
		||||
        # logging.Logger should succeed
 | 
			
		||||
        flask_logging_extras.register_logger_class(cls=MyLogger)
 | 
			
		||||
 | 
			
		||||
    def test_logger_class(self):
 | 
			
		||||
        # If app is present during __init__, and is not None, we should get
 | 
			
		||||
        # a TypeError
 | 
			
		||||
        with self.assertRaises(TypeError):
 | 
			
		||||
            flask_logging_extras.FlaskExtraLogger('test_logger',
 | 
			
		||||
                                                  app='test value')
 | 
			
		||||
 | 
			
		||||
        # If app is present, but is None, no exception should be raised
 | 
			
		||||
        flask_logging_extras.FlaskExtraLogger('test_logger', app=None)
 | 
			
		||||
 | 
			
		||||
    def test_logger_class_init_app(self):
 | 
			
		||||
        logger = flask_logging_extras.FlaskExtraLogger('test')
 | 
			
		||||
        app = Flask('test_app')
 | 
			
		||||
 | 
			
		||||
        logger.init_app(app)
 | 
			
		||||
 | 
			
		||||
        self.assertEqual({}, logger._valid_keywords)
 | 
			
		||||
 | 
			
		||||
        app.config['FLASK_LOGGING_EXTRAS_KEYWORDS'] = {'exc_info': None}
 | 
			
		||||
 | 
			
		||||
        with self.assertRaises(ValueError):
 | 
			
		||||
            logger.init_app(app)
 | 
			
		||||
 | 
			
		||||
        app.config['FLASK_LOGGING_EXTRAS_KEYWORDS'] = {'my_keyword': '<unset>'}
 | 
			
		||||
 | 
			
		||||
        logger.init_app(app)
 | 
			
		||||
        self.assertEqual({'my_keyword': '<unset>'}, logger._valid_keywords)
 | 
			
		||||
 | 
			
		||||
        # Without registration first, app.logger should not be of class
 | 
			
		||||
        # FlaskExtraLogger (even though logger.init_app() succeeded without
 | 
			
		||||
        # it.)
 | 
			
		||||
        self.assertNotIsInstance(app.logger,
 | 
			
		||||
                                 flask_logging_extras.FlaskExtraLogger)
 | 
			
		||||
 | 
			
		||||
        flask_logging_extras.register_logger_class()
 | 
			
		||||
 | 
			
		||||
        # Even after registratiiion, existing apps are (obviously) not
 | 
			
		||||
        # tampered with
 | 
			
		||||
 | 
			
		||||
        self.assertNotIsInstance(app.logger,
 | 
			
		||||
                                 flask_logging_extras.FlaskExtraLogger)
 | 
			
		||||
 | 
			
		||||
        app = Flask('test_app')
 | 
			
		||||
 | 
			
		||||
        # Newly created apps, though, should be made with this class
 | 
			
		||||
        self.assertIsInstance(app.logger,
 | 
			
		||||
                              flask_logging_extras.FlaskExtraLogger)
 | 
			
		||||
 | 
			
		||||
    def _create_app_with_logger(self, fmt, keywords=[]):
 | 
			
		||||
        app = Flask('test_app')
 | 
			
		||||
 | 
			
		||||
        self.assertIsInstance(app.logger,
 | 
			
		||||
                              flask_logging_extras.FlaskExtraLogger)
 | 
			
		||||
 | 
			
		||||
        app.config['FLASK_LOGGING_EXTRAS_KEYWORDS'] = keywords
 | 
			
		||||
        app.logger.init_app(app)
 | 
			
		||||
 | 
			
		||||
        formatter = logging.Formatter(fmt)
 | 
			
		||||
        stream = ListStream()
 | 
			
		||||
 | 
			
		||||
        handler = TestingStreamHandler(stream=stream)
 | 
			
		||||
        handler.setLevel(logging.DEBUG)
 | 
			
		||||
        handler.setFormatter(formatter)
 | 
			
		||||
 | 
			
		||||
        app.logger.addHandler(handler)
 | 
			
		||||
        app.logger.setLevel(logging.DEBUG)
 | 
			
		||||
 | 
			
		||||
        return app, stream
 | 
			
		||||
 | 
			
		||||
    def test_keywords(self):
 | 
			
		||||
        # Register our logger class as the default
 | 
			
		||||
        old_logger_class = flask_logging_extras.register_logger_class()
 | 
			
		||||
 | 
			
		||||
        app, log_stream = self._create_app_with_logger('%(message)s')
 | 
			
		||||
 | 
			
		||||
        app.logger.info('message')
 | 
			
		||||
        # With the current formatter, the last log line must be a just the
 | 
			
		||||
        # message
 | 
			
		||||
        self.assertEqual(log_stream.lines[-1], 'message\n')
 | 
			
		||||
 | 
			
		||||
        app, log_stream = self._create_app_with_logger(
 | 
			
		||||
            '%(message)s %(extra_keyword)s')
 | 
			
		||||
 | 
			
		||||
        # If we don’t register 'extra_keyword' in the app config, we get a
 | 
			
		||||
    def test_keywords_app_ctx_assign_value(self):
 | 
			
		||||
        """If we don’t register 'extra_keyword' in the app config, we get a
 | 
			
		||||
        # KeyError.  We set logging.raiseExceptions to False here; the
 | 
			
		||||
        # reason is that this doesn’t mean an actual exception is raised,
 | 
			
		||||
        # but our TestingStreamHandler does that for us (which we need for
 | 
			
		||||
        # testing purposes)
 | 
			
		||||
        old_raiseExceptions = logging.raiseExceptions
 | 
			
		||||
        logging.raiseExceptions = False
 | 
			
		||||
        with self.assertRaises(KeyError):
 | 
			
		||||
            app.logger.info('message')
 | 
			
		||||
        logging.raiseExceptions = old_raiseExceptions
 | 
			
		||||
        """
 | 
			
		||||
 | 
			
		||||
        app, log_stream = self._create_app_with_logger(
 | 
			
		||||
            '%(message)s [%(extra_keyword)s]',
 | 
			
		||||
            keywords={'extra_keyword': '<unset>'})
 | 
			
		||||
        with self.app.app_context():
 | 
			
		||||
            self.logger.info('message', extra=dict(extra_keyword='test'))
 | 
			
		||||
 | 
			
		||||
        app.logger.info('message', extra_keyword='test')
 | 
			
		||||
        self.assertEqual('message [test]\n', log_stream.lines[-1])
 | 
			
		||||
        self.assertIn('message test', self.handler.logs)
 | 
			
		||||
 | 
			
		||||
        # If we don’t provide a value for a registered extra keyword, the
 | 
			
		||||
    def test_keywords_app_ctx_no_value(self):
 | 
			
		||||
        """If we don’t provide a value for a registered extra keyword, the
 | 
			
		||||
        # string "<unset>" will be assigned.
 | 
			
		||||
        app.logger.info('message')
 | 
			
		||||
        self.assertEqual('message [<unset>]\n', log_stream.lines[-1])
 | 
			
		||||
        """
 | 
			
		||||
 | 
			
		||||
        with self.app.app_context():
 | 
			
		||||
            self.logger.info('message')
 | 
			
		||||
 | 
			
		||||
        self.assertIn('message placeholder', self.handler.logs)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
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)
 | 
			
		||||
        app.config['FLASK_LOGGING_EXTRAS_BLUEPRINT'] = ('bp', '<app>', '<norequest>')
 | 
			
		||||
 | 
			
		||||
        fmt = '%(bp)s %(message)s'
 | 
			
		||||
        self.stream = ListStream()
 | 
			
		||||
        configure_loggers('bp')
 | 
			
		||||
        self.logger = logging.getLogger('selftest')
 | 
			
		||||
        handlers = [handler for handler in self.logger.handlers if isinstance(handler, ListHandler)]
 | 
			
		||||
        self.handler = handlers[0]
 | 
			
		||||
 | 
			
		||||
        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')
 | 
			
		||||
        bp = Blueprint('test_blueprint', 'test_bp')
 | 
			
		||||
 | 
			
		||||
        @app.route('/app')
 | 
			
		||||
        def route_1():
 | 
			
		||||
            current_app.logger.info('Message')
 | 
			
		||||
            self.logger.info('Message')
 | 
			
		||||
 | 
			
		||||
            return ''
 | 
			
		||||
 | 
			
		||||
        @bp.route('/blueprint')
 | 
			
		||||
        def route_2():
 | 
			
		||||
            current_app.logger.info('Message')
 | 
			
		||||
            self.logger.info('Message')
 | 
			
		||||
 | 
			
		||||
            return ''
 | 
			
		||||
 | 
			
		||||
@@ -215,17 +136,16 @@ class LoggerBlueprintTestCase(TestCase):
 | 
			
		||||
 | 
			
		||||
        self.client = app.test_client()
 | 
			
		||||
 | 
			
		||||
    def tearDown(self):
 | 
			
		||||
        logging.setLoggerClass(self.original_logger_class)
 | 
			
		||||
 | 
			
		||||
    def test_request_log(self):
 | 
			
		||||
    def test_blueprint_log_no_blueprint(self):
 | 
			
		||||
        self.client.get('/app')
 | 
			
		||||
        self.assertEqual('<app> Message\n', self.stream.lines[-1])
 | 
			
		||||
        self.assertIn('Message <app>', self.handler.logs)
 | 
			
		||||
 | 
			
		||||
        page = self.client.get('/blueprint')
 | 
			
		||||
        self.assertEqual('test_blueprint Message\n', self.stream.lines[-1])
 | 
			
		||||
    def test_blueprint_log_blueprint(self):
 | 
			
		||||
        self.client.get('/blueprint')
 | 
			
		||||
        self.assertIn('Message test_blueprint', self.handler.logs)
 | 
			
		||||
 | 
			
		||||
    def test_blueprint_log_no_request(self):
 | 
			
		||||
        with self.app.app_context():
 | 
			
		||||
            current_app.logger.info('Message')
 | 
			
		||||
            self.logger.info('Message')
 | 
			
		||||
 | 
			
		||||
        self.assertEqual('<norequest> Message\n', self.stream.lines[-1])
 | 
			
		||||
        self.assertIn('Message <norequest>', self.handler.logs)
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user