Skip to content

Commit b4d24a7

Browse files
committed
✨ Enhance error handling: add detailed logging for DirectorV2ServiceError and improve error context creation
1 parent 1a46510 commit b4d24a7

File tree

3 files changed

+91
-20
lines changed

3 files changed

+91
-20
lines changed

services/web/server/src/simcore_service_webserver/director_v2/_controller/_rest_exceptions.py

Lines changed: 64 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,82 @@
1+
import logging
2+
3+
from aiohttp import web
4+
from common_library.error_codes import create_error_code
5+
from models_library.rest_error import ErrorGet
6+
from servicelib import status_codes_utils
17
from servicelib.aiohttp import status
2-
from simcore_service_webserver.constants import MSG_TRY_AGAIN_OR_SUPPORT
3-
from simcore_service_webserver.director_v2.exceptions import DirectorV2ServiceError
8+
from servicelib.logging_errors import create_troubleshotting_log_kwargs
49

10+
from ...constants import MSG_TRY_AGAIN_OR_SUPPORT
511
from ...exception_handling import (
612
ExceptionHandlersMap,
713
ExceptionToHttpErrorMap,
814
HttpErrorInfo,
15+
create_error_context_from_request,
16+
create_error_response,
917
exception_handling_decorator,
1018
to_exceptions_handlers_map,
1119
)
1220
from ...users.exceptions import UserDefaultWalletNotFoundError
1321
from ...wallets.errors import WalletNotEnoughCreditsError
22+
from ..exceptions import DirectorV2ServiceError
1423

1524
_exceptions_handlers_map: ExceptionHandlersMap = {}
1625

1726

27+
_logger = logging.getLogger(__name__)
28+
29+
30+
async def _handler_director_service_error_as_503_or_4xx(
31+
request: web.Request, exception: Exception
32+
) -> web.Response:
33+
assert isinstance(exception, DirectorV2ServiceError) # nosec
34+
assert status_codes_utils.is_error(
35+
exception.status
36+
), f"DirectorV2ServiceError must be an error, got {exception=}" # nosec
37+
38+
if status_codes_utils.is_5xx_server_error(exception.status):
39+
# NOTE: All directorv2 5XX are mapped to 503
40+
status_code = status.HTTP_503_SERVICE_UNAVAILABLE
41+
user_msg = (
42+
# Most likely the director service is down or misconfigured so the user is asked to try again later.
43+
"This service is temporarily unavailable. The incident was logged and will be investigated. "
44+
+ MSG_TRY_AGAIN_OR_SUPPORT
45+
)
46+
47+
# Log for further investigation
48+
oec = create_error_code(exception)
49+
_logger.exception(
50+
**create_troubleshotting_log_kwargs(
51+
user_msg,
52+
error=exception,
53+
error_code=oec,
54+
error_context={
55+
**create_error_context_from_request(request),
56+
"error_code": oec,
57+
},
58+
)
59+
)
60+
error = ErrorGet.model_construct(
61+
message=user_msg, support_id=oec, status=status_code
62+
)
63+
64+
else:
65+
# NOTE: All directorv2 4XX are mapped one-to-one
66+
assert status_codes_utils.is_4xx_client_error(
67+
exception.status
68+
), f"DirectorV2ServiceError must be a client error, got {exception=}" # nosec
69+
70+
error = ErrorGet(status=exception.status, message="{exception}")
71+
72+
return create_error_response(error, status_code=error.status)
73+
74+
75+
_exceptions_handlers_map[DirectorV2ServiceError] = (
76+
_handler_director_service_error_as_503_or_4xx
77+
)
78+
79+
1880
_TO_HTTP_ERROR_MAP: ExceptionToHttpErrorMap = {
1981
UserDefaultWalletNotFoundError: HttpErrorInfo(
2082
status.HTTP_404_NOT_FOUND,
@@ -24,13 +86,6 @@
2486
status.HTTP_402_PAYMENT_REQUIRED,
2587
"Wallet does not have enough credits for computations. {reason}",
2688
),
27-
DirectorV2ServiceError: HttpErrorInfo(
28-
status.HTTP_503_SERVICE_UNAVAILABLE,
29-
# This error is raised when the director service raises an unhandled exception.
30-
# Most likely the director service is down or misconfigured so the user is asked to try again later.
31-
"This service is temporarily unavailable. The incident was logged and will be investigated. "
32-
+ MSG_TRY_AGAIN_OR_SUPPORT,
33-
),
3489
}
3590

3691
_exceptions_handlers_map.update(to_exceptions_handlers_map(_TO_HTTP_ERROR_MAP))

services/web/server/src/simcore_service_webserver/exception_handling/__init__.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,18 @@
11
from ._base import ExceptionHandlersMap, exception_handling_decorator
2-
from ._factory import ExceptionToHttpErrorMap, HttpErrorInfo, to_exceptions_handlers_map
2+
from ._factory import (
3+
ExceptionToHttpErrorMap,
4+
HttpErrorInfo,
5+
create_error_context_from_request,
6+
create_error_response,
7+
to_exceptions_handlers_map,
8+
)
39

410
__all__: tuple[str, ...] = (
511
"ExceptionHandlersMap",
612
"ExceptionToHttpErrorMap",
713
"HttpErrorInfo",
14+
"create_error_context_from_request",
15+
"create_error_response",
816
"exception_handling_decorator",
917
"to_exceptions_handlers_map",
1018
)

services/web/server/src/simcore_service_webserver/exception_handling/_factory.py

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import logging
2-
from typing import NamedTuple, TypeAlias
2+
from typing import Any, NamedTuple, TypeAlias
33

44
from aiohttp import web
55
from common_library.error_codes import create_error_code
@@ -35,6 +35,15 @@ class HttpErrorInfo(NamedTuple):
3535
ExceptionToHttpErrorMap: TypeAlias = dict[type[Exception], HttpErrorInfo]
3636

3737

38+
def create_error_context_from_request(request: web.Request) -> dict[str, Any]:
39+
return {
40+
"request": request,
41+
"request.remote": f"{request.remote}",
42+
"request.method": f"{request.method}",
43+
"request.path": f"{request.path}",
44+
}
45+
46+
3847
def create_error_response(error: ErrorGet, status_code: int) -> web.Response:
3948
assert is_error(status_code), f"{status_code=} must be an error [{error=}]" # nosec
4049

@@ -85,20 +94,19 @@ async def _exception_handler(
8594

8695
if is_5xx_server_error(status_code):
8796
oec = create_error_code(exception)
97+
error_context = {
98+
# NOTE: `error_context` is also used to substitute f-string tokens
99+
# in the `user_msg`
100+
**create_error_context_from_request(request),
101+
"error_code": oec,
102+
}
103+
88104
_logger.exception(
89105
**create_troubleshotting_log_kwargs(
90106
user_msg,
91107
error=exception,
92108
error_code=oec,
93-
error_context={
94-
# NOTE: context is also used to substitute tokens in the error message
95-
# e.g. "support error is {error_code}"
96-
"request": request,
97-
"request.remote": f"{request.remote}",
98-
"request.method": f"{request.method}",
99-
"request.path": f"{request.path}",
100-
"error_code": oec,
101-
},
109+
error_context=error_context,
102110
)
103111
)
104112
error = ErrorGet.model_construct(

0 commit comments

Comments
 (0)