Skip to content

Commit 0de7605

Browse files
author
Bob Bui
committed
refactor: added documentation and change classes' name
1 parent fb48771 commit 0de7605

File tree

14 files changed

+169
-133
lines changed

14 files changed

+169
-133
lines changed

README.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -313,10 +313,10 @@ method:
313313

314314
Class | Description | Mandatory
315315
--- | --- | ---
316-
RequestAdapter | Helper class help to extract logging-relevant information from HTTP request object | yes
317-
ResponseAdapter | Helper class help to extract logging-relevant information from HTTP response object | yes
318-
FrameworkConfigurator | Class to perform logging configuration for given framework as needed | no
319-
AppRequestInstrumentationConfigurator | Class to perform request instrumentation logging configuration | no
316+
BaseRequestInfoExtractor | Helper class help to extract logging-relevant information from HTTP request object | yes
317+
BaseResponseInfoExtractor | Helper class help to extract logging-relevant information from HTTP response object | yes
318+
BaseFrameworkConfigurator | Class to perform logging configuration for given framework as needed | no
319+
BaseAppRequestInstrumentationConfigurator | Class to perform request instrumentation logging configuration | no
320320

321321
Take a look at [**json_logging/base_framework.py**](json_logging/framework_base.py), [**
322322
json_logging.flask**](json_logging/framework/flask) and [**json_logging.sanic**](json_logging/framework/sanic) packages

example/custom_log_format_request.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ def on_request_complete(self, response):
4343
json_logging.init_flask(enable_json=True)
4444
json_logging.init_request_instrument(app, exclude_url_patterns=[r'/exclude_from_request_instrumentation'],
4545
custom_formatter=CustomRequestJSONLog,
46-
request_response_data_extractor_class=CustomDefaultRequestResponseDTO)
46+
request_response_dto_class=CustomDefaultRequestResponseDTO)
4747

4848
# init the logger as usual
4949
logger = logging.getLogger("test logger")

json_logging/__init__.py

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,9 @@
77
from json_logging import util
88
from json_logging.dto import RequestResponseDTOBase, DefaultRequestResponseDTO
99
from json_logging.formatters import JSONRequestLogFormatter, JSONLogFormatter, JSONLogWebFormatter
10-
from json_logging.framework_base import RequestAdapter, ResponseAdapter, AppRequestInstrumentationConfigurator, \
11-
FrameworkConfigurator
10+
from json_logging.framework_base import BaseRequestInfoExtractor, BaseResponseInfoExtractor, \
11+
BaseAppRequestInstrumentationConfigurator, \
12+
BaseFrameworkConfigurator
1213
from json_logging.util import get_library_logger, is_env_var_toggle
1314

1415
CORRELATION_ID_GENERATOR = uuid.uuid1
@@ -64,6 +65,11 @@ def config_root_logger():
6465

6566

6667
def init_non_web(*args, **kw):
68+
"""
69+
Initialize for a non HTTP application
70+
:param args:
71+
:param kw:
72+
"""
6773
__init(*args, **kw)
6874

6975

@@ -106,8 +112,9 @@ def __init(framework_name=None, custom_formatter=None, enable_json=False):
106112
global _request_util
107113

108114
_current_framework = _framework_support_map[framework_name]
109-
_request_util = util.RequestUtil(request_adapter_class=_current_framework['request_adapter_class'],
110-
response_adapter_class=_current_framework['response_adapter_class'])
115+
_request_util = util.RequestUtil(
116+
request_info_extractor_class=_current_framework['request_info_extractor_class'],
117+
response_info_extractor_class=_current_framework['response_info_extractor_class'])
111118

112119
if ENABLE_JSON_LOGGING and _current_framework['app_configurator'] is not None:
113120
_current_framework['app_configurator']().config()
@@ -126,15 +133,15 @@ def __init(framework_name=None, custom_formatter=None, enable_json=False):
126133

127134

128135
def init_request_instrument(app=None, custom_formatter=None, exclude_url_patterns=[],
129-
request_response_data_extractor_class=DefaultRequestResponseDTO):
136+
request_response_dto_class=DefaultRequestResponseDTO):
130137
"""
131138
Configure the request instrumentation logging configuration for given web app. Must be called after init method
132139
133140
If **custom_formatter** is passed, it will use this formatter over the default.
134141
135142
:param app: current web application instance
136143
:param custom_formatter: formatter to override default JSONRequestLogFormatter.
137-
:param request_response_data_extractor_class: request_response_data_extractor_class to override default json_logging.RequestResponseDataExtractor.
144+
:param request_response_dto_class: request_response_dto_class to override default json_logging.RequestResponseDataExtractor.
138145
"""
139146

