Skip to content
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

[instrumentation-http] custom client/server metric attributes #5051

Closed
4 of 6 tasks
pichlermarc opened this issue Oct 7, 2024 · 1 comment
Closed
4 of 6 tasks

[instrumentation-http] custom client/server metric attributes #5051

pichlermarc opened this issue Oct 7, 2024 · 1 comment

Comments

@pichlermarc
Copy link
Member

pichlermarc commented Oct 7, 2024

Description

Important

The existence of this issue does not reflect the wish of the author for this to be implemented
This is a feature request that tracks issues created over time and provides details from the viewpoint of a maintainer which may not be immediately obvious to end-users. It serves as a basis for further discussions.

Status quo

Over time we've added quite a few so-called "hooks" to the HttpInstrumentation that allow for customization of the emitted telemetry. This allows the addition of custom attributes the Spans created by the instrumentation.

A downside of this is that the more we add these hooks, the more it allows the user to change the telemetry that's being emitted from what is defined in the semantic conventions. Another downside of this is increased maintainance burden for maintainers, approvers and triagers - as the code becomes more complex, more bugs become possible and bug reports become harder and harder to work with. The interface also becomes more and more complex for users, and it leads users down a path where it becomes an expectation for the user that application-specific instrumentation happens in instrumentation-hooks and through the addition of attributes instead of creating their own child spans using the @opentelemetry/api which contain the needed data.

For metrics, though, no such hooks exist, they would come with the same risks and challenges as the existing hooks in HttpInstrumentation.

Common Feature request

A common request is that the same treatment that's given to Spans be given to metrics emitted by the HttpInstrumentation.

Here the case seems easy enough to make: There's no easy way to instrument all http calls made through the node:http and node:https and re-create the metrics in a different way, with added attributes. It stands therefore to reason that such a hook is necessary to allow the addition of custom-non-standard attributes to this metric.

The most common request, is the one of adding http.route. http.route is not collected by default as the HttpInstrumentation on it's own cannot safely determine a low-cardinality route, since it does not have access to the routing information of a higher-level package. Without http.route the emitted metrics become essentially useless, unless only a single endpoint is served by the service.

Instrumentations, such as @opentelemetry/instrumentation-express work around this limitation by using a feature of @opentelemetry/core that allows instrumentation on the server framework to attach metadata to the context. This metadata is then read back by @opentelemetry/instrumentation-http to and attached to both the Span's attributes and the Histogram datapoint that is being recorded.

Example 1 (excerpt from instrumentation-express):

const rpcMetadata = getRPCMetadata(context.active()); // retrieve rpc metadata from the active context
if (rpcMetadata?.type === RPCType.HTTP) {
    rpcMetadata.route = route || '/'; // set the route, http instrumentation will pick that up and attach it to the span/metric as the `http.route` attribute
}

Here @opentelemetry/instrumentation-express and similar instrumentations do ensure that the route value is low-cardinality.

Users may take a similar approach as @opentelemetry/instrumentation-express does, by setting the metadata on the context themselves, either during the handling of the HTTP request on the server-side or in one of the hook offered by @opentelemetry/instrumentation-http, which comes out to be the same thing. While doing so, users have to be careful to ensure that the values provided as that metadata actually reasonable match that of a route - but in essence, this feature already exists for the http.route attribute. It does however, need a global ContextManager to be registered, which may not be the case when only exporting metrics from an app, and enabling the ContextManager introduces overhead.

Example 2 (adding metadata when handling the request):

const server = http.createServer((req, res) => {
    const rpcMetadata = getRPCMetadata(context.active()); // retrieve rpc metadata from the active context
    if (rpcMetadata?.type === RPCType.HTTP) {
        rpcMetadata.route = 'my-custom-route'
     }
    res.statusCode = 200;
    res.setHeader('Content-Type', 'text/plain');
    res.end('Hello, World!\n');
});

The second most common request is the one of adding arbitrary attributes to client and server metrics. For that, there is currently no workaround. The reasons for that can be found in the following section, "Risks".

Risks

Currently this feature being hard to use for http.route and not available at all for custom metrics has prevented less-metrics-savvy users from breaking their apps through high-cardinality metrics.

The question comes up from time-to-time to add attributes such as http.url (which can really be any URL) to a metric (example). Such a metric can cause significant problems in the user's app and downstream. Any attribute value creates a new metrics stream, and any metrics stream has to stay in-memory over the lifetime of the app to ensure that cumulative metrics work as-intended. This means that failure of the user to select an appropriate attribute will cause their app to run out of memory and crash. Further, their app will suffer an signficant performance impact from many newly created metrics streams.

To alleviate this issue, we will need to implement #4095 first. This will solve the problem of the crash, but the significant performance impact will remain if this interface is misused. Regardless of implementing this issue, the effects on downstream system can also be catatstropic, from out-of-memory OpenTelemetry Collectors and possible scaling problems of collector pipelines, down to excessive cost to the user for storing these high-cardinality metrics.

A possible path to implementing the feature

At first, we (@open-telemetry/javascript-maintainers) need to make the following decision:

Do we even want to support adding custom attributes for metrics?

Note

Update 06 Nov 2024, we've decided to go with this approach do not start working on implementing this actual feature. For now contributions are welcome on the prerequisites as they are more general features required by the specification. Only then we will allow the actual feature to be added.

If the answer to that question is YES (Update 06 Nov 2024, we've decided to go with this approach):

Outdated (06 Nov 2024) If the answer to that question is NO:

This issue is considered done when

Links

Previous requests

Resources referencing other implementation of this feature

Attachment 1 (proposed API)

Note

outdated, we will use the poposed API from Attachment 2 with no safeguard

new HttpInstrumentation({
  incomingRequestMetricAttributeHook: {
    // required property, not optional.
    possibleAttributes: {
      'http.route': [ 'my/route/foo',  'my/route/bar' ]
      'my.custom.attribute': ['foo', 'bar', 'baz']
    },
    (request) => {
       return {
           'http.route': determineRoute(request.url), // value will be filtered by instrumentation-internal logic if value does not match one of the values above, and replaced by "filtered" or "unknown"
           'my.custom.attribute': 'foo123', // gets filtered because it does not match a value from the possible values for 'my.custom.attribute' above, and replaced by "filtered" or "unknown"
           'my.unknown.attribute': 'foo', //gets removed completely from attributes as the key is not known above.
       }
    }
  } 
});

(this is a proposal I created after @dyladan's suggestion to design the interface in a way that makes it hard/impossible to misuse)

Attachment 2 (proposed API with no safeguards)

new HttpInstrumentation({
  incomingRequestMetricAttributeHook: (request) => { 
      return {
           // it is up to the user to return a reasonable amount of key-value pairs here, this needs to be properly documented.
           'http.route': determineRoute(request.url), // functionality to determine route is up to the user, it's up to the user to ensure that data is not high cardinality
           'my.custom.attribute': 'foo123, // any custom attributes are allowed 
       };
   },
   // same behavior for outgoing hook
});
@pichlermarc
Copy link
Member Author

Hi all, I'm closing this feature-request as ACCEPTED as it has been refined into #5135.

It will be ready for implementation once #4095 and #4096 are implemented. #5135 will track implementation and is blocked until the aforementioned prerequisite issues are done.

@pichlermarc pichlermarc removed the needs:refinement This issue needs to be refined/broken apart into sub-issues before implementation label Nov 11, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant