Skip to content

Commit dc80372

Browse files
author
Bob Bui
committed
refactor
1 parent da2b260 commit dc80372

File tree

10 files changed

+163
-107
lines changed

10 files changed

+163
-107
lines changed

CHANGELOG.md

+77-42
Original file line numberDiff line numberDiff line change
@@ -1,102 +1,137 @@
11
# Change Log
2+
23
All notable changes to this project will be documented in this file.
34

45
This project adheres to [Semantic Versioning](http://semver.org/).
56
The format is based on [Keep a Changelog](http://keepachangelog.com/).
67

8+
## 1.4.0rc1 - 2021-04-09
9+
10+
- refactor
11+
712
## 1.4.0rc - 2021-04-09
8-
- add capability to customize extraction of request, response info #68
13+
14+
- add capability to customize extraction of request, response info #68
915

1016
## 1.3.0 - 2021-03-25
11-
- add fastapi support #65
17+
18+
- add fastapi support #65
1219

1320
## 1.2.11 - 2020-11-07
14-
- fix Sanip IP information is a str not a list #63
15-
21+
22+
- fix Sanip IP information is a str not a list #63
23+
1624
## 1.2.10 - 2020-10-15
17-
- re fix #61 Using root logger + flask outside of flask request context throws RuntimeError
25+
26+
- re fix #61 Using root logger + flask outside of flask request context throws RuntimeError
1827

1928
## 1.2.9 - 2020-10-15
20-
- Fix #61 Using root logger + flask outside of flask request context throws RuntimeError
29+
30+
- Fix #61 Using root logger + flask outside of flask request context throws RuntimeError
2131

2232
## 1.2.8 - 2020-08-27
23-
- Fix #57
33+
34+
- Fix #57
2435

2536
## 1.2.7 - 2020-08-27
26-
- yanked, wrong branch released
37+
38+
- yanked, wrong branch released
2739

2840
## 1.2.6 - 2020-08-05
29-
- Fix condition for checking root logger handlers #53
41+
42+
- Fix condition for checking root logger handlers #53
3043

3144
## 1.2.5 - 2020-08-04
32-
- fix #44
33-
45+
46+
- fix #44
47+
3448
## 1.2.4 - 2020-08-03
35-
- fix #51
49+
50+
- fix #51
3651

3752
## 1.2.2 - 2020-07-14
38-
- fix #50
39-
53+
54+
- fix #50
55+
4056
## 1.2.1 - 2020-07-14
41-
- fix #49
42-
57+
58+
- fix #49
59+
4360
## 1.2.0 - 2020-04-10
44-
- fix #45
45-
- fix #46
46-
- refactoring
47-
- optimization log record
61+
62+
- fix #45
63+
- fix #46
64+
- refactoring
65+
- optimization log record
4866

4967
## 1.1.0 - 2020-04-10
50-
- Add the possibility to modify the formatter for request logs.
51-
68+
69+
- Add the possibility to modify the formatter for request logs.
70+
5271
## 1.0.4 - 2019-07-20
53-
- fix #30
72+
73+
- fix #30
5474

5575
## 1.0.3 - 2019-07-20
56-
- add missing kwargs for init_non_web
76+
77+
- add missing kwargs for init_non_web
5778

5879
## 1.0.2 - 2019-07-20
59-
- add method to support getting request logger by using method json_logging.get_request_logger(), fix #23
80+
81+
- add method to support getting request logger by using method json_logging.get_request_logger(), fix #23
6082

6183
## 1.0.1 - 2019-07-20
62-
- prevent log forging, fix #1
84+
85+
- prevent log forging, fix #1
6386

6487
## 1.0.0 - 2019-07-20
88+
6589
Breaking change:
66-
- add more specific init method for each framework
67-
- minor correct for ENABLE_JSON_LOGGING_DEBUG behaviour
68-
- update few code comments
69-
90+
91+
- add more specific init method for each framework
92+
- minor correct for ENABLE_JSON_LOGGING_DEBUG behaviour
93+
- update few code comments
94+
7095
## 0.1.2 - 2019-06-25
71-
- fix: add japanese character encoding for json logger #24
72-
96+
97+
- fix: add japanese character encoding for json logger #24
98+
7399
## 0.1.1 - 2019-05-22
74-
- fix: connexion under gunicorn has no has_request() #22
75-
100+
101+
- fix: connexion under gunicorn has no has_request() #22
102+
76103
## 0.1.0 - 2019-05-20
77-
- Add [Connexion](https://github.com/zalando/connexion) support
104+
105+
- Add [Connexion](https://github.com/zalando/connexion) support
78106

79107
## 0.0.13 - 2019-05-18
80-
- Fix #19
108+
109+
- Fix #19
81110

82111
## 0.0.12 - 2019-02-26
83-
- Fix #16
112+
113+
- Fix #16
84114

85115
## 0.0.11 - 2019-01-26
86-
- Adds functionality to customize the the JSON - change keys, values, etc.
87-
116+
117+
- Adds functionality to customize the the JSON - change keys, values, etc.
118+
88119
## 0.0.10 - 2018-12-04
89-
- Support for exception tracing exception
120+
121+
- Support for exception tracing exception
90122

91123
## 0.0.9 - 2018-10-16
92-
- Add Quart framework support
124+
125+
- Add Quart framework support
93126

94127
## 0.0.2 - 2017-12-24
95128

96129
### Changed
97-
- fixed https://github.com/thangbn/json-logging-python/pull/2
130+
131+
- fixed https://github.com/thangbn/json-logging-python/pull/2
98132

99133
## 0.0.1 - 2017-12-24
100134

101135
### Changed
136+
102137
- Initial release

README.md

-2
Original file line numberDiff line numberDiff line change
@@ -214,8 +214,6 @@ json_logging.config_root_logger()
214214
Customer JSON log formatter can be passed to init method. see examples for more detail: [non web](https://github.com/thangbn/json-logging-python/blob/master/example/custom_log_format.py),
215215
[web](https://github.com/thangbn/json-logging-python/blob/master/example/custom_log_format_request.py)
216216

217-
Custom extraction of request and response can be done by subclass **json_logging.RequestResponseDataExtractionBase**
218-
219217

220218
## 2.7 Exclude certain URl from request instrumentation
221219
Certain URL can be excluded from request instrumentation by specifying a list of regex into **init_request_instrument** method like below:

example/custom_log_format_request.py

+24-7
Original file line numberDiff line numberDiff line change
@@ -7,24 +7,41 @@
77
import json_logging
88

99

10-
class CustomRequestJSONLog(json_logging.JSONLogWebFormatter):
10+
class CustomRequestJSONLog(json_logging.JSONRequestLogFormatter):
1111
"""
1212
Customized logger
1313
"""
1414

15-
def format(self, record):
16-
json_customized_log_object = ({
17-
"type": "request",
15+
def _format_log_object(self, record, request_util):
16+
# request and response object can be extracted from record like this
17+
request = record.request_response_data._request
18+
response = record.request_response_data._response
19+
20+
json_log_object = super(CustomRequestJSONLog, self)._format_log_object(record, request_util)
21+
json_log_object.update({
1822
"customized_prop": "customized value",
19-
"correlation_id": json_logging.get_correlation_id(),
2023
})
21-
return json.dumps(json_customized_log_object)
24+
return json_log_object
25+
26+
27+
class CustomDefaultRequestResponseDTO(json_logging.DefaultRequestResponseDTO):
28+
"""
29+
custom implementation
30+
"""
31+
32+
def __init__(self, request, **kwargs):
33+
super(CustomDefaultRequestResponseDTO, self).__init__(request, **kwargs)
34+
35+
def on_request_complete(self, response):
36+
super(CustomDefaultRequestResponseDTO, self).on_request_complete(response)
37+
self.status = response.status
2238

2339

2440
app = flask.Flask(__name__)
2541
json_logging.init_flask(enable_json=True)
2642
json_logging.init_request_instrument(app, exclude_url_patterns=[r'/exclude_from_request_instrumentation'],
27-
custom_formatter=CustomRequestJSONLog)
43+
custom_formatter=CustomRequestJSONLog,
44+
request_response_data_extractor_class=CustomDefaultRequestResponseDTO)
2845

2946
# init the logger as usual
3047
logger = logging.getLogger("test logger")

json_logging/__init__.py

+38-32
Original file line numberDiff line numberDiff line change
@@ -98,44 +98,46 @@ def init_non_web(*args, **kw):
9898
__init(*args, **kw)
9999

100100

101-
class RequestResponseDataExtractorBase(dict):
101+
class RequestResponseDTOBase(dict):
102102
"""
103-
class that keep HTTP request information for request instrumentation logging
103+
Data transfer object for HTTP request & response information for request instrumentation logging
104+
Any key that is stored in this dict will be appended to final JSON log object
104105
"""
105106

106107
def __init__(self, request, **kwargs):
107-
super(RequestResponseDataExtractorBase, self).__init__(**kwargs)
108+
"""
109+
invoked when request start, where to extract any necessary information from the request object
110+
:param request: request object
111+
"""
112+
super(RequestResponseDTOBase, self).__init__(**kwargs)
113+
self._request = request
108114

109-
def update_response_status(self, response):
110-
pass
115+
def on_request_complete(self, response):
116+
"""
117+
invoked when request complete, update response information into this object, must be called before invoke request logging statement
118+
:param response: response object
119+
"""
120+
self._response = response
111121

112122

113-
class RequestResponseDataExtractor(RequestResponseDataExtractorBase):
123+
class DefaultRequestResponseDTO(RequestResponseDTOBase):
114124
"""
115125
default implementation
116126
"""
117127

118128
def __init__(self, request, **kwargs):
119-
super(RequestResponseDataExtractor, self).__init__(request, **kwargs)
129+
super(DefaultRequestResponseDTO, self).__init__(request, **kwargs)
120130
utcnow = datetime.utcnow()
121-
self.request_start = utcnow
122-
self.request = request
123-
self.request_received_at = util.iso_time_format(utcnow)
131+
self._request_start = utcnow
132+
self["request_received_at"] = util.iso_time_format(utcnow)
124133

125134
# noinspection PyAttributeOutsideInit
126-
def update_response_status(self, response):
127-
"""
128-
update response information into this object, must be called before invoke request logging statement
129-
:param response:
130-
"""
131-
response_adapter = _request_util.response_adapter
135+
def on_request_complete(self, response):
136+
super(DefaultRequestResponseDTO, self).on_request_complete(response)
132137
utcnow = datetime.utcnow()
133-
time_delta = utcnow - self.request_start
134-
self.response_time_ms = int(time_delta.total_seconds()) * 1000 + int(time_delta.microseconds / 1000)
135-
self.response_status = response_adapter.get_status_code(response)
136-
self.response_size_b = response_adapter.get_response_size(response)
137-
self.response_content_type = response_adapter.get_content_type(response)
138-
self.response_sent_at = util.iso_time_format(utcnow)
138+
time_delta = utcnow - self._request_start
139+
self["response_time_ms"] = int(time_delta.total_seconds()) * 1000 + int(time_delta.microseconds / 1000)
140+
self["response_sent_at"] = util.iso_time_format(utcnow)
139141

140142

141143
def __init(framework_name=None, custom_formatter=None, enable_json=False):
@@ -194,7 +196,7 @@ def __init(framework_name=None, custom_formatter=None, enable_json=False):
194196

195197

196198
def init_request_instrument(app=None, custom_formatter=None, exclude_url_patterns=[],
197-
request_response_data_extractor_class=RequestResponseDataExtractor):
199+
request_response_data_extractor_class=DefaultRequestResponseDTO):
198200
"""
199201
Configure the request instrumentation logging configuration for given web app. Must be called after init method
200202
@@ -212,8 +214,9 @@ def init_request_instrument(app=None, custom_formatter=None, exclude_url_pattern
212214
if not issubclass(custom_formatter, logging.Formatter):
213215
raise ValueError('custom_formatter is not subclass of logging.Formatter', custom_formatter)
214216

215-
if not issubclass(request_response_data_extractor_class, RequestResponseDataExtractorBase):
216-
raise ValueError('request_response_data_extractor_class is not subclass of json_logging.RequestInfoBase', custom_formatter)
217+
if not issubclass(request_response_data_extractor_class, RequestResponseDTOBase):
218+
raise ValueError('request_response_data_extractor_class is not subclass of json_logging.RequestInfoBase',
219+
custom_formatter)
217220

218221
configurator = _current_framework['app_request_instrumentation_configurator']()
219222
configurator.config(app, request_response_data_extractor_class, exclude_url_patterns=exclude_url_patterns)
@@ -275,10 +278,13 @@ class JSONRequestLogFormatter(BaseJSONFormatter):
275278

276279
def _format_log_object(self, record, request_util):
277280
json_log_object = super(JSONRequestLogFormatter, self)._format_log_object(record, request_util)
278-
request = record.request_info.request
279281
request_adapter = request_util.request_adapter
282+
response_adapter = _request_util.response_adapter
283+
request = record.request_response_data._request
284+
response = record.request_response_data._response
280285

281286
length = request_adapter.get_content_length(request)
287+
282288
json_log_object.update({
283289
"type": "request",
284290
"correlation_id": request_util.get_correlation_id(request),
@@ -292,13 +298,13 @@ def _format_log_object(self, record, request_util):
292298
"request_size_b": util.parse_int(length, -1),
293299
"remote_host": request_adapter.get_remote_ip(request),
294300
"remote_port": request_adapter.get_remote_port(request),
295-
"request_received_at": record.request_info.request_received_at,
296-
"response_time_ms": record.request_info.response_time_ms,
297-
"response_status": record.request_info.response_status,
298-
"response_size_b": record.request_info.response_size_b,
299-
"response_content_type": record.request_info.response_content_type,
300-
"response_sent_at": record.request_info.response_sent_at
301+
"response_status": response_adapter.get_status_code(response),
302+
"response_size_b": response_adapter.get_response_size(response),
303+
"response_content_type": response_adapter.get_content_type(response),
301304
})
305+
306+
json_log_object.update(record.request_response_data)
307+
302308
return json_log_object
303309

304310

json_logging/framework/connexion/__init__.py

+5-5
Original file line numberDiff line numberDiff line change
@@ -51,14 +51,14 @@ def config(self, app, request_response_data_extractor_class, exclude_url_pattern
5151
@app.app.before_request
5252
def before_request():
5353
if is_not_match_any_pattern(_current_request.path, exclude_url_patterns):
54-
g.request_info = request_response_data_extractor_class(_current_request)
54+
g.request_response_data = request_response_data_extractor_class(_current_request)
5555

5656
@app.app.after_request
5757
def after_request(response):
58-
if hasattr(g, 'request_info'):
59-
request_info = g.request_info
60-
request_info.update_response_status(response)
61-
self.request_logger.info("", extra={'request_info': request_info})
58+
if hasattr(g, 'request_response_data'):
59+
request_response_data = g.request_response_data
60+
request_response_data.on_request_complete(response)
61+
self.request_logger.info("", extra={'request_response_data': request_response_data})
6262
return response
6363

6464

json_logging/framework/fastapi/implementation.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,11 @@ async def dispatch(self, request: Request, call_next: RequestResponseEndpoint) -
3131
if not log_request:
3232
return await call_next(request)
3333

34-
request_info = _request_config_class(request)
34+
request_response_data = _request_config_class(request)
3535
response = await call_next(request)
36-
request_info.update_response_status(response)
36+
request_response_data.on_request_complete(response)
3737
self.request_logger.info(
38-
"", extra={"request_info": request_info, "type": "request"}
38+
"", extra={"request_response_data": request_response_data, "type": "request"}
3939
)
4040
return response
4141

0 commit comments

Comments
 (0)