140147
if _current_framework is None or _current_framework == '-':
@@ -144,12 +151,12 @@ def init_request_instrument(app=None, custom_formatter=None, exclude_url_pattern
144151
if not issubclass(custom_formatter, logging.Formatter):
145152
raise ValueError('custom_formatter is not subclass of logging.Formatter', custom_formatter)
146153

147-
if not issubclass(request_response_data_extractor_class, RequestResponseDTOBase):
148-
raise ValueError('request_response_data_extractor_class is not subclass of json_logging.RequestInfoBase',
154+
if not issubclass(request_response_dto_class, RequestResponseDTOBase):
155+
raise ValueError('request_response_dto_class is not subclass of json_logging.RequestInfoBase',
149156
custom_formatter)
150157

151158
configurator = _current_framework['app_request_instrumentation_configurator']()
152-
configurator.config(app, request_response_data_extractor_class, exclude_url_patterns=exclude_url_patterns)
159+
configurator.config(app, request_response_dto_class, exclude_url_patterns=exclude_url_patterns)
153160

154161
formatter = custom_formatter if custom_formatter else JSONRequestLogFormatter
155162
request_logger = configurator.request_logger

json_logging/dto.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
class RequestResponseDTOBase(dict):
77
"""
8-
Data transfer object for HTTP request & response information for request instrumentation logging
8+
Data transfer object (DTO) for HTTP request & response information for each request instrumentation logging
99
Any key that is stored in this dict will be appended to final JSON log object
1010
Served as base class for any actual RequestResponseDTO implementation
1111
"""

json_logging/formatters.py

Lines changed: 64 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
import json_logging
77

8-
# The list contains all the attributes listed in
8+
# The list contains all the attributes listed in that will not be overwritten by custom extra props
99
# http://docs.python.org/library/logging.html#logrecord-attributes
1010
RECORD_ATTR_SKIP_LIST = [
1111
'asctime', 'created', 'exc_info', 'exc_text', 'filename', 'args',
@@ -23,13 +23,18 @@
2323
basestring = str
2424

2525
if sys.version_info < (3, 0):
26-
EASY_TYPES = (basestring, bool, dict, float, int, list, type(None))
26+
EASY_SERIALIZABLE_TYPES = (basestring, bool, dict, float, int, list, type(None))
2727
else:
2828
RECORD_ATTR_SKIP_LIST.append('stack_info')
29-
EASY_TYPES = (str, bool, dict, float, int, list, type(None))
29+
EASY_SERIALIZABLE_TYPES = (str, bool, dict, float, int, list, type(None))
3030

3131

3232
def _sanitize_log_msg(record):
33+
"""
34+
Sanitize log message to make sure we can print out properly formatted JSON string
35+
:param record: log object
36+
:return: sanitized log object
37+
"""
3338
return record.getMessage().replace('\n', '_').replace('\r', '_').replace('\t', '_')
3439

3540

@@ -49,31 +54,43 @@ def __init__(self, *args, **kw):
4954
self.base_object_common["component_instance_idx"] = json_logging.COMPONENT_INSTANCE_INDEX
5055

5156
def format(self, record):
57+
"""
58+
Format the specified record as text. Overriding default python logging implementation
59+
"""
5260
log_object = self._format_log_object(record, request_util=json_logging._request_util)
5361
return json_logging.JSON_SERIALIZER(log_object)
5462

5563
def _format_log_object(self, record, request_util):
5664
utcnow = datetime.utcnow()
65+
5766
base_obj = {
5867
"written_at": json_logging.util.iso_time_format(utcnow),
5968
"written_ts": json_logging.util.epoch_nano_second(utcnow),
6069
}
70+
6171
base_obj.update(self.base_object_common)
6272
# Add extra fields
6373
base_obj.update(self._get_extra_fields(record))
74+
6475
return base_obj
6576

6677
def _get_extra_fields(self, record):
78+
"""
79+
Get the dict of custom extra fields passed to the log statement
80+
:param record: log record
81+
:return:
82+
"""
6783
fields = {}
6884

6985
if record.args:
7086
fields['msg'] = record.msg
7187

7288
for key, value in record.__dict__.items():
7389
if key not in RECORD_ATTR_SKIP_LIST:
74-
if isinstance(value, EASY_TYPES):
90+
if isinstance(value, EASY_SERIALIZABLE_TYPES):
7591
fields[key] = value
7692
else:
93+
# try to cast it to a string representation
7794
fields[key] = repr(value)
7895

7996
# Always add 'props' to the root of the log, assumes props is a dict
@@ -83,46 +100,9 @@ def _get_extra_fields(self, record):
83100
return fields
84101

85102

86-
class JSONRequestLogFormatter(BaseJSONFormatter):
87-
"""
88-
Formatter for HTTP request instrumentation logging
89-
"""
90-
91-
def _format_log_object(self, record, request_util):
92-
json_log_object = super(JSONRequestLogFormatter, self)._format_log_object(record, request_util)
93-
request_adapter = request_util.request_adapter
94-
response_adapter = json_logging._request_util.response_adapter
95-
request = record.request_response_data._request
96-
response = record.request_response_data._response
97-
98-
length = request_adapter.get_content_length(request)
99-
100-
json_log_object.update({
101-
"type": "request",
102-
"correlation_id": request_util.get_correlation_id(request),
103-
"remote_user": request_adapter.get_remote_user(request),
104-
"request": request_adapter.get_path(request),
105-
"referer": request_adapter.get_http_header(request, 'referer', json_logging.EMPTY_VALUE),
106-
"x_forwarded_for": request_adapter.get_http_header(request, 'x-forwarded-for', json_logging.EMPTY_VALUE),
107-
"protocol": request_adapter.get_protocol(request),
108-
"method": request_adapter.get_method(request),
109-
"remote_ip": request_adapter.get_remote_ip(request),
110-
"request_size_b": json_logging.util.parse_int(length, -1),
111-
"remote_host": request_adapter.get_remote_ip(request),
112-
"remote_port": request_adapter.get_remote_port(request),
113-
"response_status": response_adapter.get_status_code(response),
114-
"response_size_b": response_adapter.get_response_size(response),
115-
"response_content_type": response_adapter.get_content_type(response),
116-
})
117-
118-
json_log_object.update(record.request_response_data)
119-
120-
return json_log_object
121-
122-
123103
class JSONLogFormatter(BaseJSONFormatter):
124104
"""
125-
Formatter for non-web application log
105+
Default formatter for non-web application log
126106
"""
127107

128108
def get_exc_fields(self, record):
@@ -141,6 +121,7 @@ def format_exception(cls, exc_info):
141121

142122
def _format_log_object(self, record, request_util):
143123
json_log_object = super(JSONLogFormatter, self)._format_log_object(record, request_util)
124+
144125
json_log_object.update({
145126
"msg": _sanitize_log_msg(record),
146127
"type": "log",
@@ -159,13 +140,53 @@ def _format_log_object(self, record, request_util):
159140

160141
class JSONLogWebFormatter(JSONLogFormatter):
161142
"""
162-
Formatter for web application log
143+
Formatter for web application log with correlation-id
163144
"""
164145

165146
def _format_log_object(self, record, request_util):
166147
json_log_object = super(JSONLogWebFormatter, self)._format_log_object(record, request_util)
148+
167149
if "correlation_id" not in json_log_object:
168150
json_log_object.update({
169151
"correlation_id": request_util.get_correlation_id(within_formatter=True),
170152
})
171153
return json_log_object
154+
155+
156+
class JSONRequestLogFormatter(BaseJSONFormatter):
157+
"""
158+
Formatter for HTTP request instrumentation logging
159+
"""
160+
161+
def _format_log_object(self, record, request_util):
162+
json_log_object = super(JSONRequestLogFormatter, self)._format_log_object(record, request_util)
163+
164+
request_adapter = request_util.request_adapter
165+
response_adapter = json_logging._request_util.response_adapter
166+
167+
request = record.request_response_data._request
168+
response = record.request_response_data._response
169+
170+
length = request_adapter.get_content_length(request)
171+
172+
json_log_object.update({
173+
"type": "request",
174+
"correlation_id": request_util.get_correlation_id(request),
175+
"remote_user": request_adapter.get_remote_user(request),
176+
"request": request_adapter.get_path(request),
177+
"referer": request_adapter.get_http_header(request, 'referer', json_logging.EMPTY_VALUE),
178+
"x_forwarded_for": request_adapter.get_http_header(request, 'x-forwarded-for', json_logging.EMPTY_VALUE),
179+
"protocol": request_adapter.get_protocol(request),
180+
"method": request_adapter.get_method(request),
181+
"remote_ip": request_adapter.get_remote_ip(request),
182+
"request_size_b": json_logging.util.parse_int(length, -1),
183+
"remote_host": request_adapter.get_remote_ip(request),
184+
"remote_port": request_adapter.get_remote_port(request),
185+
"response_status": response_adapter.get_status_code(response),
186+
"response_size_b": response_adapter.get_response_size(response),
187+
"response_content_type": response_adapter.get_content_type(response),
188+
})
189+
190+
json_log_object.update(record.request_response_data)
191+
192+
return json_log_object

json_logging/framework/connexion/__init__.py

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@
55
import json_logging
66
import json_logging.framework
77
from json_logging import JSONLogWebFormatter
8-
from json_logging.framework_base import AppRequestInstrumentationConfigurator, RequestAdapter, ResponseAdapter
8+
from json_logging.framework_base import BaseAppRequestInstrumentationConfigurator, BaseRequestInfoExtractor, \
9+
BaseResponseInfoExtractor
910
from json_logging.util import is_not_match_any_pattern
1011

1112

@@ -30,8 +31,8 @@ def is_connexion_present():
3031
_connexion.g = g
3132

3233

33-
class ConnexionAppRequestInstrumentationConfigurator(AppRequestInstrumentationConfigurator):
34-
def config(self, app, request_response_data_extractor_class, exclude_url_patterns=[]):
34+
class ConnexionAppRequestInstrumentationConfigurator(BaseAppRequestInstrumentationConfigurator):
35+
def config(self, app, request_response_dto_class, exclude_url_patterns=[]):
3536
if not is_connexion_present():
3637
raise RuntimeError("connexion is not available in system runtime")
3738
from flask.app import Flask
@@ -51,7 +52,7 @@ def config(self, app, request_response_data_extractor_class, exclude_url_pattern
5152
@app.app.before_request
5253
def before_request():
5354
if is_not_match_any_pattern(_current_request.path, exclude_url_patterns):
54-
g.request_response_data = request_response_data_extractor_class(_current_request)
55+
g.request_response_data = request_response_dto_class(_current_request)
5556

5657
@app.app.after_request
5758
def after_request(response):
@@ -62,7 +63,7 @@ def after_request(response):
6263
return response
6364

6465

65-
class ConnexionRequestAdapter(RequestAdapter):
66+
class ConnexionRequestInfoExtractor(BaseRequestInfoExtractor):
6667
@staticmethod
6768
def get_request_class_type():
6869
raise NotImplementedError
@@ -120,7 +121,7 @@ def get_remote_port(self, request):
120121
return request.environ.get('REMOTE_PORT')
121122

122123

123-
class ConnexionResponseAdapter(ResponseAdapter):
124+
class ConnexionResponseInfoExtractor(BaseResponseInfoExtractor):
124125
def get_status_code(self, response):
125126
return response.status_code
126127

json_logging/framework/fastapi/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,5 @@ def is_fastapi_present():
1010

1111

1212
if is_fastapi_present():
13-
from .implementation import FastAPIAppRequestInstrumentationConfigurator, FastAPIRequestAdapter, FastAPIResponseAdapter
13+
from .implementation import FastAPIAppRequestInstrumentationConfigurator, FastAPIRequestInfoExtractor, \
14+
FastAPIResponseInfoExtractor

json_logging/framework/fastapi/implementation.py

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22

33
import json_logging
44
import json_logging.framework
5-
from json_logging.framework_base import AppRequestInstrumentationConfigurator, RequestAdapter, ResponseAdapter
5+
from json_logging.framework_base import BaseAppRequestInstrumentationConfigurator, BaseRequestInfoExtractor, \
6+
BaseResponseInfoExtractor
67

78
from json_logging.util import is_not_match_any_pattern
89

@@ -40,13 +41,13 @@ async def dispatch(self, request: Request, call_next: RequestResponseEndpoint) -
4041
return response
4142

4243

43-
class FastAPIAppRequestInstrumentationConfigurator(AppRequestInstrumentationConfigurator):
44-
def config(self, app, request_response_data_extractor_class, exclude_url_patterns=[]):
44+
class FastAPIAppRequestInstrumentationConfigurator(BaseAppRequestInstrumentationConfigurator):
45+
def config(self, app, request_response_dto_class, exclude_url_patterns=[]):
4546
if not isinstance(app, fastapi.FastAPI):
4647
raise RuntimeError("app is not a valid fastapi.FastAPI instance")
4748

4849
global _request_config_class
49-
_request_config_class = request_response_data_extractor_class
50+
_request_config_class = request_response_dto_class
5051

5152
# Disable standard logging
5253
logging.getLogger('uvicorn.access').disabled = True
@@ -57,7 +58,7 @@ def config(self, app, request_response_data_extractor_class, exclude_url_pattern
5758
app.add_middleware(JSONLoggingASGIMiddleware, exclude_url_patterns=exclude_url_patterns)
5859

5960

60-
class FastAPIRequestAdapter(RequestAdapter):
61+
class FastAPIRequestInfoExtractor(BaseRequestInfoExtractor):
6162
@staticmethod
6263
def get_request_class_type():
6364
return starlette.requests.Request
@@ -116,7 +117,7 @@ def get_remote_port(self, request: starlette.requests.Request):
116117
return request.client.port
117118

118119

119-
class FastAPIResponseAdapter(ResponseAdapter):
120+
class FastAPIResponseInfoExtractor(BaseResponseInfoExtractor):
120121
def get_status_code(self, response: starlette.responses.Response):
121122
return response.status_code
122123

0 commit comments

Comments
 (0)