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

Exceptions thrown by custom error handlers are not recorded in RestTemplate observations #32060

Closed
Vsevolod123 opened this issue Jan 19, 2024 · 6 comments
Assignees
Labels
in: web Issues in web modules (web, webmvc, webflux, websocket) status: backported An issue that has been backported to maintenance branches theme: observability An issue related to observability and tracing type: bug A general bug
Milestone

Comments

@Vsevolod123
Copy link

RestTemplate set error to observation only for IOException and RestClientException and there is no way to customize that.

I tried error handler like this

@RequiredArgsConstructor
public class DefaultResponseRestTemplateErrorHandler extends DefaultResponseErrorHandler {
    private final ObservationRegistry observationRegistry;

    @Override
    public void handleError(@NonNull ClientHttpResponse clientHttpResponse) throws IOException {
        final var status = (HttpStatus) clientHttpResponse.getStatusCode();
        final var exception = switch (status) {
            case BAD_REQUEST -> new RuntimeException("bad request");
            case UNAUTHORIZED -> new RuntimeException("unauthorized");
            case FORBIDDEN -> new RuntimeException("forbidden");
            case GONE -> new RuntimeException("gone");
            default -> new RuntimeException("other");
        };
        
        Optional.ofNullable(observationRegistry.getCurrentObservation())
                                .map(Observation::getContext)
                                .filter(ClientRequestObservationContext.class::isInstance)
                                .map(ClientRequestObservationContext.class::cast)
                                .ifPresent(context -> context.setError(exception));
                                
        throw exception;
    }
}

But observationRegistry#getCurrentObservation returns parent Observation (in my case http.server.requests).
We use spring-web 6.0.14.

I think there should be some way for customization.

@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged or decided on label Jan 19, 2024
@jhoeller jhoeller added in: web Issues in web modules (web, webmvc, webflux, websocket) theme: observability An issue related to observability and tracing labels Jan 19, 2024
@bclozel
Copy link
Member

bclozel commented Jan 19, 2024

I'm not sure I understand the goal of this customization. You can throw RestClientException instances from the error handler and they will be recorded as errors in the observation. As for why you can't get the observation as current in the error handler, this is because we didn't open a scope for the processing of the request; this could be useful if developers want to log custom error messages in their error handler and expect trace ids to be present.

Before repurposing this issue in that direction, I would like to get your feedback about throwing RestClientException instead. Thanks!

@bclozel bclozel added the status: waiting-for-feedback We need additional information before we can continue label Jan 19, 2024
@Vsevolod123
Copy link
Author

Unfortunately, it's very complicated to redesign 200+ applications in that way.

In fact, I've already been able to achive that what I need with ObservationHandler like this

public class RestTemplateObservationHandler implements ObservationHandler<ClientRequestObservationContext> {
    private static final ThreadLocal<ClientRequestObservationContext> CONTEXT_THREAD_LOCAL = new ThreadLocal<>();

    public static Optional<ClientRequestObservationContext> getCurrentContext() {
        return Optional.ofNullable(CONTEXT_THREAD_LOCAL.get());
    }

    @Override
    public void onStart(@NonNull ClientRequestObservationContext context) {
        CONTEXT_THREAD_LOCAL.set(context);
    }

    @Override
    public void onStop(@NonNull ClientRequestObservationContext context) {
        CONTEXT_THREAD_LOCAL.remove();
    }

    @Override
    public boolean supportsContext(@NonNull Observation.Context context) {
        return context instanceof ClientRequestObservationContext;
    }
}

and RestTemplateCustomizer (from spring-boot) like this

@Bean
RestTemplateCustomizer addObservationErrorHandlerRestTemplateBuilderCustomizer() {
    return restTemplate -> {
        final var originalErrorHandler = restTemplate.getErrorHandler();
        final var observationErrorHandler = new ResponseErrorHandler() {
            @Override
            public boolean hasError(@NonNull ClientHttpResponse response) throws IOException {
                return originalErrorHandler.hasError(response);
            }

            @Override
            public void handleError(@NonNull ClientHttpResponse response) throws IOException {
                try {
                    originalErrorHandler.handleError(response);
                } catch (Exception e) {
                    RestTemplateObservationHandler.getCurrentContext()
                            .ifPresent(context -> context.setError(e));
                    throw e;
                }
            }
        };

        restTemplate.setErrorHandler(observationErrorHandler);
    };
 }

But it seems strange that WebClient handles all exception types while RestTemplate handles only RestClientException.

I don't even know if it's worth closing the task.

@bclozel
Copy link
Member

bclozel commented Jan 19, 2024

So you mean that if all exceptions would be recorded as errors by the observations this would solve the issue for you? I thought the main problem was about getting the current observation.

@spring-projects-issues spring-projects-issues added status: feedback-provided Feedback has been provided and removed status: waiting-for-feedback We need additional information before we can continue labels Jan 19, 2024
@Vsevolod123
Copy link
Author

Yes, you are right.
I think that problem with currentObservation is on the micrometer side.

@bclozel
Copy link
Member

bclozel commented Jan 19, 2024

No, that's not what I said.
Currently the instrumentation in RestTemplate records only IOException and RestClientException exception as errors in the observation. This is because the actual HTTP exchange can only throw such exceptions. This would be a bit artificial, but we could instead record all Throwable as errors - considering that error handlers throwing another type of exception should also be recorded as an error. Would this solve the problem for you?

@Vsevolod123
Copy link
Author

Custom ResponseErrorHandler could throw any Throwable.

Yes, this will solve problem for me.

@bclozel bclozel added type: bug A general bug and removed status: waiting-for-triage An issue we've not yet triaged or decided on status: feedback-provided Feedback has been provided labels Jan 19, 2024
@bclozel bclozel self-assigned this Jan 19, 2024
@bclozel bclozel added this to the 6.1.4 milestone Jan 19, 2024
@bclozel bclozel changed the title Unable to get current observation with RestTemplate Exceptions thrown by custom error handlers are not recorded in RestTemplate observations Jan 19, 2024
@bclozel bclozel added the for: backport-to-6.0.x Marks an issue as a candidate for backport to 6.0.x label Jan 19, 2024
@github-actions github-actions bot added status: backported An issue that has been backported to maintenance branches and removed for: backport-to-6.0.x Marks an issue as a candidate for backport to 6.0.x labels Jan 19, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
in: web Issues in web modules (web, webmvc, webflux, websocket) status: backported An issue that has been backported to maintenance branches theme: observability An issue related to observability and tracing type: bug A general bug
Projects
None yet
Development

No branches or pull requests

4 participants