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

com.arpnetworking.metrics.impl.TsdMetricsFactory Maven / Gradle / Ivy

There is a newer version: 0.4.2
Show newest version
/**
 * 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.metrics.Metrics;
import com.arpnetworking.metrics.MetricsFactory;
import com.arpnetworking.metrics.Sink;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

/**
 * Default implementation of MetricsFactory for creating
 * 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 MetricsFactory interface documentation.
 *
 * The simplest way to create an instance of this class is to use the
 * newInstance static factory method. This method will use
 * default settings where possible.
 *
 * {@code
 * final MetricsFactory metricsFactory = TsdMetricsFactory.newInstance(
 *     "MyService",
 *     "MyService-US-Prod",
 *     new File("/usr/local/var/my-app/logs"));
 * }
 *
 * To customize the factory instance use the nested Builder class:
 *
 * {@code
 * final MetricsFactory metricsFactory = new TsdMetricsFactory.Builder()
 *     .setServiceName("MyService")
 *     .setClusterName("MyService-US-Prod")
 *     .setSinks(Collections.singletonList(
 *         new StenoLogSink.Builder().build()));
 *     .build();
 * }
 *
 * The above will write metrics to the current working directory in query.log.
 * It is strongly recommended that at least a path be set:
 *
 * {@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")
 *             .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 TSDAggregator 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 (vkoskela at groupon dot com)
 */
public class TsdMetricsFactory implements MetricsFactory {

    /**
     * Static factory. Construct an instance of TsdMetricsFactory
     * with the default sink writing to the default file name in the specified
     * directory as the provided service and cluster.
     *
     * @param serviceName The name of the service/application publishing metrics.
     * @param clusterName The name of the cluster (e.g. instance) publishign metrics.
     * @param directory The log directory.
     * @return Instance of TsdMetricsFactory.
     */
    public static MetricsFactory newInstance(
            final String serviceName,
            final String clusterName,
            final File directory) {
        return new Builder()
                .setClusterName(clusterName)
                .setServiceName(serviceName)
                .setSinks(Collections.singletonList(
                        new StenoLogSink.Builder()
                            .setDirectory(directory)
                            .build()))
                .build();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Metrics create() {
        return new TsdMetrics(
                _serviceName,
                _clusterName,
                _hostName,
                _sinks);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String toString() {
        return String.format(
                "TsdMetricsFactory{Sinks=%s, ServiceName=%s, ClusterName=%s, HostName=%s}",
                _sinks,
                _serviceName,
                _clusterName,
                _hostName);
    }

    /* package private */ List getSinks() {
        return Collections.unmodifiableList(_sinks);
    }

    /* package private */ String getServiceName() {
        return _serviceName;
    }

    /* package private */ String getHostName() {
        return _hostName;
    }

    /* package private */ String getClusterName() {
        return _clusterName;
    }

    /**
     * Protected constructor.
     *
     * @param builder Instance of Builder.
     */
    protected TsdMetricsFactory(final Builder builder) {
        _sinks = Collections.unmodifiableList(new ArrayList(builder._sinks));
        _serviceName = builder._serviceName;
        _clusterName = builder._clusterName;
        _hostName = builder._hostName;
    }

    private final List _sinks;
    private final String _serviceName;
    private final String _clusterName;
    private final String _hostName;

    private static final Logger LOGGER = LoggerFactory.getLogger(TsdMetricsFactory.class);

    /**
     * Builder for 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
     * LoggerFactory for this class.
     *
     * Further, the constructed TsdMetricsFactory will operate
     * normally except that instead of publishing metrics to the sinks it
     * will log a warning each time close() is invoked on the
     * Metrics instance.
     *
     * This class is thread safe.
     *
     * @author Ville Koskela (vkoskela at groupon dot com)
     */
    public static class Builder {

        /**
         * Public constructor.
         */
        public Builder() {
            this(HostProvider.DEFAULT, LOGGER);
        }

        // NOTE: Package private for testing
        /* package private */ Builder(final HostProvider hostProvider, final Logger logger) {
            _hostProvider = hostProvider;
            _logger = logger;
        }

        /**
         * Create an instance of MetricsFactory.
         *
         * @return Instance of MetricsFactory.
         */
        public MetricsFactory build() {
            final List failures = new ArrayList<>();

            // Defaults
            if (_hostName == null) {
                try {
                    _hostName = _hostProvider.get();
                } catch (final UnknownHostException e) {
                    failures.add("Unable to determine hostname");
                }
            }
            if (_sinks == null) {
                _sinks = Collections.singletonList(new StenoLogSink.Builder().build());
                _logger.info(String.format("Defaulted null sinks; sinks=%s", _sinks));
            }

            // Validate
            if (_serviceName == null) {
                _serviceName = "";
                failures.add("ServiceName cannot be null");
            }
            if (_clusterName == null) {
                _clusterName = "";
                failures.add("ClusterName cannot be null");
            }
            if (_hostName == null) {
                _hostName = "";
                failures.add("HostName 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(failures));
            }

            return new TsdMetricsFactory(this);
        }

        /**
         * Set the sinks to publish to. Cannot be null.
         *
         * @param value The sinks to publish to.
         * @return This Builder instance.
         */
        public Builder setSinks(final List value) {
            _sinks = value;
            return this;
        }

        /**
         * Set the service name to publish as. Cannot be null.
         *
         * @param value The service name to publish as.
         * @return This Builder instance.
         */
        public Builder setServiceName(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 Builder instance.
         */
        public Builder setClusterName(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 HostProvider
         * or its default instance if one was not specified. If the
         * HostProvider fails to provide a host name the builder
         * will fail silently and produce a fake instance of
         * Metrics on create. This is to ensure the library
         * remains exception neutral.
         *
         * @param value The host name to publish as.
         * @return This Builder instance.
         */
        public Builder setHostName(final String value) {
            _hostName = value;
            return this;
        }

        private final HostProvider _hostProvider;
        private final Logger _logger;

        private List _sinks = Collections.singletonList(new StenoLogSink.Builder().build());
        private String _serviceName;
        private String _clusterName;
        private String _hostName;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy