Skip to content

✨ Maintenance: Improves troubleshooting dump message used to log 5XX errors #7473

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Apr 4, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
""" Helpers for json serialization
- built-in json-like API
- implemented using orjson, which performs better. SEE https://github.com/ijl/orjson?tab=readme-ov-file#performance
"""Helpers for json serialization
- built-in json-like API
- implemented using orjson, which performs better. SEE https://github.com/ijl/orjson?tab=readme-ov-file#performance
"""

import datetime
Expand Down Expand Up @@ -118,6 +118,28 @@ def pydantic_encoder(obj: Any) -> Any:
raise TypeError(msg)


def representation_encoder(obj: Any):
"""
A fallback encoder that uses `pydantic_encoder` to serialize objects.
If serialization fails, it falls back to using `str(obj)`.
This is practical for representation purposes, such as logging or debugging.
Example:
>>> from common_library.json_serialization import json_dumps, representation_encoder
>>> class CustomObject:
... def __str__(self):
... return "CustomObjectRepresentation"
>>> obj = CustomObject()
>>> json_dumps(obj, default=representation_encoder)
'"CustomObjectRepresentation"'
"""
try:
return pydantic_encoder(obj)
except TypeError:
return str(obj)


def json_dumps(
obj: Any,
*,
Expand Down
23 changes: 23 additions & 0 deletions packages/common-library/tests/test_json_serialization.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
SeparatorTuple,
json_dumps,
json_loads,
representation_encoder,
)
from faker import Faker
from pydantic import AnyHttpUrl, AnyUrl, BaseModel, Field, HttpUrl, TypeAdapter
Expand Down Expand Up @@ -110,3 +111,25 @@ class M(BaseModel):
http_url=faker.url(),
)
json_dumps(obj)


def test_json_dumps_with_representation_encoder():
class CustomObject:
def __str__(self):
return "CustomObjectRepresentation"

class SomeModel(BaseModel):
x: int

obj = {
"custom": CustomObject(),
"some": SomeModel(x=42),
}

# Using representation_encoder as the default encoder
result = json_dumps(obj, default=representation_encoder, indent=1)

assert (
result
== '{\n "custom": "CustomObjectRepresentation",\n "some": {\n "x": 42\n }\n}'
)
9 changes: 5 additions & 4 deletions packages/service-library/src/servicelib/logging_errors.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import logging
from pprint import pformat
from typing import Any, TypedDict

from common_library.error_codes import ErrorCodeStr
from common_library.errors_classes import OsparcErrorMixin
from common_library.json_serialization import json_dumps, representation_encoder

from .logging_utils import LogExtra, get_log_record_extra

Expand All @@ -27,14 +27,15 @@ def create_troubleshotting_log_message(
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)
tip -- Helpful suggestions or possible solutions explaining why the error may have occurred and how it could potentially be resolved
"""
debug_data = pformat(
debug_data = json_dumps(
{
"exception_type": f"{type(error)}",
"exception_details": f"{error}",
"error_code": error_code,
"context": pformat(error_context, indent=1),
"context": error_context,
"tip": tip,
},
default=representation_encoder,
indent=1,
)

Expand Down Expand Up @@ -82,7 +83,7 @@ def create_troubleshotting_log_kwargs(
error=error,
error_code=error_code,
error_context=context,
tip=tip,
tip=tip or getattr(error, "tip", None),
)

return {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -362,7 +362,8 @@ async def get_my_profile(
)
except GroupExtraPropertiesNotFoundError as err:
raise MissingGroupExtraPropertiesForProductError(
user_id=user_id, product_name=product_name
user_id=user_id,
product_name=product_name,
) from err

return my_profile, preferences
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@
from ..errors import WebServerBaseError


class UsersBaseError(WebServerBaseError):
...
class UsersBaseError(WebServerBaseError): ...


class UserNotFoundError(UsersBaseError):
Expand Down Expand Up @@ -64,3 +63,4 @@ class BillingDetailsNotFoundError(UsersBaseError):

class MissingGroupExtraPropertiesForProductError(UsersBaseError):
msg_template = "Missing group_extra_property for product_name={product_name}"
tip = "Add a new row in group_extra_property table and assign it to this product"
Loading