Giter Site home page Giter Site logo

Comments (6)

jonatan-ivanov avatar jonatan-ivanov commented on May 2, 2024 1

most of the time folks want to customize the tags dynamically based on some business data (e.g. business entity, customer type, etc.). This data is often not part of the servlet request as parameter, body or header, rather collected as part of the business logic during the execution of the particular rest controller call.

I think if some data is missing from the Context, please let the project that instrument itself know so that they can add data to the Context that you can use. I feel though here it's not the case, if this data is collected as part of the business logic you might want to create a separate observation for that business logic and put whatever you want into the context.

as you can see, for the "top" 4 metrics, we have 3 different ways of propagating business data

I might not understand this but I think they are the same: add data to the Context somewhere and use that data elsewhere (I think Brian sees this the same way).

Looking at your code also tells me that you might want a new Observations for your business logic. Also, can't you do this instead:

observation.lowCardinalityKeyValues("someKey", someValue);

from spring-boot.

bclozel avatar bclozel commented on May 2, 2024 1

@hadjiski I think that when it comes to collecting information somewhere in the application and attaching it to an observation, you have 3 choices:

  1. add it as a new KeyValue to the observation, if the required information is already in the observation context. If it's not, as @jonatan-ivanov suggests, it might be something that is missing. If the information is really about "business code", then probably not.
  2. add the business information to the Observation.Context and then use it in the observation convention to add KeyValues.
  3. create a custom observation

If the information is local to the current request and is a cross cutting concern, like a customer id - it makes sense to add it to some ThreadLocal and apply it globally to many observations using an observation filter. If the information is even more global and completely static for the lifetime of the application, there are easier ways to globally contribute that information to all observations.

If this information is local to some business logic, then a dedicated observation probably makes sense. You shouldn't have to wrap all calls like observation.observe(() -> anyBsinessMethod(context)), but instead instrument business methods internally. You would then get the correlation with the current HTTP request and other data through tracing.

In all cases, I think that adding business data to server request attributes and using it as a carrier, out of band, to collect information for other observations, is not a great idea. It indeed looks like a missing custom observation in the application. At least this is how it looks with the information you've provided.

(btw. when it comes to pure metrics and their low cardinality values, so no traces, is using the observation.observe(...) in rough simple words just an equivalent to manually defining all the meters like counter, gauge or there is a difference between initiating a monitoring via an observation vs via a meterRegistry)

Yes, but Observation handlers will produce timers and traces. Counters and gauges don't really make sense with the Observation API. You could try to implement a custom Observation Handler to do this but I think you would easily see the limitations to that approach. For those, the Metrics API is still the way to go.

I don't think we can improve the documentation as the issue is not really about learning how to do something, but choosing a solution to collect the relevant information and thinking about the tradeoffs.

from spring-boot.

philwebb avatar philwebb commented on May 2, 2024

@jonatan-ivanov Do you have any thoughts on this one? I'm not sure that anyone on the Boot team has the experience to suggest how these things could be done. I'm also not sure where any extended documentation should live if it's needed.

from spring-boot.

bclozel avatar bclozel commented on May 2, 2024

Solutions 2) 3) and 4) all look similar to me:

  • getting the current observation
  • adding information to its Observation.Context directly and using those in the ObservationConvention
  • OR adding KeyValues directly to the Observation.Context as low/high cardinality

The fact that you can use Servlet request attributes as a context map is irrelevant here and I think this information really belongs to the Micrometer documentation, if it's not already there. I'm closing this issue as a result.

from spring-boot.

hadjiski avatar hadjiski commented on May 2, 2024

@bclozel, let me give you some concrete "running" code, so you can see the similarities, but also the differences. It took me some time to find a minimal approach, which seems to serve lots of cases and I think it is worth documenting it in some way:

...
@Autowired
private final ObservationRegistry observationRegistry;
...

Propagating...

public void pushBusinessDataToObservation(<some business data>) {

        // this makes sure that the current context (if available) will persist the business data
        Optional.ofNullable(observationRegistry.getCurrentObservation())
                .ifPresent(observation -> 
                                        observation.getContext().put("someKey", <some business data>));

        // this makes sure that the current http servlet request (if available) 
        // will persist the business data
        Optional.ofNullable(RequestContextHolder.getRequestAttributes())
                .ifPresent(requestAttributes -> 
                                       requestAttributes.setAttribute("someKey", <some business data>));
    }

Consuming...

