Skip to content

Commit 3b449f4

Browse files
committed
adding backed logger feature ...
1 parent f25ca95 commit 3b449f4

File tree

13 files changed

+315
-25
lines changed

13 files changed

+315
-25
lines changed

logmetrics_sdk/__init__.py

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +0,0 @@
1-
# -*- coding: utf-8 -*-
2-
3-
"""Top-level package for logmetrics_sdk."""
4-
5-
__author__ = """devxchange.io"""
6-
__email__ = 'info@devxchange.io'
7-
__version__ = '1.0.0'

logmetrics_sdk/common/__init__.py

Whitespace-only changes.
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
2+
class LogMetricsConfig:
3+
4+
enable_logmetrics = True
5+
enable_frontend_request = True
6+
enable_frontend_response = True
7+
enable_backend = True
8+
enable_backend_request = True
9+
enable_backend_response = True
10+
11+
@classmethod
12+
def set_internal_state(cls, enable_logmetrics=True,
13+
enable_frontend_request=True, enable_frontend_response=True,
14+
enable_backend=True, enable_backend_request=True, enable_backend_response=True):
15+
cls.enable_logmetrics = enable_logmetrics
16+
cls.enable_frontend_request = enable_frontend_request
17+
cls.enable_frontend_response = enable_frontend_response
18+
cls.enable_backend = enable_backend
19+
cls.enable_backend_request = enable_backend_request
20+
cls.enable_backend_response = enable_backend_response
21+
Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
message: dict = {
2+
'TrackingID': str,
23
'MessageType': str,
34
'Duration': int,
45
'Host': str,
@@ -13,7 +14,7 @@
1314
'StartDateTime': str,
1415
'EndDateTime': str,
1516
'Aspects': str,
16-
'Query': str,
17+
'QueryParams': str,
1718
'Path': str,
1819
'RequestBody': str,
1920
'ResponseBody': str,
@@ -29,9 +30,7 @@
2930

3031

3132
def clear_message():
32-
"""
33-
34-
"""
33+
message['TrackingID'] = None
3534
message['MessageType'] = "LOGMETRICS_MESSAGE"
3635
message['Duration'] = 0
3736
message['Host'] = None
@@ -45,7 +44,8 @@ def clear_message():
4544
message['ServiceVersion'] = None
4645
message['StartDateTime'] = None
4746
message['EndDateTime'] = None
48-
message['Query'] = None
47+
message['Aspects'] = None
48+
message['QueryParams'] = None
4949
message['Path'] = None
5050
message['RequestBody'] = None
5151
message['ResponseBody'] = None

logmetrics_sdk/logmetrics.py

Lines changed: 37 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,32 +5,49 @@
55
from functools import wraps
66
from json import JSONDecodeError
77
from typing import Dict
8+
import uuid
89

910
from logmetrics_sdk.utils.importer import LazyImport
10-
from logmetrics_sdk.utils.message import message, clear_message
11+
from logmetrics_sdk.common.message import message, clear_message
1112
from logmetrics_sdk.utils.constants import *
1213
from logmetrics_sdk.utils.timer import TimeUtil, preferred_clock
14+
from logmetrics_sdk.patch.patch import patch_backend
15+
from logmetrics_sdk.common.logmetrics_config import LogMetricsConfig
1316

1417
flask = LazyImport('flask')
1518
logging.basicConfig(level=logging.INFO)
1619

1720

18-
def LogMetrics(_fn: object = None) -> object:
19-
"""
20-
"""
21+
def LogMetrics(_fn=None, *, enable_logmetrics=True, enable_frontend_request=True,enable_frontend_response=True,
22+
enable_backend=True, enable_backend_request=True, enable_backend_response = True):
23+
24+
LogMetricsConfig.set_internal_state(enable_logmetrics,
25+
enable_frontend_request,
26+
enable_frontend_response,
27+
enable_backend,
28+
enable_backend_request,
29+
enable_backend_response)
30+
patch_backend()
2131

2232
def new_func(fn):
2333
@wraps(fn)
2434
def wrap(*args, **kwargs):
2535
start = preferred_clock()
2636
clear_message()
37+
message['TrackingID'] = str(uuid.uuid4())
2738
message['Action'] = FRONTEND
2839
message['MessageType'] = LOGMETRICS_MESSAGE
2940
message['StartDateTime'] = str(datetime.datetime.now())
3041

3142
if flask.has_request_context():
32-
message['Query'] = flask.request.query_string
33-
message['RequestBody'] = flask.request.data
43+
44+
if enable_frontend_request:
45+
if flask.request.data:
46+
message['RequestBody'] = flask.request.data
47+
48+
if flask.request.query_string:
49+
message['QueryParams'] = flask.request.query_string
50+
3451
message['ClientHost'] = flask.request.remote_addr
3552
message['Host'] = flask.request.host
3653
message['HttpMethod'] = flask.request.method
@@ -48,10 +65,12 @@ def wrap(*args, **kwargs):
4865
status = int(response.status_code)
4966
message['HttpStatus'] = status
5067
message['Fault'] = status not in (200, 300)
51-
message['ResponseBody'] = json.dumps(data)
68+
if enable_frontend_response and data:
69+
message['ResponseBody'] = json.dumps(data)
5270
message['ContentType'] = response.content_type
5371
except (JSONDecodeError, TypeError):
54-
message['ResponseBody'] = json.dumps(data)
72+
if enable_frontend_response and data:
73+
message['ResponseBody'] = json.dumps(data)
5574

5675
elif isinstance(return_value, Dict):
5776
if 'statusCode' in return_value:
@@ -60,9 +79,11 @@ def wrap(*args, **kwargs):
6079
message['Fault'] = not (status == 200 or status == 300)
6180
if 'body' in return_value:
6281
try:
63-
message['ResponseBody'] = json.dumps(json.loads(return_value['body']))
82+
if enable_frontend_response:
83+
message['ResponseBody'] = json.dumps(json.loads(return_value['body']))
6484
except (JSONDecodeError, TypeError):
65-
message['ResponseBody'] = return_value['body']
85+
if enable_frontend_response:
86+
message['ResponseBody'] = return_value['body']
6687
else:
6788
try:
6889
status = int(getattr(return_value, 'status_code', 200))
@@ -72,7 +93,12 @@ def wrap(*args, **kwargs):
7293
pass
7394
message['Duration'] = TimeUtil.get_duration(start, end)
7495

75-
logging.info(message)
96+
if enable_logmetrics:
97+
try:
98+
logging.info(json.dumps(message))
99+
except Exception as ex:
100+
logging.error(f'{ex}')
101+
76102
return return_value
77103

78104
wrap._original = fn

logmetrics_sdk/patch/__init__.py

Whitespace-only changes.

logmetrics_sdk/patch/patch.py

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
2+
import logging
3+
import importlib
4+
5+
log = logging.getLogger(__name__)
6+
7+
8+
SUPPORTED_MODULES = (
9+
'requests'
10+
)
11+
12+
_PATCHED_MODULES = set()
13+
14+
15+
def patch_backend():
16+
patch(SUPPORTED_MODULES, raise_errors=False)
17+
18+
19+
def patch(modules_to_patch, raise_errors=True):
20+
if isinstance(modules_to_patch, str):
21+
modules_to_patch = [modules_to_patch]
22+
modules = set()
23+
for module_to_patch in modules_to_patch:
24+
modules.add(module_to_patch)
25+
for m in modules:
26+
_patch_module(m, raise_errors)
27+
28+
29+
def _patch_module(module_to_patch, raise_errors=True):
30+
try:
31+
_patch(module_to_patch)
32+
except KeyError:
33+
if raise_errors:
34+
raise
35+
log.debug('failed to patch module %s', module_to_patch)
36+
37+
38+
def _patch(module_to_patch):
39+
40+
path = 'logmetrics_sdk.patch.%s' % module_to_patch
41+
42+
if module_to_patch in _PATCHED_MODULES:
43+
log.debug('%s already patched', module_to_patch)
44+
return
45+
46+
imported_module = importlib.import_module(path)
47+
imported_module.patch()
48+
49+
_PATCHED_MODULES.add(module_to_patch)
50+
log.info('successfully patched module %s', module_to_patch)
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
from .patch import patch_requests_session as patch
2+
3+
__all__ = ['patch']
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
2+
import json
3+
import logging
4+
import requests.sessions
5+
import chardet
6+
import datetime
7+
8+
from functools import wraps
9+
from urllib.parse import urlsplit
10+
from json import JSONDecodeError
11+
from xml.etree.ElementTree import ParseError
12+
from requests.structures import CaseInsensitiveDict
13+
14+
from logmetrics_sdk.common.logmetrics_config import LogMetricsConfig
15+
from logmetrics_sdk.common.message import message
16+
from logmetrics_sdk.utils.parse import parse_qs
17+
from logmetrics_sdk.utils.constants import *
18+
from logmetrics_sdk.utils.json_util import to_object
19+
20+
21+
CONTENT_TYPE = 'content-type'
22+
23+
log = logging.getLogger(__name__)
24+
25+
26+
def patch_requests_session():
27+
_patch_request()
28+
29+
30+
def _patch_request():
31+
32+
def time_requests_session_request(f):
33+
34+
@wraps(f)
35+
def wrap(self, method, url, **kwargs):
36+
start_time = str(datetime.datetime.now())
37+
collect_payload_for_host = True
38+
parsed_url = urlsplit(url)
39+
query_params = (requests.sessions.merge_setting(kwargs.get('params'), self.params)
40+
or parse_qs(parsed_url.query))
41+
request_data = kwargs.get('data') or kwargs.get('json')
42+
43+
_message = {
44+
'TrackingID': message.get('TrackingID'),
45+
'MessageType': LOGMETRICS_MESSAGE,
46+
'FrontendMethod': message.get('FrontendMethod', None),
47+
'ApplicationName': message.get('ApplicationName', None),
48+
'BackendServiceName': None,
49+
'BackendSystem': parsed_url.netloc,
50+
'BackendMethod': parsed_url.path,
51+
'HttpMethod': method,
52+
'Action': BACKEND,
53+
'StartDateTime': start_time
54+
}
55+
56+
if collect_payload_for_host and query_params:
57+
_message['QueryParams'] = query_params
58+
59+
if request_data:
60+
request_headers = requests.sessions.merge_setting(
61+
kwargs.get('headers'), self.headers, dict_class=CaseInsensitiveDict
62+
)
63+
if collect_payload_for_host and LogMetricsConfig.enable_backend_request:
64+
payload = _process_payload_data(
65+
payload=request_data,
66+
payload_type='request',
67+
content_type=request_headers.get(CONTENT_TYPE)
68+
)
69+
_message.update(payload)
70+
response = None
71+
try:
72+
response = f(self, method, url, **kwargs)
73+
except Exception as e:
74+
_message['Fault'] = True
75+
_message['ErrorMessage'] = getattr(e, 'message', str(e))
76+
_message['ErrorCode'] = type(e).__name__
77+
_message['HttpStatus'] = None
78+
raise e
79+
finally:
80+
if response:
81+
if response.encoding is None:
82+
response.encoding = chardet.detect(response.content)['encoding']
83+
if collect_payload_for_host and LogMetricsConfig.enable_backend_response:
84+
payload = _process_payload_data(
85+
payload=response.text,
86+
payload_type='response',
87+
content_type=response.headers.get(CONTENT_TYPE))
88+
_message.update(payload)
89+
_message['HttpStatus'] = response.status_code
90+
_message['Duration'] = 0
91+
if LogMetricsConfig.enable_backend_response:
92+
logging.info(json.dumps(_message))
93+
return response
94+
return wrap
95+
96+
requests.sessions.Session.request = time_requests_session_request(
97+
requests.sessions.Session.request
98+
)
99+
100+
101+
def _process_payload_data(payload: str, payload_type: str, content_type: str):
102+
103+
_message = {}
104+
metric_fieldname = payload_type.capitalize() + 'Body'
105+
content_is_json, content_is_xml, content_is_plaintext = False, False, False
106+
107+
if content_type:
108+
content_is_json = '/json' in content_type
109+
content_is_xml = '/xml' in content_type
110+
content_is_plaintext = 'text/' in content_type
111+
112+
if content_is_json:
113+
try:
114+
py_obj_data = to_object(payload)
115+
_message['ResponseBody'] = json.dumps(py_obj_data)
116+
except JSONDecodeError:
117+
_message[metric_fieldname] = payload.strip()
118+
_message['ErrorMessage'] = ('Error occurred while parsing JSON data for '
119+
+ metric_fieldname)
120+
_message['ErrorCode'] = 'JSONParseError'
121+
122+
elif content_is_xml:
123+
try:
124+
_message[metric_fieldname] = "{\"XML\": true}"
125+
except ParseError:
126+
_message[metric_fieldname] = payload.strip()
127+
_message['ErrorMessage'] = ('Error occurred while parsing XML data for '
128+
+ metric_fieldname)
129+
_message['ErrorCode'] = 'XMLParseError'
130+
131+
elif content_is_plaintext:
132+
_message[metric_fieldname] = payload.strip()
133+
134+
else:
135+
try:
136+
py_obj_data = to_object(payload)
137+
_message[metric_fieldname] = json.dumps(py_obj_data)
138+
except JSONDecodeError:
139+
try:
140+
message[metric_fieldname] = "{\"XML\": true}"
141+
142+
except ParseError:
143+
_message[metric_fieldname] = payload.strip()
144+
_message['ErrorMessage'] = ('Error occurred while parsing data as JSON & XML for '
145+
+ metric_fieldname)
146+
_message['ErrorCode'] = 'ParseError'
147+
148+
return _message

0 commit comments

Comments
 (0)