Skip to content

Support (auto) instrument Spring 7 interface declarative HTTP clients #16412

@holgerstolzenberg

Description

@holgerstolzenberg

Is your feature request related to a problem? Please describe.

I am currently integrating "opentelemetry-spring-boot-starter" with a Spring Boot v4 application.
Whereas nearly everything worked easily OOTB, I had some struggles getting a Spring Framework 7 interface based HTTP client instrumented properly.

In order to solve the issue, I ended up adding a custom interceptor implementation. One smaller obstacle was the fact that for whatever reason I cannot import classes like SpringWebMvcTelemetry.

I'll provide the bean definition of the client and the custom interceptor here, so others can use that in the mean time.

public interface AuditEventClient {
  @WithSpan
  @PostExchange(url = "/audit-events", contentType = AuditEvent.MIME_TYPE)
  void pushEvent(
      @RequestParam @SpanAttribute("audit.event.mode") final Mode mode,
      @RequestBody @SpanAttribute("audit.event") final AuditEvent event);
}

@Configuration
class AuditTrailConfiguration {
  private static final String INTERNAL_CLIENT = "internal-client";

  @Bean
  AuditEventClient auditEventClient(
      final ClientRegistrationRepository repository,
      final OAuth2AuthorizedClientService service,
      final RestClient.Builder builder,
      final AuditTrailConfigurationProperties properties,
      final ObjectProvider<OpenTelemetry> openTelemetryProvider) {
    val authorizedClientManager =
        new AuthorizedClientServiceOAuth2AuthorizedClientManager(repository, service);

    val oauthHttpRequestInterceptor =
        new OAuth2ClientHttpRequestInterceptor(authorizedClientManager);
    oauthHttpRequestInterceptor.setClientRegistrationIdResolver(_ -> INTERNAL_CLIENT);

    val openTelemetry = openTelemetryProvider.getIfAvailable();
    if (isNull(openTelemetry)) {
      throw new IllegalStateException("OpenTelemetry is not available");
    }

    val baseUrl = properties.client().baseUrl();
    val restClient =
        builder
            .baseUrl(baseUrl)
            .requestInterceptor(OpenTelemetryClientHttpRequestInterceptor.with(openTelemetry)) // ‼️ manual registration necessary
            .requestInterceptor(oauthHttpRequestInterceptor)
            .build();
    val exchangeAdapter = RestClientAdapter.create(restClient);

    return HttpServiceProxyFactory.builderFor(exchangeAdapter)
        .build()
        .createClient(AuditEventClient.class);
  }
}

// custom implementation, maybe there is a re-usable one already?
@UtilityClass
public final class OpenTelemetryClientHttpRequestInterceptor {

  @Nonnull
  public static ClientHttpRequestInterceptor with(@Nonnull final OpenTelemetry openTelemetry) {
    checkNotNull(openTelemetry, "openTelemetry");

    return (request, body, execution) -> {
      openTelemetry
          .getPropagators()
          .getTextMapPropagator()
          .inject(
              Context.current(), request, (req, key, value) -> req.getHeaders().set(key, value));
      return execution.execute(request, body);
    };
  }
}

Describe the solution you'd like

It would be cool if the spring boot starter just automatically instruments these clients as it instruments RestClient, WebClient etc.

Describe alternatives you've considered

  • I tried Arconia Framework but had other issues.
  • I tried new spring-boot-starter-telemetry but it looks like if currently lacks an option to push metrics via grpc

Additional context

None.

Tip

React with 👍 to help prioritize this issue. Please use comments to provide useful context, avoiding +1 or me too, to help us triage it. Learn more here.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions