Source code for flask_logging_extras

# -*- coding: utf-8 -*-
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.

"""
Extra functionality for Flask logging

Flask-Logging-Extras is a Flask extension that plugs into the logging
mechanism of Flask applications.

Flask-Logging-Extras requires you to set FLASK_LOGGING_EXTRAS_KEYWORDS to a
dictionary value, where the dictionary key is a the key you can use in the
log message format, and the value is a default value that is substituted if
no value is present in the message record.
"""

from importlib import import_module
import logging

from flask import has_request_context, request, current_app, has_app_context

__version_info__ = ('2', '0', '0')
__version__ = '.'.join(__version_info__)
__author__ = 'Gergely Polonkai'
__license__ = 'MIT'
__copyright__ = '(c) 2015-2018 Benchmarked.games'


def _import_by_string(fqn):
    try:
        mod_name, var_name = fqn.rsplit('.', 1)
    except ValueError:
        mod_name = fqn
        var_name = None

    mod = import_module(mod_name)

    if var_name is None:
        return mod

    try:
        var = getattr(mod, var_name)
    except AttributeError:
        raise ImportError('{var_name} not found in {mod_name}'.format(var_name=var_name, mod_name=mod_name))

    return var


[docs]class FlaskExtraLoggerFormatter(logging.Formatter): """A log formatter class that is capable of adding extra keywords to log formatters and logging the blueprint name Usage: .. code-block:: python import logging from logging.config import dictConfig dictConfig({ 'formatters': { 'extras': { 'format': '[%(asctime)s] [%(levelname)s] [%(category)s] [%(bp)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'] = { 'BLUEPRINT': { 'FORMAT_NAME': 'bp', 'APP_BLUEPRINT': '<app>', 'NO_REQUEST_BLUEPRINT': '<not a request>', }, 'RESOLVERS': { 'categoy': '<unset>', 'client': 'log_helper.get_client', }, } bp = Blueprint('my_blueprint', __name__) app.register_blueprint(bp) logger = logging.getLogger('my_app') # This will produce something like this in app.log: # [2018-05-02 12:44:48.944] [INFO] [my category] [<not request>] The message logger.info('The message', extra=dict(category='my category')) # This will produce something like this in app.log: # [2018-05-02 12:44:48.944] [INFO] [None] [<not request>] The message logger.info('The message') @app.route('/1') def route_1(): # 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 a log message like this: # [2018-05-02 12:44:48.944] [INFO] [None] [my_blueprint] Message logger.info('Message') return '' # This will produce a log message like this: # [2018-05-02 12:44:48.944] [INFO] [<unset>] [<NOT REQUEST>] Message logger.info('Message') """ def __init__(self, *args, **kwargs): super(FlaskExtraLoggerFormatter, self).__init__(*args, **kwargs) self.resolvers = {} self.bp_var = None self.bp_app = None self.bp_noreq = None self.__inited = False
[docs] def init_app(self, app): """Initialise the formatter with app-specific values from ``app``’s configuration """ if self.__inited: return config = app.config.get( 'FLASK_LOGGING_EXTRAS', {}) blueprint_config = config.get('BLUEPRINT', {}) self.bp_var = blueprint_config.get('FORMAT_NAME', 'blueprint') self.bp_app = blueprint_config.get('APP_BLUEPRINT', '<app>') self.bp_noreq = blueprint_config.get('NO_REQUEST_BLUEPRINT', '<not a request>') for var_name, resolver_fqn in config.get('RESOLVERS', {}).items(): if resolver_fqn is None: resolver = None else: try: resolver = _import_by_string(resolver_fqn) except ImportError: resolver = resolver_fqn self.resolvers[var_name] = resolver self.__inited = True
[docs] def format(self, record): blueprint = None if has_app_context(): self.init_app(current_app) if self.bp_var and has_request_context(): blueprint = request.blueprint or self.bp_app if self.bp_var and self.bp_var not in record.__dict__: setattr(record, self.bp_var, blueprint or self.bp_noreq) for var_name, resolver in self.resolvers.items(): if var_name in record.__dict__: continue setattr(record, var_name, resolver() if callable(resolver) else resolver) return super(FlaskExtraLoggerFormatter, self).format(record)