qena-shared-lib 0.1.12__py3-none-any.whl → 0.1.14__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.
- qena_shared_lib/__init__.py +2 -2
- qena_shared_lib/application.py +71 -29
- qena_shared_lib/background.py +6 -5
- qena_shared_lib/exception_handlers.py +203 -175
- qena_shared_lib/exceptions.py +10 -10
- qena_shared_lib/http.py +16 -16
- qena_shared_lib/rabbitmq/__init__.py +8 -6
- qena_shared_lib/rabbitmq/_base.py +96 -127
- qena_shared_lib/rabbitmq/_channel.py +4 -1
- qena_shared_lib/rabbitmq/_exception_handlers.py +154 -119
- qena_shared_lib/rabbitmq/_listener.py +94 -38
- qena_shared_lib/rabbitmq/_rpc_client.py +4 -4
- qena_shared_lib/remotelogging/__init__.py +15 -0
- qena_shared_lib/{logstash → remotelogging}/_base.py +47 -67
- qena_shared_lib/remotelogging/logstash/__init__.py +9 -0
- qena_shared_lib/remotelogging/logstash/_base.py +32 -0
- qena_shared_lib/{logstash → remotelogging/logstash}/_http_sender.py +5 -4
- qena_shared_lib/{logstash → remotelogging/logstash}/_tcp_sender.py +7 -5
- qena_shared_lib/scheduler.py +49 -24
- qena_shared_lib/security.py +2 -2
- qena_shared_lib/utils.py +9 -3
- {qena_shared_lib-0.1.12.dist-info → qena_shared_lib-0.1.14.dist-info}/METADATA +23 -20
- qena_shared_lib-0.1.14.dist-info/RECORD +31 -0
- qena_shared_lib/logstash/__init__.py +0 -17
- qena_shared_lib-0.1.12.dist-info/RECORD +0 -29
- {qena_shared_lib-0.1.12.dist-info → qena_shared_lib-0.1.14.dist-info}/WHEEL +0 -0
qena_shared_lib/__init__.py
CHANGED
@@ -5,8 +5,8 @@ from . import (
|
|
5
5
|
exceptions,
|
6
6
|
http,
|
7
7
|
logging,
|
8
|
-
logstash,
|
9
8
|
rabbitmq,
|
9
|
+
remotelogging,
|
10
10
|
scheduler,
|
11
11
|
security,
|
12
12
|
utils,
|
@@ -19,7 +19,7 @@ __all__ = [
|
|
19
19
|
"exceptions",
|
20
20
|
"http",
|
21
21
|
"logging",
|
22
|
-
"
|
22
|
+
"remotelogging",
|
23
23
|
"rabbitmq",
|
24
24
|
"scheduler",
|
25
25
|
"security",
|
qena_shared_lib/application.py
CHANGED
@@ -2,17 +2,17 @@ from enum import Enum
|
|
2
2
|
from typing import Any, TypeVar
|
3
3
|
|
4
4
|
from fastapi import APIRouter, FastAPI
|
5
|
-
from fastapi.exceptions import RequestValidationError
|
6
5
|
from prometheus_fastapi_instrumentator import Instrumentator
|
7
6
|
from punq import Container, Scope, empty
|
8
7
|
from starlette.types import Lifespan
|
8
|
+
from typing_extensions import Self
|
9
9
|
|
10
10
|
from .exception_handlers import (
|
11
|
-
|
12
|
-
|
13
|
-
|
11
|
+
AbstractHttpExceptionHandler,
|
12
|
+
GeneralHttpExceptionHandler,
|
13
|
+
HTTPServiceExceptionHandler,
|
14
|
+
RequestValidationErrorHandler,
|
14
15
|
)
|
15
|
-
from .exceptions import ServiceException
|
16
16
|
from .http import ControllerBase
|
17
17
|
|
18
18
|
__all__ = [
|
@@ -21,6 +21,7 @@ __all__ = [
|
|
21
21
|
"FastAPI",
|
22
22
|
]
|
23
23
|
|
24
|
+
|
24
25
|
D = TypeVar("D")
|
25
26
|
|
26
27
|
|
@@ -46,7 +47,7 @@ class Builder:
|
|
46
47
|
self._container = Container()
|
47
48
|
self._built = False
|
48
49
|
|
49
|
-
def with_environment(self, environment: Environment) ->
|
50
|
+
def with_environment(self, environment: Environment) -> Self:
|
50
51
|
match environment:
|
51
52
|
case Environment.DEVELOPMENT:
|
52
53
|
self._environment = Environment.DEVELOPMENT
|
@@ -60,35 +61,33 @@ class Builder:
|
|
60
61
|
|
61
62
|
return self
|
62
63
|
|
63
|
-
def with_title(self, title: str) ->
|
64
|
+
def with_title(self, title: str) -> Self:
|
64
65
|
self._title = title
|
65
66
|
|
66
67
|
return self
|
67
68
|
|
68
|
-
def with_description(self, description: str) ->
|
69
|
+
def with_description(self, description: str) -> Self:
|
69
70
|
self._description = description
|
70
71
|
|
71
72
|
return self
|
72
73
|
|
73
|
-
def with_version(self, version: str) ->
|
74
|
+
def with_version(self, version: str) -> Self:
|
74
75
|
self._version = version
|
75
76
|
|
76
77
|
return self
|
77
78
|
|
78
|
-
def with_lifespan(self, lifespan: Lifespan) ->
|
79
|
+
def with_lifespan(self, lifespan: Lifespan) -> Self:
|
79
80
|
self._lifespan = lifespan
|
80
81
|
|
81
82
|
return self
|
82
83
|
|
83
|
-
def with_controllers(
|
84
|
-
self, controllers: list[type[ControllerBase]]
|
85
|
-
) -> "Builder":
|
84
|
+
def with_controllers(self, *controllers: type[ControllerBase]) -> Self:
|
86
85
|
for index, controller in enumerate(controllers):
|
87
86
|
if not isinstance(controller, type) or not issubclass(
|
88
87
|
controller, ControllerBase
|
89
88
|
):
|
90
89
|
raise TypeError(
|
91
|
-
f"controller {index} is {type(
|
90
|
+
f"controller {index} is {type(controller)}, expected instance of type or subclass of `ControllerBase`"
|
92
91
|
)
|
93
92
|
|
94
93
|
self._container.register(
|
@@ -99,7 +98,7 @@ class Builder:
|
|
99
98
|
|
100
99
|
return self
|
101
100
|
|
102
|
-
def with_routers(self, routers:
|
101
|
+
def with_routers(self, *routers: APIRouter) -> Self:
|
103
102
|
if any(not isinstance(router, APIRouter) for router in routers):
|
104
103
|
raise TypeError("some routers are not type `APIRouter`")
|
105
104
|
|
@@ -107,13 +106,41 @@ class Builder:
|
|
107
106
|
|
108
107
|
return self
|
109
108
|
|
109
|
+
def with_exception_handlers(
|
110
|
+
self, *exception_handlers: type[AbstractHttpExceptionHandler]
|
111
|
+
) -> Self:
|
112
|
+
for index, exception_handler in enumerate(exception_handlers):
|
113
|
+
if not isinstance(exception_handler, type) or not issubclass(
|
114
|
+
exception_handler, AbstractHttpExceptionHandler
|
115
|
+
):
|
116
|
+
raise TypeError(
|
117
|
+
f"exception handler {index} is {type(exception_handler)}, expected instance of type or subclass of `AbstractHttpExceptionHandler`"
|
118
|
+
)
|
119
|
+
|
120
|
+
self._container.register(
|
121
|
+
service=AbstractHttpExceptionHandler,
|
122
|
+
factory=exception_handler,
|
123
|
+
scope=Scope.singleton,
|
124
|
+
)
|
125
|
+
|
126
|
+
return self
|
127
|
+
|
128
|
+
def with_default_exception_handlers(self) -> Self:
|
129
|
+
self.with_exception_handlers(
|
130
|
+
GeneralHttpExceptionHandler,
|
131
|
+
HTTPServiceExceptionHandler,
|
132
|
+
RequestValidationErrorHandler,
|
133
|
+
)
|
134
|
+
|
135
|
+
return self
|
136
|
+
|
110
137
|
def with_singleton(
|
111
138
|
self,
|
112
139
|
service: type[D],
|
113
140
|
factory: Any = empty,
|
114
141
|
instance: Any = empty,
|
115
142
|
**kwargs: Any,
|
116
|
-
) ->
|
143
|
+
) -> Self:
|
117
144
|
self._container.register(
|
118
145
|
service=service,
|
119
146
|
factory=factory,
|
@@ -126,7 +153,7 @@ class Builder:
|
|
126
153
|
|
127
154
|
def with_transient(
|
128
155
|
self, service: type[D], factory: Any = empty, **kwargs: Any
|
129
|
-
) ->
|
156
|
+
) -> Self:
|
130
157
|
self._container.register(
|
131
158
|
service=service,
|
132
159
|
factory=factory,
|
@@ -136,7 +163,7 @@ class Builder:
|
|
136
163
|
|
137
164
|
return self
|
138
165
|
|
139
|
-
def with_metrics(self, endpoint: str = "/metrics") ->
|
166
|
+
def with_metrics(self, endpoint: str = "/metrics") -> Self:
|
140
167
|
self._metrics_endpoint = endpoint
|
141
168
|
self._instrumentator = Instrumentator()
|
142
169
|
|
@@ -158,13 +185,8 @@ class Builder:
|
|
158
185
|
)
|
159
186
|
app.state.container = self._container
|
160
187
|
|
161
|
-
|
162
|
-
|
163
|
-
handle_request_validation_error
|
164
|
-
)
|
165
|
-
app.exception_handler(Exception)(handle_general_http_exception)
|
166
|
-
|
167
|
-
self._resolve_api_controllers(app)
|
188
|
+
self._register_api_controllers(app)
|
189
|
+
self._register_exception_handlers(app)
|
168
190
|
|
169
191
|
if self._instrumentator is not None:
|
170
192
|
self._instrumentator.instrument(app).expose(
|
@@ -177,14 +199,34 @@ class Builder:
|
|
177
199
|
|
178
200
|
return app
|
179
201
|
|
180
|
-
def
|
181
|
-
|
202
|
+
def _register_api_controllers(self, app: FastAPI) -> None:
|
203
|
+
for router in self._routers + self._resolve_api_controllers():
|
204
|
+
app.include_router(router)
|
205
|
+
|
206
|
+
def _resolve_api_controllers(self) -> list[APIRouter]:
|
207
|
+
return [
|
182
208
|
api_controller.register_route_handlers()
|
183
209
|
for api_controller in self._container.resolve_all(ControllerBase)
|
184
210
|
]
|
185
211
|
|
186
|
-
|
187
|
-
|
212
|
+
def _register_exception_handlers(self, app: FastAPI) -> None:
|
213
|
+
for exception_handler in self._resolve_exception_handlers():
|
214
|
+
if not callable(exception_handler):
|
215
|
+
raise ValueError(
|
216
|
+
f"exception handler {exception_handler.__class__.__name__} is not callable"
|
217
|
+
)
|
218
|
+
|
219
|
+
app.exception_handler(exception_handler.exception)(
|
220
|
+
exception_handler
|
221
|
+
)
|
222
|
+
|
223
|
+
def _resolve_exception_handlers(self) -> list[AbstractHttpExceptionHandler]:
|
224
|
+
return [
|
225
|
+
exception_handler
|
226
|
+
for exception_handler in self._container.resolve_all(
|
227
|
+
AbstractHttpExceptionHandler
|
228
|
+
)
|
229
|
+
]
|
188
230
|
|
189
231
|
@property
|
190
232
|
def environment(self) -> Environment:
|
qena_shared_lib/background.py
CHANGED
@@ -3,13 +3,14 @@ from asyncio import (
|
|
3
3
|
Task,
|
4
4
|
gather,
|
5
5
|
)
|
6
|
+
from typing import Any
|
6
7
|
from uuid import uuid4
|
7
8
|
|
8
9
|
from prometheus_client import Enum as PrometheusEnum
|
9
10
|
from starlette.background import BackgroundTask
|
10
11
|
|
11
12
|
from .logging import LoggerProvider
|
12
|
-
from .
|
13
|
+
from .remotelogging import BaseRemoteLogSender
|
13
14
|
from .utils import AsyncEventLoopMixin
|
14
15
|
|
15
16
|
__all__ = [
|
@@ -27,14 +28,14 @@ class Background(AsyncEventLoopMixin):
|
|
27
28
|
|
28
29
|
def __init__(
|
29
30
|
self,
|
30
|
-
|
31
|
+
remote_logger: BaseRemoteLogSender,
|
31
32
|
) -> None:
|
32
33
|
self._queue: Queue[tuple[BackgroundTask | None, str | None]] = Queue()
|
33
34
|
self._started = False
|
34
35
|
self._stopped = False
|
35
|
-
self.
|
36
|
+
self._remote_logger = remote_logger
|
36
37
|
self._logger = LoggerProvider.default().get_logger("backgroud")
|
37
|
-
self._tasks: dict[str, Task] = {}
|
38
|
+
self._tasks: dict[str, Task[Any]] = {}
|
38
39
|
|
39
40
|
async def _task_manager(
|
40
41
|
self, task: BackgroundTask, task_id: str | None = None
|
@@ -53,7 +54,7 @@ class Background(AsyncEventLoopMixin):
|
|
53
54
|
|
54
55
|
await self._tasks[task_id]
|
55
56
|
except Exception:
|
56
|
-
self.
|
57
|
+
self._remote_logger.error(
|
57
58
|
"exception occured when running background task {task.func.__name__} with id {task_id}"
|
58
59
|
)
|
59
60
|
finally:
|
@@ -1,12 +1,11 @@
|
|
1
1
|
from collections.abc import Iterable
|
2
|
-
from typing import Any
|
2
|
+
from typing import Any, cast
|
3
3
|
|
4
4
|
from fastapi import Request, Response, status
|
5
5
|
from fastapi.exceptions import RequestValidationError
|
6
6
|
from fastapi.responses import JSONResponse
|
7
7
|
from pydantic_core import to_jsonable_python
|
8
8
|
|
9
|
-
from .dependencies.http import get_service
|
10
9
|
from .exceptions import (
|
11
10
|
HTTPServiceError,
|
12
11
|
RabbitMQServiceException,
|
@@ -14,194 +13,223 @@ from .exceptions import (
|
|
14
13
|
Severity,
|
15
14
|
)
|
16
15
|
from .logging import LoggerProvider
|
17
|
-
from .
|
16
|
+
from .remotelogging import BaseRemoteLogSender
|
18
17
|
|
19
18
|
__all__ = [
|
20
|
-
"
|
21
|
-
"
|
22
|
-
"
|
19
|
+
"AbstractHttpExceptionHandler",
|
20
|
+
"GeneralHttpExceptionHandler",
|
21
|
+
"HTTPServiceExceptionHandler",
|
22
|
+
"RequestValidationErrorHandler",
|
23
23
|
]
|
24
24
|
|
25
25
|
|
26
|
-
|
27
|
-
|
28
|
-
) ->
|
29
|
-
|
30
|
-
logger_provider = get_service(app=request.app, service_key=LoggerProvider)
|
31
|
-
logger = logger_provider.get_logger("http.exception_handler")
|
32
|
-
exception_severity = exception.severity or Severity.LOW
|
33
|
-
user_agent = request.headers.get("user-agent", "__unknown__")
|
34
|
-
message = exception.message
|
35
|
-
tags = [
|
36
|
-
"HTTP",
|
37
|
-
request.method,
|
38
|
-
request.url.path,
|
39
|
-
exception.__class__.__name__,
|
40
|
-
]
|
41
|
-
extra = {
|
42
|
-
"serviceType": "HTTP",
|
43
|
-
"method": request.method,
|
44
|
-
"path": request.url.path,
|
45
|
-
"userAgent": user_agent,
|
46
|
-
"exception": exception.__class__.__name__,
|
47
|
-
}
|
48
|
-
exc_info = (
|
49
|
-
(type(exception), exception, exception.__traceback__)
|
50
|
-
if exception.extract_exc_info
|
51
|
-
else None
|
52
|
-
)
|
53
|
-
|
54
|
-
match exception_severity:
|
55
|
-
case Severity.LOW:
|
56
|
-
logstash_logger_method = logstash.info
|
57
|
-
logger_method = logger.info
|
58
|
-
case Severity.MEDIUM:
|
59
|
-
logstash_logger_method = logstash.warning
|
60
|
-
logger_method = logger.warning
|
61
|
-
case _:
|
62
|
-
message = "something went wrong"
|
63
|
-
logstash_logger_method = logstash.error
|
64
|
-
logger_method = logger.error
|
65
|
-
|
66
|
-
content: dict[str, Any] = {
|
67
|
-
"severity": exception_severity.name,
|
68
|
-
"message": message,
|
69
|
-
}
|
70
|
-
status_code = _status_code_from_severity(exception.severity)
|
71
|
-
headers = None
|
72
|
-
|
73
|
-
match exception:
|
74
|
-
case HTTPServiceError() as http_service_error:
|
75
|
-
if http_service_error.body is not None:
|
76
|
-
extra_body = to_jsonable_python(http_service_error.body)
|
77
|
-
is_updated = False
|
78
|
-
|
79
|
-
try:
|
80
|
-
if isinstance(extra_body, Iterable):
|
81
|
-
content.update(extra_body)
|
82
|
-
|
83
|
-
is_updated = True
|
84
|
-
except:
|
85
|
-
pass
|
86
|
-
|
87
|
-
if not is_updated:
|
88
|
-
content["data"] = extra_body
|
89
|
-
|
90
|
-
if http_service_error.response_code is not None:
|
91
|
-
content["code"] = http_service_error.response_code
|
92
|
-
str_response_code = str(http_service_error.response_code)
|
93
|
-
extra["responseCode"] = str_response_code
|
94
|
-
|
95
|
-
tags.append(str_response_code)
|
96
|
-
|
97
|
-
if http_service_error.corrective_action is not None:
|
98
|
-
content["correctiveAction"] = (
|
99
|
-
http_service_error.corrective_action
|
100
|
-
)
|
101
|
-
|
102
|
-
if http_service_error.status_code is not None:
|
103
|
-
status_code = http_service_error.status_code
|
104
|
-
str_status_code = str(status_code)
|
105
|
-
extra["statusCode"] = str_status_code
|
106
|
-
|
107
|
-
tags.append(str_status_code)
|
108
|
-
|
109
|
-
if http_service_error.headers is not None:
|
110
|
-
headers = http_service_error.headers
|
111
|
-
case RabbitMQServiceException() as rabbitmq_service_exception:
|
112
|
-
str_error_code = str(rabbitmq_service_exception.code)
|
113
|
-
extra["code"] = str_error_code
|
114
|
-
|
115
|
-
tags.append(str_error_code)
|
116
|
-
|
117
|
-
if exception.tags:
|
118
|
-
tags.extend(exception.tags)
|
119
|
-
|
120
|
-
if exception.extra:
|
121
|
-
extra.update(exception.extra)
|
122
|
-
|
123
|
-
if exception.logstash_logging:
|
124
|
-
logstash_logger_method(
|
125
|
-
message=exception.message,
|
126
|
-
tags=tags,
|
127
|
-
extra=extra,
|
128
|
-
exception=exception if exception.extract_exc_info else None,
|
129
|
-
)
|
130
|
-
else:
|
131
|
-
logger_method(
|
132
|
-
"\n%s %s\n%s",
|
133
|
-
request.method,
|
134
|
-
request.url.path,
|
135
|
-
exception.message,
|
136
|
-
exc_info=exc_info,
|
137
|
-
)
|
26
|
+
class AbstractHttpExceptionHandler:
|
27
|
+
@property
|
28
|
+
def exception(self) -> type[Exception]:
|
29
|
+
raise NotImplementedError()
|
138
30
|
|
139
|
-
return JSONResponse(
|
140
|
-
content=content,
|
141
|
-
status_code=status_code,
|
142
|
-
headers=headers,
|
143
|
-
)
|
144
31
|
|
32
|
+
class HTTPServiceExceptionHandler(AbstractHttpExceptionHandler):
|
33
|
+
@property
|
34
|
+
def exception(self) -> type[Exception]:
|
35
|
+
return cast(type[Exception], ServiceException)
|
145
36
|
|
146
|
-
def
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
},
|
162
|
-
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
|
163
|
-
)
|
164
|
-
|
165
|
-
|
166
|
-
def handle_general_http_exception(
|
167
|
-
request: Request, exception: Exception
|
168
|
-
) -> Response:
|
169
|
-
logstash = get_service(app=request.app, service_key=BaseLogstashSender)
|
170
|
-
user_agent = request.get("user-agent", "__unknown__")
|
171
|
-
|
172
|
-
logstash.error(
|
173
|
-
message=f"something went wrong on endpoint `{request.method} {request.url.path}`",
|
174
|
-
tags=[
|
37
|
+
def __init__(
|
38
|
+
self,
|
39
|
+
remote_logger: BaseRemoteLogSender,
|
40
|
+
logger_provider: LoggerProvider,
|
41
|
+
):
|
42
|
+
self._remote_logger = remote_logger
|
43
|
+
self._logger = logger_provider.get_logger("http.exception_handler")
|
44
|
+
|
45
|
+
def __call__(
|
46
|
+
self, request: Request, exception: ServiceException
|
47
|
+
) -> Response:
|
48
|
+
exception_severity = exception.severity or Severity.LOW
|
49
|
+
user_agent = request.headers.get("user-agent", "__unknown__")
|
50
|
+
message = exception.message
|
51
|
+
tags = [
|
175
52
|
"HTTP",
|
176
53
|
request.method,
|
177
54
|
request.url.path,
|
178
55
|
exception.__class__.__name__,
|
179
|
-
]
|
180
|
-
extra={
|
56
|
+
]
|
57
|
+
extra = {
|
181
58
|
"serviceType": "HTTP",
|
182
59
|
"method": request.method,
|
183
60
|
"path": request.url.path,
|
184
61
|
"userAgent": user_agent,
|
185
62
|
"exception": exception.__class__.__name__,
|
186
|
-
}
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
63
|
+
}
|
64
|
+
exc_info = (
|
65
|
+
(type(exception), exception, exception.__traceback__)
|
66
|
+
if exception.extract_exc_info
|
67
|
+
else None
|
68
|
+
)
|
69
|
+
|
70
|
+
match exception_severity:
|
71
|
+
case Severity.LOW:
|
72
|
+
remote_logger_method = self._remote_logger.info
|
73
|
+
logger_method = self._logger.info
|
74
|
+
case Severity.MEDIUM:
|
75
|
+
remote_logger_method = self._remote_logger.warning
|
76
|
+
logger_method = self._logger.warning
|
77
|
+
case _:
|
78
|
+
message = "something went wrong"
|
79
|
+
remote_logger_method = self._remote_logger.error
|
80
|
+
logger_method = self._logger.error
|
81
|
+
|
82
|
+
content: dict[str, Any] = {
|
83
|
+
"severity": exception_severity.name,
|
84
|
+
"message": message,
|
85
|
+
}
|
86
|
+
status_code = self._status_code_from_severity(exception.severity)
|
87
|
+
headers = None
|
88
|
+
|
89
|
+
match exception:
|
90
|
+
case HTTPServiceError() as http_service_error:
|
91
|
+
if http_service_error.body is not None:
|
92
|
+
extra_body = to_jsonable_python(http_service_error.body)
|
93
|
+
is_updated = False
|
94
|
+
|
95
|
+
try:
|
96
|
+
if isinstance(extra_body, Iterable):
|
97
|
+
content.update(extra_body)
|
98
|
+
|
99
|
+
is_updated = True
|
100
|
+
except:
|
101
|
+
pass
|
102
|
+
|
103
|
+
if not is_updated:
|
104
|
+
content["data"] = extra_body
|
105
|
+
|
106
|
+
if http_service_error.response_code is not None:
|
107
|
+
content["code"] = http_service_error.response_code
|
108
|
+
str_response_code = str(http_service_error.response_code)
|
109
|
+
extra["responseCode"] = str_response_code
|
110
|
+
|
111
|
+
tags.append(str_response_code)
|
112
|
+
|
113
|
+
if http_service_error.corrective_action is not None:
|
114
|
+
content["correctiveAction"] = (
|
115
|
+
http_service_error.corrective_action
|
116
|
+
)
|
117
|
+
|
118
|
+
if http_service_error.status_code is not None:
|
119
|
+
status_code = http_service_error.status_code
|
120
|
+
str_status_code = str(status_code)
|
121
|
+
extra["statusCode"] = str_status_code
|
122
|
+
|
123
|
+
tags.append(str_status_code)
|
124
|
+
|
125
|
+
if http_service_error.headers is not None:
|
126
|
+
headers = http_service_error.headers
|
127
|
+
case RabbitMQServiceException() as rabbitmq_service_exception:
|
128
|
+
str_error_code = str(rabbitmq_service_exception.code)
|
129
|
+
extra["code"] = str_error_code
|
130
|
+
|
131
|
+
tags.append(str_error_code)
|
132
|
+
|
133
|
+
if exception.tags:
|
134
|
+
tags.extend(exception.tags)
|
135
|
+
|
136
|
+
if exception.extra:
|
137
|
+
extra.update(exception.extra)
|
138
|
+
|
139
|
+
if exception.remote_logging:
|
140
|
+
remote_logger_method(
|
141
|
+
message=exception.message,
|
142
|
+
tags=tags,
|
143
|
+
extra=extra,
|
144
|
+
exception=exception if exception.extract_exc_info else None,
|
145
|
+
)
|
146
|
+
else:
|
147
|
+
logger_method(
|
148
|
+
"\n%s %s\n%s",
|
149
|
+
request.method,
|
150
|
+
request.url.path,
|
151
|
+
exception.message,
|
152
|
+
exc_info=exc_info,
|
153
|
+
)
|
154
|
+
|
155
|
+
return JSONResponse(
|
156
|
+
content=content,
|
157
|
+
status_code=status_code,
|
158
|
+
headers=headers,
|
159
|
+
)
|
160
|
+
|
161
|
+
def _status_code_from_severity(self, severity: Severity | None) -> int:
|
162
|
+
if (
|
163
|
+
severity is None
|
164
|
+
or severity is Severity.LOW
|
165
|
+
or severity is Severity.MEDIUM
|
166
|
+
):
|
167
|
+
return cast(int, status.HTTP_400_BAD_REQUEST)
|
206
168
|
|
207
|
-
|
169
|
+
return cast(int, status.HTTP_500_INTERNAL_SERVER_ERROR)
|
170
|
+
|
171
|
+
|
172
|
+
class RequestValidationErrorHandler(AbstractHttpExceptionHandler):
|
173
|
+
@property
|
174
|
+
def exception(self) -> type[Exception]:
|
175
|
+
return cast(type[Exception], RequestValidationError)
|
176
|
+
|
177
|
+
def __init__(self, logger_provider: LoggerProvider):
|
178
|
+
self._logger = logger_provider.get_logger("http.exception_handler")
|
179
|
+
|
180
|
+
def __call__(
|
181
|
+
self, request: Request, error: RequestValidationError
|
182
|
+
) -> Response:
|
183
|
+
message = "invalid request data"
|
184
|
+
|
185
|
+
self._logger.warning(
|
186
|
+
"\n%s %s\n%s", request.method, request.url.path, message
|
187
|
+
)
|
188
|
+
|
189
|
+
return JSONResponse(
|
190
|
+
content={
|
191
|
+
"severity": Severity.MEDIUM.name,
|
192
|
+
"message": message,
|
193
|
+
"code": 100,
|
194
|
+
"detail": to_jsonable_python(error.errors()),
|
195
|
+
},
|
196
|
+
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
|
197
|
+
)
|
198
|
+
|
199
|
+
|
200
|
+
class GeneralHttpExceptionHandler(AbstractHttpExceptionHandler):
|
201
|
+
@property
|
202
|
+
def exception(self) -> type[Exception]:
|
203
|
+
return Exception
|
204
|
+
|
205
|
+
def __init__(self, remote_logger: BaseRemoteLogSender):
|
206
|
+
self._remote_logger = remote_logger
|
207
|
+
|
208
|
+
def __call__(self, request: Request, exception: Exception) -> Response:
|
209
|
+
user_agent = request.get("user-agent", "__unknown__")
|
210
|
+
|
211
|
+
self._remote_logger.error(
|
212
|
+
message=f"something went wrong on endpoint `{request.method} {request.url.path}`",
|
213
|
+
tags=[
|
214
|
+
"HTTP",
|
215
|
+
request.method,
|
216
|
+
request.url.path,
|
217
|
+
exception.__class__.__name__,
|
218
|
+
],
|
219
|
+
extra={
|
220
|
+
"serviceType": "HTTP",
|
221
|
+
"method": request.method,
|
222
|
+
"path": request.url.path,
|
223
|
+
"userAgent": user_agent,
|
224
|
+
"exception": exception.__class__.__name__,
|
225
|
+
},
|
226
|
+
exception=exception,
|
227
|
+
)
|
228
|
+
|
229
|
+
return JSONResponse(
|
230
|
+
content={
|
231
|
+
"severity": Severity.HIGH.name,
|
232
|
+
"message": "something went wrong",
|
233
|
+
},
|
234
|
+
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
235
|
+
)
|