public KeyValues getLowCardinalityKeyValuesFromCarrierAttributes(ServerRequestObservationContext context) {

        // this serves only http.server.requests metric and it is the only way to do it,
        // since during the pushing of the business data the current observation is a child 
        // of the http.server.requests one,
        // so propagating it via the http servlet request appears to be the only out of the box option

        return KeyValues.of(
                Optional.ofNullable(context)
                        .map(c -> c.getCarrier()
                                .getAttribute("someKey")
                        .orElse(<empty>)
                        .toArray(new String[0])
        );
    }

    public KeyValues getLowCardinalityKeyValuesFromContextMap(Observation.Context context) {

        // this serves all the cases, where a proper context object is accessible within the customizer
        // this is the case for: http.client.requests and tasks.scheduled.execution
        // we better use this provided context, since for example for tasks.scheduled.execution
       // the observationRegistry.getCurrentObservation() is 'null', so we lookup through the context chain
       // where is our business data

        MetricEnrichment found = null;

        ContextView currentObservationContextView = context;
        while (currentObservationContextView != null) {

            if ((found = currentObservationContextView.get("someKey")) != null) {

                break;
            }

            currentObservationContextView = 
                Optional.ofNullable(currentObservationContextView.getParentObservation())
                    .map(ObservationView::getContextView)
                    .orElse(null);
        }

        return KeyValues.of(
                Optional.ofNullable(found)
                        .orElse(<empty>)
                        .toArray(new String[0])
        );
    }

    public Iterable<Tag> getLowCardinalityKeyValuesFromCurrentContextMap() {

        // this serves all the cases, where no context is accessible within the customizer
        // this is the case for: spring.data.repository.invocations and all the mongodb.driver ones
        // this time, the current observation is properly put to the scope,
       // so the observationRegistry.getCurrentObservation() is available and 
       // we can lookup through its context chain where is our business data

        MetricEnrichment found = null;

        ObservationView currentObservationView = observationRegistry.getCurrentObservation();
        while (currentObservationView != null) {
            ContextView currentObservationContextView = currentObservationView.getContextView();

            if ((found = currentObservationContextView.get("someKey")) != null) {

                break;
            }

            currentObservationView = currentObservationContextView.getParentObservation();
        }

        return Tags.of(
                Optional.ofNullable(found)
                        .orElse(<empty>)
                        .toArray(new String[0])
        );
    }

from spring-boot.

hadjiski avatar hadjiski commented on May 2, 2024

@bclozel @jonatan-ivanov thanks for your comments

I think if some data is missing from the Context, please let the project that instrument itself know so that they can add data to the Context that you can use. I feel though here it's not the case

No no, you might have gotten me wrong, I don't miss data in the context, I just look for consistent elegant way to access dynamic business data during the customization of the tags, which naturally means, that it has to be pushed to the observation context manually beforehand like you mentioned here:

if this data is collected as part of the business logic you might want to create a separate observation for that business logic and put whatever you want into the context.

Though I did not want to solve it via extra observation (thanks for the suggestion), because this would add the "ugly" overhead of wrapping all code places with observation.observe(() -> anyBsinessMethod(context));

I might not understand this but I think they are the same: add data to the Context somewhere and use that data elsewhere (I think Brian sees this the same way)

Yes, that is true, but where to add it resp. how to consume it differs (as already mentioned):

  • http.server.requests's context is a parent's parent one of the business layer one (spring.security.http.secured.requests), so adding the business data to the context is not working as we cannot add data to the parent contexts and the only way is to pass it to the http servlet request, which is available in the business layer and also available in the customizer later as it is appended as a carrier
  • for http.client.requests and tasks.scheduled.execution we can perfectly utilize directly the current context as they live within the same context as the business layer and the context is available in the customizers
  • the spring data metrics are not even initiating a new observation, rather directly a meterRegistry metric, so no context is available in the customizers, but we still can utilize the current context just getting it via the autowired observationRegistry
    (btw. when it comes to pure metrics and their low cardinality values, so no traces, is using the observation.observe(...) in rough simple words just an equivalent to manually defining all the meters like counter, gauge or there is a difference between initiating a monitoring via an observation vs via a meterRegistry)

And exactly this I would like to see documented with code examples, which would save time researching through the whole monitoring topic.

Also, can't you do this instead

No, this appears not possible as the business logic is mostly within the spring.security.http.secured.requests context (in a standard rest controller spring boot app), so adding custom tags directly to the current observation in the business layer will end up in the wrong metric

from spring-boot.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.