All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.turbospaces.http.CustomMicrometerHttpRequestExecutor Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2019 VMware, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.turbospaces.http;

import io.micrometer.common.lang.Nullable;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Tag;
import io.micrometer.core.instrument.Tags;
import io.micrometer.core.instrument.Timer;
import io.micrometer.core.instrument.binder.httpcomponents.ApacheHttpClientContext;
import io.micrometer.core.instrument.binder.httpcomponents.ApacheHttpClientObservationConvention;
import io.micrometer.core.instrument.binder.httpcomponents.DefaultApacheHttpClientObservationConvention;
import io.micrometer.core.instrument.binder.httpcomponents.DefaultUriMapper;
import io.micrometer.core.instrument.observation.ObservationOrTimerCompatibleInstrumentation;
import io.micrometer.observation.Observation;
import io.micrometer.observation.ObservationRegistry;
import org.apache.http.HttpClientConnection;
import org.apache.http.HttpException;
import org.apache.http.HttpRequest;
import org.apache.http.HttpResponse;
import org.apache.http.protocol.HttpContext;
import org.apache.http.protocol.HttpRequestExecutor;

import java.io.IOException;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.function.Function;

/**
 * Copy of  MicrometerHttpRequestExecutor(1.2.0) with additional tag mapping functionality.
 * exportTagsForRoute is disabled by default.
 */
public class CustomMicrometerHttpRequestExecutor extends HttpRequestExecutor {

    static final String METER_NAME = "httpcomponents.httpclient.request";

    private final MeterRegistry registry;

    private final ObservationRegistry observationRegistry;

    @Nullable
    private final ApacheHttpClientObservationConvention convention;

    private final Function uriMapper;
    private final Function> additionalTagsMapper = new AdditionalTagsMapper();

    private final Iterable extraTags;

    private final boolean exportTagsForRoute;

    /**
     * Use {@link #builder(MeterRegistry)} to create an instance of this class.
     */
    private CustomMicrometerHttpRequestExecutor(int waitForContinue, MeterRegistry registry,
                                                Function uriMapper, Iterable extraTags, boolean exportTagsForRoute,
                                                ObservationRegistry observationRegistry, @Nullable ApacheHttpClientObservationConvention convention) {
        super(waitForContinue);
        this.registry = Optional.ofNullable(registry).orElseThrow(
                () -> new IllegalArgumentException("registry is required but has been initialized with null"));
        this.uriMapper = Optional.ofNullable(uriMapper).orElseThrow(
                () -> new IllegalArgumentException("uriMapper is required but has been initialized with null"));
        this.extraTags = Optional.ofNullable(extraTags).orElse(Collections.emptyList());
        this.exportTagsForRoute = exportTagsForRoute;
        this.observationRegistry = observationRegistry;
        this.convention = convention;
    }

    /**
     * Use this method to create an instance of {@link CustomMicrometerHttpRequestExecutor}.
     *
     * @param registry The registry to register the metrics to.
     * @return An instance of the builder, which allows further configuration of the
     * request executor.
     */
    public static Builder builder(MeterRegistry registry) {
        return new Builder(registry);
    }

    @Override
    public HttpResponse execute(HttpRequest request, HttpClientConnection conn, HttpContext context)
            throws IOException, HttpException {
        ObservationOrTimerCompatibleInstrumentation sample = ObservationOrTimerCompatibleInstrumentation
                .start(registry, observationRegistry,
                        () -> new ApacheHttpClientContext(request, context, uriMapper, exportTagsForRoute), convention,
                        DefaultApacheHttpClientObservationConvention.INSTANCE);
        String statusCodeOrError = "UNKNOWN";

        try {
            HttpResponse response = super.execute(request, conn, context);
            sample.setResponse(response);
            statusCodeOrError = getStatusValue(response, null);
            return response;
        } catch (IOException | HttpException | RuntimeException e) {
            statusCodeOrError = "IO_ERROR";
            sample.setThrowable(e);
            throw e;
        } finally {
            String status = statusCodeOrError;
            sample.stop(METER_NAME, "Duration of Apache HttpClient request execution", () -> Tags
                    .of("method", getMethodString(request), "uri",
                            uriMapper.apply(request), "status", status)
                    .and(extraTags)
                    .and(additionalTagsMapper.apply(request)));
        }
    }

