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.
@@ -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,12 @@
1
+ from enum import IntEnum
2
+
3
+ class LogEventTypeEnum(IntEnum):
4
+ START = 1
5
+ END = 2
6
+
7
+ class LogLevelEnum(IntEnum):
8
+ INFORMATION = 1
9
+ WARNING = 2
10
+ CRITICAL = 3
11
+ SECURITY = 4
12
+ ERROR = 5
@@ -0,0 +1,5 @@
1
+ from contextvars import ContextVar
2
+
3
+ from sharedkernel.logger.log_dto import LogDTO
4
+
5
+ log_info: ContextVar[LogDTO] = ContextVar("log_info")
@@ -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.1
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.1.dist-info/METADATA,sha256=KCRXXvtefyW4zey034jNVCc8i2XOMuKYlOzcQELY9rg,3528
33
- sharedkernel-2.5.1.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
34
- sharedkernel-2.5.1.dist-info/top_level.txt,sha256=TVTOnV1MItSSlpSjqkiijuHkoVsGHS4CArpsM-lylkE,13
35
- sharedkernel-2.5.1.dist-info/RECORD,,
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,,