Skip to content

Commit 0049779

Browse files
authored
Merge branch 'master' into is1701/pg-readonly-user
2 parents 179d03b + d7bb29e commit 0049779

File tree

34 files changed

+637
-245
lines changed

34 files changed

+637
-245
lines changed

.github/workflows/ci-testing-deploy.yml

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -276,6 +276,18 @@ jobs:
276276
fail-fast: false
277277
name: "[build] docker images"
278278
steps:
279+
- name: Remove unused software
280+
run: |
281+
echo "Available storage before:"
282+
sudo df -h
283+
echo
284+
sudo rm -rf /usr/share/dotnet
285+
sudo rm -rf /usr/local/lib/android
286+
sudo rm -rf /opt/ghc
287+
sudo rm -rf /opt/hostedtoolcache/CodeQL
288+
echo "Available storage after:"
289+
sudo df -h
290+
echo
279291
- uses: actions/checkout@v4
280292
- name: setup docker buildx
281293
id: buildx
@@ -2485,7 +2497,6 @@ jobs:
24852497
name: ${{ github.job }}_tracing
24862498
path: tests/e2e-playwright/test-results
24872499

2488-
24892500
system-test-environment-setup:
24902501
timeout-minutes: 30 # if this timeout gets too small, then split the tests
24912502
name: "[sys] environment setup"

packages/models-library/Makefile

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -110,14 +110,15 @@ erd-ServiceInput.svg: _erdantic
110110
DOWNLOADED_TEST_DATA_DIR = "$(CURDIR)/tests/data/.downloaded-ignore"
111111

112112
.PHONY: _httpx
113-
_httpx: _check_venv_active
113+
_ensure_httpx: _check_venv_active
114114
# ensures requirements installed
115115
@python3 -c "import httpx" 2>/dev/null || uv pip install httpx
116116

117-
PHONY: pull_test_data
118-
pull_test_data: $(DOT_ENV_FILE) _httpx ## downloads tests data from registry (this can take some time!)
119-
# downloading all metadata files
117+
PHONY: tests-data
118+
tests-data: $(DOT_ENV_FILE) _ensure_httpx ## downloads tests data from registry defined in .env (time-intensive!)
119+
# Downloading all metadata files ...
120120
@set -o allexport; \
121121
source $<; \
122122
set +o allexport; \
123123
python3 "$(PACKAGES_DIR)/pytest-simcore/src/pytest_simcore/helpers/docker_registry.py" $(DOWNLOADED_TEST_DATA_DIR)
124+
@echo "Run now 'pytest -vv -m diagnostics tests'"

packages/models-library/src/models_library/errors_classes.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,3 +30,7 @@ def _get_full_class_name(cls) -> str:
3030
)
3131
]
3232
return ".".join(reversed(relevant_classes))
33+
34+
def error_context(self):
35+
"""Returns context in which error occurred and stored within the exception"""
36+
return dict(**self.__dict__)

packages/models-library/tests/test_errors_classes.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,3 +148,14 @@ class MyErrorAfter(OsparcErrorMixin, ValueError):
148148
msg_template = "{value} and {missing}"
149149

150150
assert str(MyErrorAfter(value=42)) == "42 and 'missing=?'"
151+
152+
153+
def test_exception_context():
154+
class MyError(OsparcErrorMixin, ValueError):
155+
msg_template = "{value} and {missing}"
156+
157+
exc = MyError(value=42, missing="foo", extra="bar")
158+
assert exc.error_context() == {"value": 42, "missing": "foo", "extra": "bar"}
159+
160+
exc = MyError(value=42)
161+
assert exc.error_context() == {"value": 42}

packages/notifications-library/src/notifications_library/templates/on_account_form.email.content.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
<p>Dear Support team
55

66
<p>
7-
We have received the following request form for an account in {{ product.display_name }} from {{ host }}
7+
We have received the following request form for an account in {{ product.display_name }} from <b>{{ host }}</b>
88
</p>
99

1010
<pre>

packages/notifications-library/src/notifications_library/templates/on_account_form.email.content.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
Dear Support team,
22

3-
We have received the following request form for an account in {{ product.display_name }} from {{ host }}:
3+
We have received the following request form for an account in {{ product.display_name }} from **{{ host }}**:
44

55
{{ dumps(request_form) }}
66

packages/service-library/src/servicelib/aiohttp/rest_middlewares.py

Lines changed: 30 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,11 @@
1212
from aiohttp import web
1313
from aiohttp.web_request import Request
1414
from aiohttp.web_response import StreamResponse
15+
from models_library.errors_classes import OsparcErrorMixin
1516
from models_library.utils.json_serialization import json_dumps
17+
from servicelib.error_codes import create_error_code
1618

19+
from ..logging_utils import create_troubleshotting_log_message, get_log_record_extra
1720
from ..mimetype_constants import MIMETYPE_APPLICATION_JSON
1821
from ..utils import is_production_environ
1922
from .rest_models import ErrorItemType, ErrorType, LogMessageType
@@ -28,6 +31,7 @@
2831
from .typing_extension import Handler, Middleware
2932

3033
DEFAULT_API_VERSION = "v0"
34+
FMSG_INTERNAL_ERROR_USER_FRIENDLY = "We apologize for the inconvenience. Our team has recorded the issue and is working to resolve it as quickly as possible. Thank you for your patience [{}]"
3135

3236