    String getStatusValue(@Nullable HttpResponse response, Throwable error) {
        if (error instanceof IOException || error instanceof HttpException || error instanceof RuntimeException) {
            return "IO_ERROR";
        }

        return response != null ? Integer.toString(response.getStatusLine().getStatusCode()) : "CLIENT_ERROR";
    }

    String getMethodString(@Nullable HttpRequest request) {
        return request != null && request.getRequestLine().getMethod() != null ? request.getRequestLine().getMethod()
                : "UNKNOWN";
    }

    public static class Builder {

        private final MeterRegistry registry;

        private ObservationRegistry observationRegistry = ObservationRegistry.NOOP;

        private int waitForContinue = HttpRequestExecutor.DEFAULT_WAIT_FOR_CONTINUE;

        private Iterable extraTags = Collections.emptyList();

        private Function uriMapper = new DefaultUriMapper();

        private boolean exportTagsForRoute = false;

        @Nullable
        private ApacheHttpClientObservationConvention observationConvention;

        Builder(MeterRegistry registry) {
            this.registry = registry;
        }

        /**
         * @param waitForContinue Overrides the wait for continue time for this request
         *                        executor. See {@link HttpRequestExecutor} for details.
         * @return This builder instance.
         */
        public Builder waitForContinue(int waitForContinue) {
            this.waitForContinue = waitForContinue;
            return this;
        }

        /**
         * These tags will not be applied when instrumentation is performed with the
         * {@link Observation} API. Configure an
         * {@link ApacheHttpClientObservationConvention} instead with the extra key
         * values.
         *
         * @param tags Additional tags which should be exposed with every value.
         * @return This builder instance.
         * @see #observationConvention(ApacheHttpClientObservationConvention)
         * @see #observationRegistry(ObservationRegistry)
         * @see DefaultApacheHttpClientObservationConvention
         */
        public Builder tags(Iterable tags) {
            this.extraTags = tags;
            return this;
        }

        /**
         * Allows to register a mapping function for exposing request URIs. Be careful,
         * exposing request URIs could result in a huge number of tag values, which could
         * cause problems in your meter registry.
         * 

* By default, this feature is almost disabled. It only exposes values of the * {@value DefaultUriMapper#URI_PATTERN_HEADER} HTTP header. * * @param uriMapper A mapper that allows mapping and exposing request paths. * @return This builder instance. * @see DefaultUriMapper */ public Builder uriMapper(Function uriMapper) { this.uriMapper = uriMapper; return this; } /** * Allows to expose the target scheme, host and port with every metric. Be careful * with enabling this feature: If your client accesses a huge number of remote * servers, this would result in a huge number of tag values, which could cause * cardinality problems. *

* By default, this feature is disabled. * * @param exportTagsForRoute Set this to true, if the metrics should be tagged * with the target route. * @return This builder instance. */ public Builder exportTagsForRoute(boolean exportTagsForRoute) { this.exportTagsForRoute = exportTagsForRoute; return this; } /** * Configure an observation registry to instrument using the {@link Observation} * API instead of directly with a {@link Timer}. * * @param observationRegistry registry with which to instrument * @return This builder instance. * @since 1.10.0 */ public Builder observationRegistry(ObservationRegistry observationRegistry) { this.observationRegistry = observationRegistry; return this; } /** * Provide a custom convention to override the default convention used when * instrumenting with the {@link Observation} API. This only takes effect when an * {@link #observationRegistry(ObservationRegistry)} is configured. * * @param convention semantic convention to use * @return This builder instance. * @see #observationRegistry(ObservationRegistry) * @since 1.10.0 */ public Builder observationConvention(ApacheHttpClientObservationConvention convention) { this.observationConvention = convention; return this; } /** * @return Creates an instance of {@link CustomMicrometerHttpRequestExecutor} with all * the configured properties. */ public CustomMicrometerHttpRequestExecutor build() { return new CustomMicrometerHttpRequestExecutor(waitForContinue, registry, uriMapper, extraTags, exportTagsForRoute, observationRegistry, observationConvention); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy