/* * Copyright 2016 LINE Corporation * * LINE Corporation licenses this file to you 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: * * http://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.linecorp.armeria.client.logging; import static java.util.Objects.requireNonNull; import java.util.function.Function; import com.codahale.metrics.MetricRegistry; import com.google.common.base.MoreObjects; import com.linecorp.armeria.client.Client; import com.linecorp.armeria.client.ClientRequestContext; import com.linecorp.armeria.client.SimpleDecoratingClient; import com.linecorp.armeria.common.Request; import com.linecorp.armeria.common.Response; import com.linecorp.armeria.common.RpcRequest; import com.linecorp.armeria.common.http.HttpHeaders; import com.linecorp.armeria.common.logging.RequestLog; import com.linecorp.armeria.common.logging.RequestLogAvailability; import com.linecorp.armeria.internal.logging.DropwizardMetricCollector; /** * Decorates a {@link Client} to collect metrics into Dropwizard {@link MetricRegistry}. * To use, simply prepare a {@link MetricRegistry} and add this decorator to a client. * * <p>Example: * <pre>{@code * MetricRegistry metricRegistry = new MetricRegistry(); * MyService.Iface client = new ClientBuilder(uri) * .decorate(DropwizardMetricCollectingClient.newDecorator( * metricRegistry, MetricRegistry.name("clients", "myService"))) * .build(MyService.Iface.class); * } * </pre> * * <p>It is generally recommended to define your own name for the service instead of using something like * the Java class to make sure otherwise safe changes like renames don't break metrics. * * @param <I> the request type * @param <O> the response type */ public final class DropwizardMetricCollectingClient<I extends Request, O extends Response> extends SimpleDecoratingClient<I, O> { /** * Returns a {@link Client} decorator that tracks request stats using the Dropwizard metrics library. * * @param metricRegistry the {@link MetricRegistry} to store metrics into. * @param metricNameFunc the function that transforms a {@link RequestLog} into a metric name */ public static <I extends Request, O extends Response> Function<Client<? super I, ? extends O>, DropwizardMetricCollectingClient<I, O>> newDecorator( MetricRegistry metricRegistry, Function<? super RequestLog, String> metricNameFunc) { requireNonNull(metricRegistry, "metricRegistry"); requireNonNull(metricNameFunc, "metricNameFunc"); return client -> new DropwizardMetricCollectingClient<>(client, metricRegistry, metricNameFunc); } /** * Returns a {@link Client} decorator that tracks request stats using the Dropwizard metrics library. * * @param metricRegistry the {@link MetricRegistry} to store metrics into. * @param metricNamePrefix the prefix of the names of the metrics created by the returned decorator. */ public static <I extends Request, O extends Response> Function<Client<? super I, ? extends O>, DropwizardMetricCollectingClient<I, O>> newDecorator( MetricRegistry metricRegistry, String metricNamePrefix) { requireNonNull(metricNamePrefix, "metricNamePrefix"); return newDecorator(metricRegistry, log -> defaultMetricName(log, metricNamePrefix)); } private static String defaultMetricName(RequestLog log, String metricNamePrefix) { String methodName = null; final Object envelope = log.requestEnvelope(); final Object content = log.requestContent(); if (envelope instanceof HttpHeaders) { methodName = ((HttpHeaders) envelope).method().name(); } if (content instanceof RpcRequest) { methodName = ((RpcRequest) content).method(); } if (methodName == null) { methodName = MoreObjects.firstNonNull(log.method(), "__UNKNOWN_METHOD__"); } return MetricRegistry.name(metricNamePrefix, methodName); } private final DropwizardMetricCollector collector; @SuppressWarnings("unchecked") DropwizardMetricCollectingClient( Client<? super I, ? extends O> delegate, MetricRegistry metricRegistry, Function<? super RequestLog, String> metricNameFunc) { super(delegate); collector = new DropwizardMetricCollector( metricRegistry, (Function<RequestLog, String>) metricNameFunc); } @Override public O execute(ClientRequestContext ctx, I req) throws Exception { ctx.log().addListener(collector::onRequestStart, RequestLogAvailability.REQUEST_ENVELOPE, RequestLogAvailability.REQUEST_CONTENT); ctx.log().addListener(collector::onRequestEnd, RequestLogAvailability.REQUEST_END); ctx.log().addListener(collector::onResponse, RequestLogAvailability.COMPLETE); return delegate().execute(ctx, req); } }