sharedkernel 2.5.1__py3-none-any.whl → 2.5.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- sharedkernel/logger/log_decorator.py +64 -0
- sharedkernel/logger/log_dto.py +23 -0
- sharedkernel/logger/log_enums.py +12 -0
- sharedkernel/logger/log_info.py +5 -0
- sharedkernel/logger/log_middlewares.py +38 -0
- sharedkernel/logger/logger_service.py +155 -0
- {sharedkernel-2.5.1.dist-info → sharedkernel-2.5.2.dist-info}/METADATA +3 -1
- {sharedkernel-2.5.1.dist-info → sharedkernel-2.5.2.dist-info}/RECORD +10 -4
- {sharedkernel-2.5.1.dist-info → sharedkernel-2.5.2.dist-info}/WHEEL +0 -0
- {sharedkernel-2.5.1.dist-info → sharedkernel-2.5.2.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import inspect
|
|
2
|
+
from functools import wraps
|
|
3
|
+
from typing import Callable, Type
|
|
4
|
+
from asyncio import iscoroutinefunction
|
|
5
|
+
import os
|
|
6
|
+
|
|
7
|
+
from sharedkernel.logger.logger_service import LoggerService
|
|
8
|
+
|
|
9
|
+
LOG_ENABLE = True if os.getenv("LOG_ENABLE", "True").lower() == "true" else False
|
|
10
|
+
print("Log Enable" if LOG_ENABLE else "Log Disable")
|
|
11
|
+
|
|
12
|
+
logger = LoggerService()
|
|
13
|
+
|
|
14
|
+
def decorate_class_methods(cls: Type) -> Type:
|
|
15
|
+
for name, attr in cls.__dict__.items():
|
|
16
|
+
if name.startswith("_"):
|
|
17
|
+
continue
|
|
18
|
+
|
|
19
|
+
if isinstance(attr, staticmethod):
|
|
20
|
+
original = attr.__func__
|
|
21
|
+
wrapped = unified_logger(class_name=cls.__name__)(original)
|
|
22
|
+
setattr(cls, name, staticmethod(wrapped))
|
|
23
|
+
|
|
24
|
+
elif isinstance(attr, classmethod):
|
|
25
|
+
original = attr.__func__
|
|
26
|
+
wrapped = unified_logger(class_name=cls.__name__)(original)
|
|
27
|
+
setattr(cls, name, classmethod(wrapped))
|
|
28
|
+
|
|
29
|
+
elif callable(attr):
|
|
30
|
+
wrapped = unified_logger(class_name=cls.__name__)(attr)
|
|
31
|
+
setattr(cls, name, wrapped)
|
|
32
|
+
|
|
33
|
+
return cls
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def unified_logger(endpoint_name: str = None, class_name: str = None) -> Callable:
|
|
38
|
+
def decorator(target: Callable | Type):
|
|
39
|
+
|
|
40
|
+
if not LOG_ENABLE:
|
|
41
|
+
return target
|
|
42
|
+
|
|
43
|
+
if inspect.isclass(target):
|
|
44
|
+
return decorate_class_methods(target)
|
|
45
|
+
|
|
46
|
+
method_name = f"{class_name}.{target.__name__}" if class_name else target.__name__
|
|
47
|
+
|
|
48
|
+
if iscoroutinefunction(target):
|
|
49
|
+
|
|
50
|
+
@wraps(target)
|
|
51
|
+
async def async_wrapper(*args, **kwargs):
|
|
52
|
+
return await logger.execute_async(target, args, kwargs, method_name, endpoint_name)
|
|
53
|
+
|
|
54
|
+
return async_wrapper
|
|
55
|
+
|
|
56
|
+
else:
|
|
57
|
+
|
|
58
|
+
@wraps(target)
|
|
59
|
+
def sync_wrapper(*args, **kwargs):
|
|
60
|
+
return logger.execute_sync(target, args, kwargs, method_name, endpoint_name)
|
|
61
|
+
|
|
62
|
+
return sync_wrapper
|
|
63
|
+
|
|
64
|
+
return decorator
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
from typing import Optional
|
|
2
|
+
from pydantic import BaseModel
|
|
3
|
+
import datetime
|
|
4
|
+
|
|
5
|
+
from sharedkernel.logger.log_enums import LogEventTypeEnum, LogLevelEnum
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class LogDTO(BaseModel):
|
|
9
|
+
EventType : Optional[LogEventTypeEnum] = None
|
|
10
|
+
Description: Optional[str] = None
|
|
11
|
+
MethodName: Optional[str] = None
|
|
12
|
+
IsSuccess : Optional[bool] = None
|
|
13
|
+
UserId : Optional[str] = None
|
|
14
|
+
UserName : Optional[str] = None
|
|
15
|
+
FailureReason : Optional[str] = None
|
|
16
|
+
UserAgent : Optional[str] = None
|
|
17
|
+
IpAddress : Optional[str] = None
|
|
18
|
+
RequestPath : Optional[str] = None
|
|
19
|
+
ServiceName : Optional[str] = None
|
|
20
|
+
HttpMethod : Optional[str] = None
|
|
21
|
+
CorrelationId : Optional[str] = None
|
|
22
|
+
Level : Optional[LogLevelEnum] = None
|
|
23
|
+
CreatedOn : datetime.datetime = None
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import uuid
|
|
2
|
+
from typing import Callable
|
|
3
|
+
import os
|
|
4
|
+
from fastapi import Request, Response
|
|
5
|
+
from starlette.middleware.base import BaseHTTPMiddleware
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
from sharedkernel.logger.log_info import log_info
|
|
9
|
+
from sharedkernel.logger.log_dto import LogDTO
|
|
10
|
+
|
|
11
|
+
IP_HEADER_NAME = os.getenv("IP_HEADER_NAME")
|
|
12
|
+
LOG_ENABLE = True if os.getenv("LOG_ENABLE", "True").lower() == "true" else False
|
|
13
|
+
|
|
14
|
+
class LogMiddleware(BaseHTTPMiddleware):
|
|
15
|
+
|
|
16
|
+
async def dispatch(self, request: Request, call_next: Callable) -> Response:
|
|
17
|
+
|
|
18
|
+
if not LOG_ENABLE:
|
|
19
|
+
return await call_next(request)
|
|
20
|
+
|
|
21
|
+
correlation_id = request.headers.get("x-request-id") or uuid.uuid4().hex
|
|
22
|
+
ip_address = request.headers.get(IP_HEADER_NAME)
|
|
23
|
+
log_obj = LogDTO(
|
|
24
|
+
CorrelationId=correlation_id,
|
|
25
|
+
RequestPath=request.url.path,
|
|
26
|
+
HttpMethod=request.method,
|
|
27
|
+
UserAgent=request.headers.get("user-agent"),
|
|
28
|
+
IpAddress=ip_address,
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
log_info.set(log_obj)
|
|
32
|
+
|
|
33
|
+
response = await call_next(request)
|
|
34
|
+
|
|
35
|
+
return response
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
from fastapi import HTTPException, WebSocket
|
|
2
|
+
from kombu import Connection, Producer, Exchange
|
|
3
|
+
from pydantic import BaseModel
|
|
4
|
+
from typing import Optional
|
|
5
|
+
import datetime
|
|
6
|
+
import uuid
|
|
7
|
+
import os
|
|
8
|
+
|
|
9
|
+
from sharedkernel.logger.log_info import log_info
|
|
10
|
+
from sharedkernel.enum import ErrorCode
|
|
11
|
+
from sharedkernel.objects.user_info import current_user_info
|
|
12
|
+
from sharedkernel.exception.exception import BusinessException
|
|
13
|
+
from sharedkernel.logger.log_dto import LogDTO
|
|
14
|
+
from sharedkernel.logger.log_enums import LogEventTypeEnum, LogLevelEnum
|
|
15
|
+
|
|
16
|
+
RABBITMQ_USER = os.getenv("RABBITMQ_USER")
|
|
17
|
+
RABBITMQ_PASSWORD = os.getenv("RABBITMQ_PASSWORD")
|
|
18
|
+
RABBITMQ_HOST = os.getenv("RABBITMQ_HOST")
|
|
19
|
+
RABBITMQ_VHOST = os.getenv("RABBITMQ_VHOST")
|
|
20
|
+
LOG_EXCHANGE = os.getenv("LOG_EXCHANGE")
|
|
21
|
+
LOG_REQUESTS_QUEUE = os.getenv("LOG_REQUESTS_QUEUE")
|
|
22
|
+
SERVICE_NAME = os.getenv("SERVICE_NAME")
|
|
23
|
+
IP_HEADER_NAME = os.getenv("IP_HEADER_NAME")
|
|
24
|
+
|
|
25
|
+
rabbitmq_url = (
|
|
26
|
+
f"amqp://{RABBITMQ_USER}:{RABBITMQ_PASSWORD}"
|
|
27
|
+
f"@{RABBITMQ_HOST}/{RABBITMQ_VHOST}"
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
class LoggerService:
|
|
31
|
+
def __init__(self):
|
|
32
|
+
self._connection: Optional[Connection] = None
|
|
33
|
+
self._producer: Optional[Producer] = None
|
|
34
|
+
self._exchange = Exchange(LOG_EXCHANGE, type="topic")
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _ensure_connection(self):
|
|
38
|
+
if not self._connection or not self._connection.connected:
|
|
39
|
+
self._connection = Connection(rabbitmq_url)
|
|
40
|
+
self._connection.connect()
|
|
41
|
+
self._producer = Producer(self._connection)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def publish(self, log: BaseModel):
|
|
45
|
+
self._ensure_connection()
|
|
46
|
+
|
|
47
|
+
self._producer.publish(
|
|
48
|
+
body=log.model_dump_json(),
|
|
49
|
+
exchange=self._exchange,
|
|
50
|
+
routing_key=LOG_REQUESTS_QUEUE,
|
|
51
|
+
headers={
|
|
52
|
+
"cap-msg-id": str(uuid.uuid4()),
|
|
53
|
+
"cap-msg-name": LOG_REQUESTS_QUEUE,
|
|
54
|
+
},
|
|
55
|
+
content_type="application/json",
|
|
56
|
+
retry=True
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def build(
|
|
61
|
+
self,
|
|
62
|
+
method_name: str,
|
|
63
|
+
event_type: LogEventTypeEnum,
|
|
64
|
+
endpoint_name: str = None,
|
|
65
|
+
is_success: bool = True,
|
|
66
|
+
failure_reason: Optional[str] = None,
|
|
67
|
+
) -> LogDTO:
|
|
68
|
+
|
|
69
|
+
log = log_info.get()
|
|
70
|
+
user = current_user_info.get(None)
|
|
71
|
+
if user:
|
|
72
|
+
log.UserId = user.nameid
|
|
73
|
+
log.ServiceName = SERVICE_NAME
|
|
74
|
+
log.MethodName = method_name
|
|
75
|
+
log.Description = endpoint_name
|
|
76
|
+
log.IsSuccess = is_success
|
|
77
|
+
log.Level = LogLevelEnum.INFORMATION if is_success else LogLevelEnum.ERROR
|
|
78
|
+
log.EventType = event_type
|
|
79
|
+
log.FailureReason = failure_reason
|
|
80
|
+
log.CreatedOn = datetime.datetime.now(datetime.timezone.utc)
|
|
81
|
+
|
|
82
|
+
return log
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def map_exception(self, exc: Exception):
|
|
86
|
+
if isinstance(exc, BusinessException):
|
|
87
|
+
return str(exc.detail)
|
|
88
|
+
if isinstance(exc, HTTPException):
|
|
89
|
+
return str(exc.detail)
|
|
90
|
+
return ErrorCode.Internal_Server
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def emit(self, log: LogDTO):
|
|
94
|
+
self.publish(log)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
async def execute_async(self, target, args, kwargs, method_name, endpoint_name):
|
|
98
|
+
self.emit(self.build(method_name, LogEventTypeEnum.START, endpoint_name))
|
|
99
|
+
|
|
100
|
+
try:
|
|
101
|
+
result = await target(*args, **kwargs)
|
|
102
|
+
|
|
103
|
+
except Exception as exc:
|
|
104
|
+
self.emit(
|
|
105
|
+
self.build(
|
|
106
|
+
method_name,
|
|
107
|
+
LogEventTypeEnum.END,
|
|
108
|
+
endpoint_name,
|
|
109
|
+
is_success=False,
|
|
110
|
+
failure_reason=self.map_exception(exc),
|
|
111
|
+
)
|
|
112
|
+
)
|
|
113
|
+
raise
|
|
114
|
+
|
|
115
|
+
else:
|
|
116
|
+
self.emit(self.build(method_name, LogEventTypeEnum.END, endpoint_name))
|
|
117
|
+
return result
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def execute_sync(self, target, args, kwargs, method_name, endpoint_name):
|
|
121
|
+
self.emit(self.build(method_name, LogEventTypeEnum.START, endpoint_name))
|
|
122
|
+
|
|
123
|
+
try:
|
|
124
|
+
result = target(*args, **kwargs)
|
|
125
|
+
|
|
126
|
+
except Exception as exc:
|
|
127
|
+
self.emit(
|
|
128
|
+
self.build(
|
|
129
|
+
method_name,
|
|
130
|
+
LogEventTypeEnum.END,
|
|
131
|
+
endpoint_name,
|
|
132
|
+
is_success=False,
|
|
133
|
+
failure_reason=self.map_exception(exc),
|
|
134
|
+
)
|
|
135
|
+
)
|
|
136
|
+
raise
|
|
137
|
+
|
|
138
|
+
else:
|
|
139
|
+
self.emit(self.build(method_name, LogEventTypeEnum.END, endpoint_name))
|
|
140
|
+
return result
|
|
141
|
+
|
|
142
|
+
@staticmethod
|
|
143
|
+
def websocket_handler(websocket: WebSocket):
|
|
144
|
+
correlation_id = websocket.headers.get("x-request-id") or uuid.uuid4().hex
|
|
145
|
+
ip_address = websocket.headers.get(IP_HEADER_NAME)
|
|
146
|
+
|
|
147
|
+
log_obj = LogDTO(
|
|
148
|
+
CorrelationId=correlation_id,
|
|
149
|
+
RequestPath=websocket.url.path,
|
|
150
|
+
HttpMethod="ws",
|
|
151
|
+
UserAgent=websocket.headers.get("user-agent"),
|
|
152
|
+
IpAddress=ip_address,
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
log_info.set(log_obj)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sharedkernel
|
|
3
|
-
Version: 2.5.
|
|
3
|
+
Version: 2.5.2
|
|
4
4
|
Summary: sharekernel is a shared package between all python projects
|
|
5
5
|
Author: Smilinno
|
|
6
6
|
Description-Content-Type: text/markdown
|
|
@@ -30,6 +30,8 @@ Dynamic: summary
|
|
|
30
30
|
this is a shared kernel package
|
|
31
31
|
|
|
32
32
|
# Change Log
|
|
33
|
+
### Version 2.5.2
|
|
34
|
+
- Minor Fix
|
|
33
35
|
### Version 2.5.1
|
|
34
36
|
- Apply log on jwt service
|
|
35
37
|
### Version 2.5.0
|
|
@@ -19,6 +19,12 @@ sharedkernel/enum/sort_order.py,sha256=T_1AknuVg2OSJMlIDkEiCUNHPW0yDnFvbzoNpTgG3
|
|
|
19
19
|
sharedkernel/exception/__init__.py,sha256=Yjkd1VsLFuzfWcMj-v8YYTA-vjtUlF4ESglBFeN1zvs,315
|
|
20
20
|
sharedkernel/exception/exception.py,sha256=TOxnymWJfhsrMmuLsG-amqJlDxmzznEfRnqDBe1vizk,1016
|
|
21
21
|
sharedkernel/exception/exception_handlers.py,sha256=VHEYaf1quaf63w_JCsNWFbMs2TxqmQqqlL4yGt1OjXI,3302
|
|
22
|
+
sharedkernel/logger/log_decorator.py,sha256=poVxjTSdzCu6p8ohKMJ7ODsJ_Vd2ytxHxHSu0KNjVcg,1987
|
|
23
|
+
sharedkernel/logger/log_dto.py,sha256=LwZXuo6zBofF0rpmqrDdBV7lDum2nqE7SAdScgcQ0BI,785
|
|
24
|
+
sharedkernel/logger/log_enums.py,sha256=b6RCy6oLx-uUQANQVoEq3yxMld0m4Gtq5pTDos78vL8,209
|
|
25
|
+
sharedkernel/logger/log_info.py,sha256=bGv8QBTNB-4EfJf-cYuuAU2c_d1PNt_7R15vc3y-fJk,141
|
|
26
|
+
sharedkernel/logger/log_middlewares.py,sha256=5I7TB97lATJBi_Sfn344JCcMfFKJdr0joq0vieNPV4c,1109
|
|
27
|
+
sharedkernel/logger/logger_service.py,sha256=gmDvYRmDnnSZHnLlJsYT9LE7aTyC3CYB9nl8EVLo0gI,5010
|
|
22
28
|
sharedkernel/normalizer/__init__.py,sha256=cDmbquAW6o7rnvv3XkI7h5vMYp-3NmGN6zp1ryYOUcc,154
|
|
23
29
|
sharedkernel/normalizer/number_normalizer.py,sha256=zJ700T0t9P7hgxp7vox98LdPw9A4jsUjIHA_II9YmqU,286
|
|
24
30
|
sharedkernel/normalizer/phone_number_normalizer.py,sha256=8z-JGWvH45GiOkceMz9jZ4gLoW9X120I1JSzCAGWcvU,19268
|
|
@@ -29,7 +35,7 @@ sharedkernel/objects/json_string_model.py,sha256=j63tnoqiok0EmBP6T-ChYuQYKPw7mLq
|
|
|
29
35
|
sharedkernel/objects/jwt_model.py,sha256=XQHQhTbg7PT8XiUh5fd9MwRH4ldPsesI_hfbjaSqdKg,134
|
|
30
36
|
sharedkernel/objects/result.py,sha256=I_9hX5TPEO1oStzuFLjFh1rtimXorz7ml-OaW_2BMvc,680
|
|
31
37
|
sharedkernel/objects/user_info.py,sha256=51WyspRxlIWzK7Lfxgqg4D6mylXeHe9ZSenf-RhYTdA,286
|
|
32
|
-
sharedkernel-2.5.
|
|
33
|
-
sharedkernel-2.5.
|
|
34
|
-
sharedkernel-2.5.
|
|
35
|
-
sharedkernel-2.5.
|
|
38
|
+
sharedkernel-2.5.2.dist-info/METADATA,sha256=GrSNIZOW0sDbtsBsFyJtUtY2j0m1CPt2VRMtOESx9Sc,3558
|
|
39
|
+
sharedkernel-2.5.2.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
40
|
+
sharedkernel-2.5.2.dist-info/top_level.txt,sha256=TVTOnV1MItSSlpSjqkiijuHkoVsGHS4CArpsM-lylkE,13
|
|
41
|
+
sharedkernel-2.5.2.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|