Browse Source

Add Blueprint logging support

blueprint-logging
Gergely Polonkai 2 years ago
parent
commit
583dfdd941
4 changed files with 183 additions and 10 deletions
  1. 57
    2
      README.rst
  2. 68
    6
      flask_logging_extras/__init__.py
  3. 1
    1
      setup.py
  4. 57
    1
      tests/test_logger_keywords.py

+ 57
- 2
README.rst View File

@@ -15,8 +15,16 @@ Flask-Logging-Extras
15 15
 
16 16
 Flask-Logging-Extras adds additional logging features for Flask applications.
17 17
 
18
-The only feature implemented now is adding extra arguments to the format
19
-string, like this (this example adds the category keyword to the logs:
18
+Extra keywords in the log formatters
19
+------------------------------------
20
+
21
+Adding extra keywords to the log format message is a bit tedious, as these
22
+must be supplied to the logging methods in the `extra` argument as a
23
+dictionary.
24
+
25
+Flask-Logging-Extras makes this easier, so you can add such keywords to the
26
+logging methods directly. The example adds the category keyword to the logs,
27
+and shows how to do it with and without Flask-Logging-Extras:
20 28
 
21 29
 .. code-block:: python
22 30
 
@@ -26,9 +34,56 @@ string, like this (this example adds the category keyword to the logs:
26 34
    app.config['FLASK_LOGGING_EXTRAS_KEYWORDS'] = {'category': '<unset>'}
27 35
    app.logger.init_app(app)
28 36
 
37
+   # Without Flask-Logging-Extras
38
+   current_app.logger.info('this is a the message, as usual',
39
+                           extra={'category': 'fancy-category'})
40
+   # With Flask-Logging-Extras
29 41
    current_app.logger.info('this is the message, as usual',
30 42
                            category='fancy-category')
31 43
 
44
+Logging the blueprint name
45
+--------------------------
46
+
47
+Although you can always access the blueprint name using `request.blueprint`,
48
+adding it to the logs as a new keyword is not so easy.
49
+
50
+With Flask-Logging-Extras you can specify a keyword that will hold the
51
+blueprint name in the logs, and specify what value to put there if the log
52
+doesn’t originate in a request, or it is not from a blueprint route, but
53
+from an app route.
54
+
55
+.. code-block:: python
56
+
57
+   fmt = '[%(blueprint)s] %(message)s'
58
+   # Initialize log handlers as usual, like creating a FileHandler, and
59
+   # assign fmt to it as a format string
60
+   app.config['FLASK_LOGGING_EXTRAS_BLUEPRINT'] = (
61
+       'blueprint',
62
+       '<APP>',
63
+       '<NO REQUEST>',
64
+   )
65
+
66
+   bp = Blueprint('bpname', __name__)
67
+   app.register_blueprint(bp)
68
+
69
+   @app.route('/route/1/')
70
+   def route_1():
71
+       # This will produce the log message: "[<APP>] Message"
72
+       current_app.logger.info('Message')
73
+
74
+       return 'response 1'
75
+
76
+   @bp.route('/route/2/')
77
+   def route_2():
78
+       # This will produce the log message: "[bpname] Message"
79
+       current_app.logger.info('Message')
80
+
81
+       return 'response 2'
82
+
83
+   def random_function_outside_of_a_request():
84
+       # This will produce the log message: "[<NO REQUEST>] Message"
85
+       current_app.logger.info('Message')
86
+
32 87
 Installation
33 88
 ------------
34 89
 

+ 68
- 6
flask_logging_extras/__init__.py View File

@@ -32,6 +32,8 @@ no value is present in the message record.
32 32
 
33 33
 import logging
34 34
 
35
+from flask import has_request_context, request
36
+
35 37
 __version_info__ = ('0', '0', '1')
36 38
 __version__ = '.'.join(__version_info__)
37 39
 __author__ = 'Gergely Polonkai'
@@ -40,7 +42,8 @@ __copyright__ = '(c) 2015 GT2'
40 42
 
41 43
 class FlaskExtraLogger(logging.getLoggerClass()):
42 44
     """
43
-    A logger class that is capable of adding extra keywords to log formatters
45
+    A logger class that is capable of adding extra keywords to log
46
+    formatters and logging the blueprint name
44 47
 
45 48
     Usage:
46 49
 
@@ -55,8 +58,14 @@ class FlaskExtraLogger(logging.getLoggerClass()):
55 58
 
56 59
        app = Flask(__name__)
57 60
        app.config['FLASK_LOGGING_EXTRAS_KEYWORDS'] = {'category': '<unset>'}
61
+       app.config['FLASK_LOGGING_EXTRAS_BLUEPRINT'] = ('blueprint',
62
+                                                       '<APP>',
63
+                                                       '<NOT REQUEST>')
58 64
        app.logger.init_app()
59 65
 
66
+       bp = Blueprint('my_blueprint', __name__)
67
+       app.register_blueprint(bp)
68
+
60 69
        formatter = logging.Formatter(
61 70
            '[%(asctime)s] [%(levelname)s] [%(category)s] %(message)s')
62 71
        handler = logging.FileHandler('app.log', mode='a')
@@ -68,9 +77,38 @@ class FlaskExtraLogger(logging.getLoggerClass()):
68 77
        app.logger.info('The message', category='my category')
69 78
 
70 79
        # This will produce something like this in app.log:
71
-       # [TIMESTAMP2017-01-16 08:44:48.944] [INFO] [my category] The message
80
+       # [2017-01-16 08:44:48.944] [INFO] [my category] The message
81
+
82
+       formatter = logging.Formatter('[%(blueprint)s] %(message)s')
83
+       handler = logging.FileHandler('other.log', mode='a')
84
+       handler.setFormatter(formatter)
85
+       handler.setLevel(logging.INFO)
86
+
87
+       app.logger.addHandler(handler)
88
+
89
+       @app.route('/1/')
90
+       def route_1():
91
+           # This will produce this log message:
92
+           # [<APP>] Message
93
+           current_app.logger.info('Message')
94
+
95
+           return ''
96
+
97
+       @bp.route('/2')
98
+       def route_2():
99
+           # This will produce this log message:
100
+           # [my blueprint] Message
101
+           current_app.logger.info('Message')
102
+
103
+           return ''
104
+
105
+       # This will produce this log message:
106
+       [<NOT REQUEST>] Message
107
+       app.logger.info('Message')
72 108
     """
73 109
 
110
+    _RESERVED_KEYWORDS = ('exc_info', 'extra', 'stack_info')
111
+
74 112
     def __init__(self, *args, **kwargs):
75 113
         if 'app' in kwargs:
76 114
             if kwargs['app'] is not None:
@@ -84,6 +122,9 @@ class FlaskExtraLogger(logging.getLoggerClass()):
84 122
 
85 123
         self.app = None
86 124
         self._valid_keywords = []
125
+        self._blueprint_var = None
126
+        self._blueprint_app = None
127
+        self._blueprint_norequest = None
87 128
 
88 129
         super(FlaskExtraLogger, self).__init__(*args, **kwargs)
89 130
 
@@ -91,6 +132,13 @@ class FlaskExtraLogger(logging.getLoggerClass()):
91 132
         if 'extra' not in kwargs:
92 133
             kwargs['extra'] = {}
93 134
 
135
+        # If we were asked to log the blueprint name, add it to the extra list
136
+        if self._blueprint_var is not None:
137
+            if has_request_context():
138
+                kwargs['extra'][self._blueprint_var] = request.blueprint or self._blueprint_app
139
+            else:
140
+                kwargs['extra'][self._blueprint_var] = self._blueprint_norequest
141
+
94 142
         for kw in self._valid_keywords:
95 143
             if kw in kwargs:
96 144
                 kwargs['extra'][kw] = kwargs[kw]
@@ -100,6 +148,13 @@ class FlaskExtraLogger(logging.getLoggerClass()):
100 148
 
101 149
         super(FlaskExtraLogger, self)._log(*args, **kwargs)
102 150
 
151
+    def _check_reserved_word(self, word):
152
+        if word in self._RESERVED_KEYWORDS:
153
+            raise ValueError(
154
+                '"{keyword}" cannot be used as an extra keyword, as it is '
155
+                'reserved for internal use.'
156
+                .format(keyword=word))
157
+
103 158
     def init_app(self, app):
104 159
         """
105 160
         Intialize the logger class with a Flask application
@@ -118,14 +173,21 @@ class FlaskExtraLogger(logging.getLoggerClass()):
118 173
         """
119 174
 
120 175
         app.config.setdefault('FLASK_LOGGING_EXTRAS_KEYWORDS', {})
176
+        app.config.setdefault('FLASK_LOGGING_EXTRAS_BLUEPRINT',
177
+                              (None, '<app>', '<not a request>'))
121 178
 
122 179
         for kw in app.config['FLASK_LOGGING_EXTRAS_KEYWORDS']:
123
-            if kw in ['exc_info', 'extra', 'stack_info']:
124
-                raise ValueError(
125
-                    '"{keyword}" member of FLASK_LOGGING_EXTRAS_KEYWORDS is '
126
-                    'reserved for internal use.')
180
+            self._check_reserved_word(kw)
181
+
182
+        self._check_reserved_word(
183
+            app.config['FLASK_LOGGING_EXTRAS_BLUEPRINT'][0])
127 184
 
128 185
         self._valid_keywords = app.config['FLASK_LOGGING_EXTRAS_KEYWORDS']
186
+        (
187
+            self._blueprint_var,
188
+            self._blueprint_app,
189
+            self._blueprint_norequest,
190
+        ) = app.config['FLASK_LOGGING_EXTRAS_BLUEPRINT']
129 191
 
130 192
 
131 193
 def register_logger_class(cls=FlaskExtraLogger):

+ 1
- 1
setup.py View File

@@ -8,7 +8,7 @@ Flask-Logging-Extras provides extra logging functionality for Flask apps.
8 8
 from setuptools import setup
9 9
 
10 10
 setup(name='Flask-Logging-Extras',
11
-      version='0.0.1',
11
+      version='0.1.0',
12 12
       url='https://github.com/gergelypolonkai/flask-logging-extras',
13 13
       license='MIT',
14 14
       author='Gergely Polonkai',

+ 57
- 1
tests/test_logger_keywords.py View File

@@ -7,7 +7,7 @@ import logging
7 7
 import sys
8 8
 from unittest import TestCase
9 9
 
10
-from flask import Flask
10
+from flask import Flask, Blueprint, current_app
11 11
 
12 12
 import flask_logging_extras
13 13
 
@@ -173,3 +173,59 @@ class LoggerKeywordsTestCase(TestCase):
173 173
         # string "<unset>" will be assigned.
174 174
         app.logger.info('message')
175 175
         self.assertEqual('message [<unset>]\n', log_stream.lines[-1])
176
+
177
+
178
+class LoggerBlueprintTestCase(TestCase):
179
+    def setUp(self):
180
+        # Register our logger class
181
+        self.original_logger_class = logging.getLoggerClass()
182
+        flask_logging_extras.register_logger_class()
183
+
184
+        app = Flask('test_app')
185
+        self.app = app
186
+        app.config['FLASK_LOGGING_EXTRAS_BLUEPRINT'] = (
187
+            'bp', '<app>', '<norequest>')
188
+        app.logger.init_app(app)
189
+
190
+        fmt = '%(bp)s %(message)s'
191
+        self.stream = ListStream()
192
+
193
+        formatter = logging.Formatter(fmt)
194
+        handler = TestingStreamHandler(stream=self.stream)
195
+        handler.setLevel(logging.DEBUG)
196
+        handler.setFormatter(formatter)
197
+        app.logger.addHandler(handler)
198
+        app.logger.setLevel(logging.DEBUG)
199
+
200
+        bp = Blueprint('test_blueprint', 'test_bpg13')
201
+
202
+        @app.route('/app')
203
+        def route_1():
204
+            current_app.logger.info('Message')
205
+
206
+            return ''
207
+
208
+        @bp.route('/blueprint')
209
+        def route_2():
210
+            current_app.logger.info('Message')
211
+
212
+            return ''
213
+
214
+        app.register_blueprint(bp)
215
+
216
+        self.client = app.test_client()
217
+
218
+    def tearDown(self):
219
+        logging.setLoggerClass(self.original_logger_class)
220
+
221
+    def test_request_log(self):
222
+        self.client.get('/app')
223
+        self.assertEqual('<app> Message\n', self.stream.lines[-1])
224
+
225
+        page = self.client.get('/blueprint')
226
+        self.assertEqual('test_blueprint Message\n', self.stream.lines[-1])
227
+
228
+        with self.app.app_context():
229
+            current_app.logger.info('Message')
230
+
231
+        self.assertEqual('<norequest> Message\n', self.stream.lines[-1])

Loading…
Cancel
Save