3337
_logger = logging.getLogger(__name__)
@@ -40,29 +44,41 @@ def is_api_request(request: web.Request, api_version: str) -> bool:
4044

4145
def error_middleware_factory(
4246
api_version: str,
43-
log_exceptions: bool = True,
4447
) -> Middleware:
4548
_is_prod: bool = is_production_environ()
4649

4750
def _process_and_raise_unexpected_error(request: web.BaseRequest, err: Exception):
51+
52+
error_code = create_error_code(err)
53+
error_context: dict[str, Any] = {
54+
"request.remote": f"{request.remote}",
55+
"request.method": f"{request.method}",
56+
"request.path": f"{request.path}",
57+
}
58+
if isinstance(err, OsparcErrorMixin):
59+
error_context.update(err.error_context())
60+
61+
frontend_msg = FMSG_INTERNAL_ERROR_USER_FRIENDLY.format(error_code)
62+
log_msg = create_troubleshotting_log_message(
63+
message_to_user=frontend_msg,
64+
error=err,
65+
error_code=error_code,
66+
error_context=error_context,
67+
)
68+
4869
http_error = create_http_error(
4970
err,
50-
"Unexpected Server error",
71+
frontend_msg,
5172
web.HTTPInternalServerError,
5273
skip_internal_error_details=_is_prod,
5374
)
54-
55-
if log_exceptions:
56-
_logger.error(
57-
'Unexpected server error "%s" from access: %s "%s %s". Responding with status %s',
58-
type(err),
59-
request.remote,
60-
request.method,
61-
request.path,
62-
http_error.status,
63-
exc_info=err,
64-
stack_info=True,
65-
)
75+
_logger.exception(
76+
log_msg,
77+
extra=get_log_record_extra(
78+
error_code=error_code,
79+
user_id=error_context.get("user_id"),
80+
),
81+
)
6682
raise http_error
6783

6884
@web.middleware

packages/service-library/src/servicelib/aiohttp/rest_responses.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ def create_http_error(
110110
error = ErrorType(
111111
errors=items,
112112
status=http_error_cls.status_code,
113-
message=items[0].message if items else default_message,
113+
message=default_message,
114114
)
115115

116116
assert not http_error_cls.empty_body # nosec

packages/service-library/src/servicelib/logging_utils.py

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@
1616
from pathlib import Path
1717
from typing import Any, TypeAlias, TypedDict, TypeVar
1818

19+
from models_library.utils.json_serialization import json_dumps
20+
21+
from .error_codes import ErrorCodeStr
1922
from .utils_secrets import mask_sensitive_data
2023

2124
_logger = logging.getLogger(__name__)
@@ -320,20 +323,58 @@ def log_catch(logger: logging.Logger, *, reraise: bool = True) -> Iterator[None]
320323

321324
class LogExtra(TypedDict, total=False):
322325
log_uid: str
326+
log_oec: str
323327

324328

325329
LogLevelInt: TypeAlias = int
326330
LogMessageStr: TypeAlias = str
327331

328332

329-
def get_log_record_extra(*, user_id: int | str | None = None) -> LogExtra | None:
333+
def get_log_record_extra(
334+
*,
335+
user_id: int | str | None = None,
336+
error_code: str | None = None,
337+
) -> LogExtra | None:
330338
extra: LogExtra = {}
339+
331340
if user_id:
332341
assert int(user_id) > 0 # nosec
333342
extra["log_uid"] = f"{user_id}"
343+
if error_code:
344+
extra["log_oec"] = error_code
345+
334346
return extra or None
335347

336348

349+
def create_troubleshotting_log_message(
350+
message_to_user: str,
351+
error: BaseException,
352+
error_code: ErrorCodeStr,
353+
error_context: dict[str, Any] | None = None,
354+
tip: str | None = None,
355+
) -> str:
356+
"""Create a formatted message for _logger.exception(...)
357+
358+
Arguments:
359+
message_to_user -- A user-friendly message to be displayed on the front-end explaining the issue in simple terms.
360+
error -- the instance of the handled exception
361+
error_code -- A unique error code (e.g., OEC or osparc-specific) to identify the type or source of the error for easier tracking.
362+
error_context -- Additional context surrounding the exception, such as environment variables or function-specific data. This can be derived from exc.error_context() (relevant when using the OsparcErrorMixin)
363+
tip -- Helpful suggestions or possible solutions explaining why the error may have occurred and how it could potentially be resolved
364+
"""
365+
debug_data = json_dumps(
366+
{
367+
"exception_details": f"{error}",
368+
"error_code": error_code,
369+
"context": error_context,
370+
"tip": tip,
371+
},
372+
indent=1,
373+
)
374+
375+
return f"{message_to_user}.\n{debug_data}"
376+
377+
337378
def _un_capitalize(s: str) -> str:
338379
return s[:1].lower() + s[1:] if s else ""
339380

packages/service-library/tests/aiohttp/long_running_tasks/conftest.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,8 @@ async def _string_list_task(
3333
await asyncio.sleep(sleep_time)
3434
task_progress.update(message="generated item", percent=index / num_strings)
3535
if fail:
36-
raise RuntimeError("We were asked to fail!!")
36+
msg = "We were asked to fail!!"
37+
raise RuntimeError(msg)
3738

3839
# NOTE: this code is used just for the sake of not returning the default 200
3940
return web.json_response(

0 commit comments

Comments
 (0)