
com.arpnetworking.metrics.impl.TsdMetricsFactory Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of metrics-client Show documentation
Show all versions of metrics-client Show documentation
Client library in Java for metrics collection and publication.
/*
* Copyright 2014 Groupon.com
*
* 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
*
* 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.arpnetworking.metrics.impl;
import com.arpnetworking.commons.hostresolver.BackgroundCachingHostResolver;
import com.arpnetworking.commons.hostresolver.HostResolver;
import com.arpnetworking.commons.uuidfactory.SplittableRandomUuidFactory;
import com.arpnetworking.commons.uuidfactory.UuidFactory;
import com.arpnetworking.metrics.Metrics;
import com.arpnetworking.metrics.MetricsFactory;
import com.arpnetworking.metrics.Sink;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.lang.reflect.Method;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import java.util.function.Supplier;
import javax.annotation.Nullable;
/**
* Default implementation of {@link MetricsFactory} for creating
* {@link Metrics} instances for publication of time series data (TSD).
*
* For more information about the semantics of this class and its methods
* please refer to the {@link MetricsFactory} interface documentation.
*
* The simplest way to create an instance of this class is to use the
* {@link TsdMetricsFactory#newInstance(String, String)} static factory method.
* This method will use default settings where possible.
*
* {@code
* final MetricsFactory metricsFactory = TsdMetricsFactory.newInstance(
* "MyService",
* "MyService-US-Prod");
* }
*
* To customize the factory instance use the nested {@link Builder} class:
*
* {@code
* final MetricsFactory metricsFactory = new TsdMetricsFactory.Builder()
* .setServiceName("MyService")
* .setClusterName("MyService-US-Prod")
* .setSinks(Collections.singletonList(
* new ApacheHttpSink.Builder().build()));
* .build();
* }
*
* The above will write metrics to http://localhost:7090/metrics/v1/application.
* This is the default port and path of the Metrics Aggregator Daemon (MAD). It
* is sometimes desirable to customize this path; for example, when running MAD
* under Docker:
*
* {@code
* final MetricsFactory metricsFactory = new TsdMetricsFactory.Builder()
* .setServiceName("MyService")
* .setClusterName("MyService-US-Prod")
* .setSinks(Collections.singletonList(
* new ApacheHttpSink.Builder()
* .setUri(URI.create("http://192.168.0.1:1234/metrics/v1/application"))
* .build()));
* .build();
* }
*
* Alternatively, metrics may be written to a file:
*
* {@code
* final MetricsFactory metricsFactory = new TsdMetricsFactory.Builder()
* .setServiceName("MyService")
* .setClusterName("MyService-US-Prod")
* .setSinks(Collections.singletonList(
* new FileLogSink.Builder().build()));
* .build();
* }
*
* The above will write metrics to query.log in the current directory. It is
* advised that at least the directory be set when using the FileLogSink:
*
* {@code
* final MetricsFactory metricsFactory = new TsdMetricsFactory.Builder()
* .setServiceName("MyService")
* .setClusterName("MyService-US-Prod")
* .setSinks(Collections.singletonList(
* new FileLogSink.Builder()
* .setDirectory("/usr/local/var/my-app/logs")
* .build()));
* .build();
* }
*
* The above will write metrics to /usr/local/var/my-app/logs in query.log.
* Additionally, you can customize the base file name and extension for your
* application. However, if you are using MAD remember to configure it to
* match:
*
* {@code
* final MetricsFactory metricsFactory = new TsdMetricsFactory.Builder()
* .setServiceName("MyService")
* .setClusterName("MyService-US-Prod")
* .setSinks(Collections.singletonList(
* new StenoLogSink.Builder()
* .setDirectory("/usr/local/var/my-app/logs")
* .setName("tsd")
* .setExtension(".txt")
* .build()));
* .build();
* }
*
* The above will write metrics to /usr/local/var/my-app/logs in tsd.txt. The
* extension is configured separately as the files are rolled over every hour
* inserting a date-time between the name and extension like:
*
* query-log.YYYY-MM-DD-HH.log
*
* This class is thread safe.
*
* @author Ville Koskela (ville dot koskela at inscopemetrics dot io)
*/
public class TsdMetricsFactory implements MetricsFactory {
/**
* Static factory. Construct an instance of {@link TsdMetricsFactory}
* using the first available default {@link Sink}.
*
* @param serviceName The name of the service/application publishing metrics.
* @param clusterName The name of the cluster (e.g. instance) publishing metrics.
* @return Instance of {@link TsdMetricsFactory}.
*/
public static MetricsFactory newInstance(
final String serviceName,
final String clusterName) {
return new Builder()
.setClusterName(clusterName)
.setServiceName(serviceName)
.build();
}
@Override
public Metrics create() {
final UUID uuid = _uuidFactory.get();
try {
return new TsdMetrics(
uuid,
_serviceName,
_clusterName,
_hostResolver.get(),
_sinks);
// CHECKSTYLE.OFF: IllegalCatch - Suppliers do not throw checked exceptions
} catch (final RuntimeException e) {
// CHECKSTYLE.ON: IllegalCatch
final List failures = Collections.singletonList("Unable to determine hostname");
_logger.warn(
String.format(
"Unable to construct TsdMetrics, metrics disabled; failures=%s",
failures),
e);
return new TsdMetrics(
uuid,
_serviceName,
_clusterName,
DEFAULT_HOST_NAME,
Collections.singletonList(
new WarningSink.Builder()
.setReasons(failures)
.build()));
}
}
@Override
public String toString() {
return String.format(
"TsdMetricsFactory{Sinks=%s, ServiceName=%s, ClusterName=%s, HostResolver=%s}",
_sinks,
_serviceName,
_clusterName,
_hostResolver);
}
/* package private */ List getSinks() {
return Collections.unmodifiableList(_sinks);
}
/* package private */ String getServiceName() {
return _serviceName;
}
/* package private */ Supplier getHostResolver() {
return _hostResolver;
}
/* package private */ Supplier getUuidFactory() {
return _uuidFactory;
}
/* package private */ String getClusterName() {
return _clusterName;
}
/* package private */ static @Nullable List createDefaultSinks(final List defaultSinkClassNames) {
for (final String sinkClassName : defaultSinkClassNames) {
final Optional> sinkClass = getSinkClass(sinkClassName);
if (sinkClass.isPresent()) {
final Optional sink = createSink(sinkClass.get());
if (sink.isPresent()) {
return Collections.singletonList(sink.get());
}
}
}
return Collections.unmodifiableList(
Collections.singletonList(
new WarningSink.Builder()
.setReasons(Collections.singletonList("No default sink found."))
.build()));
}
@SuppressWarnings("unchecked")
@SuppressFBWarnings("REC_CATCH_EXCEPTION")
/* package private */ static Optional createSink(final Class extends Sink> sinkClass) {
try {
final Class> sinkBuilderClass = Class.forName(sinkClass.getName() + "$Builder");
final Object sinkBuilder = sinkBuilderClass.newInstance();
final Method buildMethod = sinkBuilderClass.getMethod("build");
return Optional.of((Sink) buildMethod.invoke(sinkBuilder));
// CHECKSTYLE.OFF: IllegalCatch - Much cleaner than catching the half-dozen checked exceptions
} catch (final Exception e) {
// CHECKSTYLE.ON: IllegalCatch
LOGGER.warn(
String.format(
"Unable to load sink; sinkClass=%s",
sinkClass),
e);
return Optional.empty();
}
}
@SuppressWarnings("unchecked")
/* package private */ static Optional> getSinkClass(final String name) {
try {
return Optional.of((Class extends Sink>) Class.forName(name));
} catch (final ClassNotFoundException e) {
return Optional.empty();
}
}
/**
* Protected constructor.
*
* @param builder Instance of {@link Builder}.
*/
protected TsdMetricsFactory(final Builder builder) {
this(builder, LOGGER);
}
/**
* Protected constructor.
*
* @param builder Instance of {@link Builder}.
*/
/* package private */ TsdMetricsFactory(final Builder builder, final Logger logger) {
_sinks = Collections.unmodifiableList(new ArrayList<>(builder._sinks));
_uuidFactory = builder._uuidFactory;
_serviceName = builder._serviceName;
_clusterName = builder._clusterName;
_hostResolver = builder._hostResolver;
_logger = logger;
}
private final List _sinks;
private final Supplier _uuidFactory;
private final String _serviceName;
private final String _clusterName;
private final Supplier _hostResolver;
private final Logger _logger;
private static final String DEFAULT_SERVICE_NAME = "";
private static final String DEFAULT_CLUSTER_NAME = "";
private static final String DEFAULT_HOST_NAME = "";
private static final List DEFAULT_SINK_CLASS_NAMES;
private static final Logger LOGGER = LoggerFactory.getLogger(TsdMetricsFactory.class);
static {
final List sinkClassNames = new ArrayList<>();
sinkClassNames.add("com.arpnetworking.metrics.impl.ApacheHttpSink");
sinkClassNames.add("com.arpnetworking.metrics.impl.FileSink");
DEFAULT_SINK_CLASS_NAMES = Collections.unmodifiableList(sinkClassNames);
}
/**
* Builder for {@link TsdMetricsFactory}.
*
* This class does not throw exceptions if it is used improperly. An
* example of improper use would be if the constraints on a field are
* not satisfied. To prevent breaking the client application no
* exception is thrown; instead a warning is logged using the SLF4J
* {@link LoggerFactory} for this class.
*
* Further, the constructed {@link TsdMetricsFactory} will operate
* normally except that instead of publishing metrics to the sinks it
* will log a warning each time {@link Metrics#close()} is invoked on the
* {@link Metrics} instance.
*
* This class is thread safe.
*
* @author Ville Koskela (ville dot koskela at inscopemetrics dot io)
*/
public static class Builder implements com.arpnetworking.commons.builder.Builder {
/**
* Public constructor.
*/
public Builder() {
this(DEFAULT_HOST_RESOLVER, LOGGER);
}
/**
* Public constructor.
*
* @param hostResolver The {@link HostResolver} instance to use
* to determine the default host name.
*/
public Builder(final HostResolver hostResolver) {
this(hostResolver, LOGGER);
}
// NOTE: Package private for testing
/* package private */ Builder(@Nullable final Supplier hostResolver, @Nullable final Logger logger) {
_hostResolver = hostResolver;
_logger = logger;
}
/**
* Create an instance of {@link MetricsFactory}.
*
* @return Instance of {@link MetricsFactory}.
*/
@Override
public MetricsFactory build() {
final List failures = new ArrayList<>();
// Defaults
if (_sinks == null) {
_sinks = DEFAULT_SINKS;
_logger.info(String.format("Defaulted null sinks; sinks=%s", _sinks));
}
if (_hostResolver == null) {
_hostResolver = DEFAULT_HOST_RESOLVER;
_logger.info(String.format("Defaulted null host resolver; resolver=%s", _hostResolver));
}
// Validate
if (_serviceName == null) {
_serviceName = DEFAULT_SERVICE_NAME;
failures.add("ServiceName cannot be null");
}
if (_clusterName == null) {
_clusterName = DEFAULT_CLUSTER_NAME;
failures.add("ClusterName cannot be null");
}
// Apply fallback
if (!failures.isEmpty()) {
_logger.warn(String.format(
"Unable to construct TsdMetricsFactory, metrics disabled; failures=%s",
failures));
_sinks = Collections.singletonList(
new WarningSink.Builder()
.setReasons(failures)
.build());
}
return new TsdMetricsFactory(this);
}
/**
* Set the sinks to publish to. Cannot be null.
*
* @param value The sinks to publish to.
* @return This {@link Builder} instance.
*/
public Builder setSinks(@Nullable final List value) {
_sinks = value;
return this;
}
/**
* Set the UuidFactory to be used to create UUIDs assigned to instances
* of {@link Metrics} created by this {@link MetricsFactory}.
* Cannot be null. Optional. Defaults to using the Java native
* {@link java.util.UUID#randomUUID()}.
*
* @param uuidFactory The {@link UuidFactory} instance.
* @return This {@link Builder} instance.
*/
public Builder setUuidFactory(@Nullable final UuidFactory uuidFactory) {
_uuidFactory = uuidFactory;
return this;
}
/**
* Set the service name to publish as. Cannot be null.
*
* @param value The service name to publish as.
* @return This {@link Builder} instance.
*/
public Builder setServiceName(@Nullable final String value) {
_serviceName = value;
return this;
}
/**
* Set the cluster name to publish as. Cannot be null.
*
* @param value The cluster name to publish as.
* @return This {@link Builder} instance.
*/
public Builder setClusterName(@Nullable final String value) {
_clusterName = value;
return this;
}
/**
* Set the host name to publish as. Cannot be null. Optional. Default
* is the host name provided by the provided {@code hostResolver}
* or its default instance if one was not specified. If the
* {@code hostResolver} fails to provide a host name the builder
* will produce a fake instance of {@link Metrics} on create. This
* is to ensure the library remains exception neutral.
*
* @param value The host name to publish as.
* @return This {@link Builder} instance.
*/
public Builder setHostName(@Nullable final String value) {
_hostResolver = () -> value;
return this;
}
private final Logger _logger;
private List _sinks = DEFAULT_SINKS;
private Supplier _uuidFactory = DEFAULT_UUID_FACTORY;
private String _serviceName;
private String _clusterName;
private Supplier _hostResolver;
private static final List DEFAULT_SINKS = createDefaultSinks(DEFAULT_SINK_CLASS_NAMES);
private static final Supplier DEFAULT_HOST_RESOLVER = new BackgroundCachingHostResolver(Duration.ofMinutes(1));
private static final Supplier DEFAULT_UUID_FACTORY = new SplittableRandomUuidFactory();
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy