External object adapters
External object adapters let LogPayloadProcessor use a custom logging representation for objects that should not implement to_log_payload() themselves.
This is useful when the object type belongs to another package, is shared across layers, or should stay independent from the logger API.
The object remains unchanged. The logging representation is provided from the outside.
Adapter resolver
Adapters are connected through log_adapter_resolver.
The resolver receives the original value and returns either an adapter or None.
from typing import Any
def resolver(value: Any):
...
If the resolver returns None, LogPayloadProcessor continues with generic normalization.
If the resolver returns an adapter, the processor calls it with two arguments:
adapter(value, verbosity_level)
The adapter must return a dictionary.
If it returns a dictionary, that dictionary is used as the custom payload representation.
If it returns something else, or raises an exception, the processor ignores the adapter result and falls back to generic normalization.
Adapter shape
A practical adapter has this shape:
from typing import Any
from mvx.common.logger import LogVerbosityLevel
def user_adapter(
value: Any,
verbosity_level: LogVerbosityLevel,
) -> dict[str, Any]:
...
The first argument is the original value.
The second argument is the effective verbosity level of the processor.
The adapter decides how the value should be represented in logs.
Minimal example
Suppose there is a class that should not depend on MVX Logger.
from dataclasses import dataclass
@dataclass(frozen=True, slots=True)
class ExternalUser:
user_id: str
email: str
internal_token: str
The class does not implement to_log_payload().
A separate adapter can define its logging representation:
from typing import Any
from mvx.common.logger import LogVerbosityLevel
def external_user_adapter(
value: ExternalUser,
verbosity_level: LogVerbosityLevel,
) -> dict[str, Any]:
payload: dict[str, Any] = {
"user_id": value.user_id,
}
if verbosity_level == LogVerbosityLevel.MAXIMUM:
payload["email"] = value.email
payload["internal_token"] = "***"
return payload
The resolver chooses this adapter for ExternalUser values:
from typing import Any
def resolver(value: Any):
if isinstance(value, ExternalUser):
return external_user_adapter
return None
Then the resolver is passed to LogPayloadProcessor:
from mvx.common.logger import LogPayloadProcessor
processor = LogPayloadProcessor(
log_adapter_resolver=resolver,
)
When the processor normalizes an ExternalUser value, it uses the adapter result.
Verbosity-aware adapters
Adapters receive the effective processor verbosity level.
This allows them to return different payloads for different logging depth modes.
def external_user_adapter(
value: ExternalUser,
verbosity_level: LogVerbosityLevel,
) -> dict[str, Any]:
if verbosity_level == LogVerbosityLevel.MINIMAL:
return {
"user_id": value.user_id,
}
return {
"user_id": value.user_id,
"email": value.email,
"internal_token": "***",
}
The adapter should still keep the payload bounded and safe.
MAXIMUM should mean more useful diagnostic detail, not uncontrolled object dumping.
Priority
to_log_payload() has priority over adapters.
The order is:
to_log_payload()
|
v
log_adapter_resolver
|
v
generic normalization rules
If a value provides a valid to_log_payload() result, the adapter resolver is not used for that value.
Adapters are used only when the object does not provide its own valid payload.
Fallback behavior
Adapter usage is safe by design.
The processor falls back to generic normalization when:
no resolver is configured;
the resolver returns
None;the resolver raises an exception;
the adapter raises an exception;
the adapter returns a value that is not a dictionary.
In these cases, logging continues and the value is handled by the usual normalization rules.
When to use adapters
Use an adapter when the logging representation should stay outside the object type.
Typical cases:
the object comes from another package;
the object belongs to a lower-level layer that should not know about logging;
different applications need different logging representations for the same object;
logging output must be controlled by application configuration;
sensitive fields must be masked without modifying the object class.
Use to_log_payload() when the logging representation is part of the object’s own public behavior.
Use an adapter when the representation is external to the object.
What to remember
Adapters provide external logging representations for objects.
log_adapter_resolverchooses an adapter for a value.The adapter receives the original value and the effective verbosity level.
The adapter must return a dictionary to be accepted.
to_log_payload()takes priority over adapters.Invalid adapter results and adapter errors are ignored, and generic normalization is used as fallback.