(shameless plug i recently wrote about how decorators can be used to keep the domain metric/plumbing free)
https://medium.com/dm03514-tech-blog/designpatterns-consider...
(also just disclaimer: Team's i've been on have had lots of success using the approach outlined in the article, injecting a metrics/instrumentation object, it cleans up the domain logic, is easy to provide a stub implementation during tests, and abstracts the from metric implementations.
------
A decorator version might compose the production code by wrapping the domain logic in metric adapters:
metrics = InstantiateMetricsClient()
ObservableShoppingCart(
ShoppingCart(
ObservableDiscountService(
DiscountService(
),
metrics=metrics,
)
)
metrics=metrics,
)
----Another option that I've seen be used successfully is to completely decouple domain events and the surfacing of domain events by having the domain generically emit events, and the logging/metrics subscribe and surface those however it decides.
----
I feel like for normal logging statements and metrics it only borders on being an issue, but when metrics include many timing wrappers, or when tracing (which involves much more reporting) is involved explicit adapters help to really keep things clean.