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

com.newrelic.telemetry.TelemetryClient Maven / Gradle / Ivy

/*
 * Copyright 2020 New Relic Corporation. All rights reserved.
 * SPDX-License-Identifier: Apache-2.0
 */
package com.newrelic.telemetry;

import com.newrelic.telemetry.events.EventBatch;
import com.newrelic.telemetry.events.EventBatchSender;
import com.newrelic.telemetry.exceptions.ResponseException;
import com.newrelic.telemetry.exceptions.RetryWithBackoffException;
import com.newrelic.telemetry.exceptions.RetryWithRequestedWaitException;
import com.newrelic.telemetry.exceptions.RetryWithSplitException;
import com.newrelic.telemetry.http.HttpPoster;
import com.newrelic.telemetry.logs.LogBatch;
import com.newrelic.telemetry.logs.LogBatchSender;
import com.newrelic.telemetry.metrics.MetricBatch;
import com.newrelic.telemetry.metrics.MetricBatchSender;
import com.newrelic.telemetry.spans.SpanBatch;
import com.newrelic.telemetry.spans.SpanBatchSender;
import java.util.List;
import java.util.concurrent.Executors;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * This class should be the go-to spot for sending telemetry to New Relic. It includes the canonical
 * implementation of retry-logic that we recommend being used when interacting with the ingest APIs.
 *
 * 

Note: This class creates a single threaded scheduled executor on which all sending happens. Be * sure to call {@link #shutdown()} if you don't want this background thread to keep the VM from * exiting. */ public class TelemetryClient { private static final Logger LOG = LoggerFactory.getLogger(TelemetryClient.class); private static final int DEFAULT_SHUTDOWN_SECONDS = 3; private static final boolean DEFAULT_IS_DAEMON = true; private static final int DEFAULT_MAX_TELEMETRY_LIMIT = 1_000_000; private final EventBatchSender eventBatchSender; private final MetricBatchSender metricBatchSender; private final SpanBatchSender spanBatchSender; private final LimitingScheduler scheduler; private final int shutdownSeconds; private final LogBatchSender logBatchSender; private NotificationHandler notificationHandler = new LoggingNotificationHandler(LOG); /** * Create a new TelemetryClient instance, with four senders. Note that if you don't intend to send * one of the telemetry types, you can pass in a null value for that sender. * * @param metricBatchSender The sender for dimensional metrics. * @param spanBatchSender The sender for distributed tracing spans. * @param eventBatchSender The sender for custom events. * @param logBatchSender The sender for log entries. */ public TelemetryClient( MetricBatchSender metricBatchSender, SpanBatchSender spanBatchSender, EventBatchSender eventBatchSender, LogBatchSender logBatchSender) { this( metricBatchSender, spanBatchSender, eventBatchSender, logBatchSender, DEFAULT_SHUTDOWN_SECONDS, DEFAULT_IS_DAEMON); } /** * Create a new TelemetryClient instance, with four senders and seconds to wait for shutdown. * * @param metricBatchSender The sender for dimensional metrics. * @param spanBatchSender The sender for distributed tracing spans. * @param eventBatchSender The sender for custom events * @param logBatchSender The sender for log entries. * @param shutdownSeconds num of seconds to wait for graceful shutdown of its executor * @param useDaemonThread A flag to decide user-threads or daemon-threads */ public TelemetryClient( MetricBatchSender metricBatchSender, SpanBatchSender spanBatchSender, EventBatchSender eventBatchSender, LogBatchSender logBatchSender, int shutdownSeconds, boolean useDaemonThread) { this( metricBatchSender, spanBatchSender, eventBatchSender, logBatchSender, shutdownSeconds, useDaemonThread, DEFAULT_MAX_TELEMETRY_LIMIT); } /** * Create a new TelemetryClient instance, with four senders and seconds to wait for shutdown. You * can also specify if the backing threads should be daemon threads, and the max number of * telemetry to buffer. * * @param metricBatchSender The sender for dimensional metrics. * @param spanBatchSender The sender for distributed tracing spans. * @param eventBatchSender The sender for custom events * @param logBatchSender The sender for log entries. * @param shutdownSeconds num of seconds to wait for graceful shutdown of its executor * @param useDaemonThread A flag to decide user-threads or daemon-threads * @param maxTelemetryBuffer The max number of telemetry to buffer */ public TelemetryClient( MetricBatchSender metricBatchSender, SpanBatchSender spanBatchSender, EventBatchSender eventBatchSender, LogBatchSender logBatchSender, int shutdownSeconds, boolean useDaemonThread, int maxTelemetryBuffer) { this.metricBatchSender = metricBatchSender; this.spanBatchSender = spanBatchSender; this.eventBatchSender = eventBatchSender; this.logBatchSender = logBatchSender; this.shutdownSeconds = shutdownSeconds; this.scheduler = buildScheduler(useDaemonThread, maxTelemetryBuffer); } private interface BatchSender { void sendBatch(TelemetryBatch batch) throws ResponseException; } /** * Send a batch of {@link com.newrelic.telemetry.metrics.Metric} instances, with standard retry * logic. This happens on a background thread, asynchronously, so currently there will be no * feedback to the caller outside of the logs. * * @param batch batch metrics to be applied */ public void sendBatch(MetricBatch batch) { scheduleBatchSend( (b) -> metricBatchSender.sendBatch((MetricBatch) b), batch, 0, TimeUnit.SECONDS); } /** * Send a batch of {@link com.newrelic.telemetry.spans.Span} instances, with standard retry logic. * This happens on a background thread, asynchronously, so currently there will be no feedback to * the caller outside of the logs. * * @param batch to be sent */ public void sendBatch(SpanBatch batch) { scheduleBatchSend((b) -> spanBatchSender.sendBatch((SpanBatch) b), batch, 0, TimeUnit.SECONDS); } /** * Send a batch of {@link com.newrelic.telemetry.events.Event} instances, with standard retry * logic. This happens on a background thread, asynchronously, so currently there will be no * feedback to the caller outside of the logs. * * @param batch to be sent */ public void sendBatch(EventBatch batch) { scheduleBatchSend( (b) -> eventBatchSender.sendBatch((EventBatch) b), batch, 0, TimeUnit.SECONDS); } /** * Send a batch of {@link com.newrelic.telemetry.logs.Log} entries, with standard retry logic. * This happens on a background thread, asynchronously, so currently there will be no feedback to * the caller outside of the logs. * * @param batch to be sent */ public void sendBatch(LogBatch batch) { scheduleBatchSend((b) -> logBatchSender.sendBatch((LogBatch) b), batch, 0, TimeUnit.SECONDS); } private void scheduleBatchSend( BatchSender sender, TelemetryBatch batch, long waitTime, TimeUnit timeUnit) { scheduleBatchSend(sender, batch, waitTime, timeUnit, Backoff.defaultBackoff()); } private void scheduleBatchSend( BatchSender sender, TelemetryBatch batch, long waitTime, TimeUnit timeUnit, Backoff backoff) { if (scheduler.isTerminated()) { return; } try { scheduler.schedule( batch.size(), () -> sendWithErrorHandling(sender, batch, backoff), waitTime, timeUnit); } catch (RejectedExecutionException e) { if (notificationHandler != null) { notificationHandler.noticeError("Problem scheduling batch : ", e, batch); } } } private void sendWithErrorHandling( BatchSender batchSender, TelemetryBatch batch, Backoff backoff) { try { batchSender.sendBatch(batch); LOG.debug("Telemetry - {} - sent", batch.getClass().getSimpleName()); } catch (RetryWithBackoffException e) { backoff(batchSender, batch, backoff); } catch (RetryWithRequestedWaitException e) { retry(batchSender, batch, e); } catch (RetryWithSplitException e) { splitAndSend(batchSender, batch, e); } catch (ResponseException e) { if (notificationHandler != null) { notificationHandler.noticeError( "Received a fatal exception from the New Relic API. Aborting batch send.", e, batch); } } catch (Exception e) { if (notificationHandler != null) { notificationHandler.noticeError("Unexpected failure when sending data.", e, batch); } } } private void splitAndSend( BatchSender sender, TelemetryBatch batch, RetryWithSplitException e) { if (notificationHandler != null) { notificationHandler.noticeInfo("Batch size too large, splitting and retrying.", e, batch); } List> splitBatches = batch.split(); splitBatches.forEach( metricBatch -> scheduleBatchSend(sender, metricBatch, 0, TimeUnit.SECONDS)); } private void retry( BatchSender sender, TelemetryBatch batch, RetryWithRequestedWaitException e) { if (notificationHandler != null) { notificationHandler.noticeInfo( String.format( "Batch sending failed. Retrying failed batch after %d %s", e.getWaitTime(), e.getTimeUnit()), batch); } scheduleBatchSend(sender, batch, e.getWaitTime(), e.getTimeUnit()); } private void backoff( BatchSender sender, TelemetryBatch batch, Backoff backoff) { long newWaitTime = backoff.nextWaitMs(); if (newWaitTime == -1) { if (notificationHandler != null) { notificationHandler.noticeError( String.format("Max retries exceeded. Dropping %d pieces of data!", batch.size()), batch); } return; } if (notificationHandler != null) { notificationHandler.noticeInfo( String.format( "Batch sending failed. Backing off %d %s", newWaitTime, TimeUnit.MILLISECONDS), batch); } scheduleBatchSend(sender, batch, newWaitTime, TimeUnit.MILLISECONDS, backoff); } /** Cleanly shuts down the background Executor thread. */ public void shutdown() { LOG.info("Shutting down the TelemetryClient background Executor"); scheduler.shutdown(); try { if (!scheduler.awaitTermination(shutdownSeconds, TimeUnit.SECONDS)) { LOG.warn("couldn't shutdown within timeout"); scheduler.shutdownNow(); } } catch (InterruptedException e) { LOG.error("interrupted graceful shutdown", e); Thread.currentThread().interrupt(); } } /** * Create a fully operational {@link TelemetryClient} with all default options. * * @param httpPosterCreator A {@link Supplier} used to create an {@link HttpPoster} instance. * @param insertApiKey The New Relic Insert API to use. * @return A fully operational TelemetryClient instance. */ public static TelemetryClient create( Supplier httpPosterCreator, String insertApiKey) { return create(httpPosterCreator, new BaseConfig(insertApiKey)); } /** * Create a fully operational {@link TelemetryClient} from a BaseConfig instance * * @param httpPosterCreator A {@link Supplier} used to create an {@link HttpPoster} instance. * @param baseConfig the base configuration * @return A fully operational TelemetryClient instance. */ public static TelemetryClient create( Supplier httpPosterCreator, BaseConfig baseConfig) { MetricBatchSender metricBatchSender = MetricBatchSender.create(httpPosterCreator, baseConfig); SpanBatchSender spanBatchSender = SpanBatchSender.create(httpPosterCreator, baseConfig); EventBatchSender eventBatchSender = EventBatchSender.create(httpPosterCreator, baseConfig); LogBatchSender logBatchSender = LogBatchSender.create(httpPosterCreator, baseConfig); return new TelemetryClient( metricBatchSender, spanBatchSender, eventBatchSender, logBatchSender); } /** * Create ScheduledExecutorService from a parameter given by constructor * * @param useDaemonThread A flag to decide user-threads or daemon-threads * @param maxTelemetryBuffer Max number of telemetry to buffer * @return ScheduledExecutorService */ private static LimitingScheduler buildScheduler(boolean useDaemonThread, int maxTelemetryBuffer) { ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor( r -> { Thread thread = new Thread(r); thread.setDaemon(useDaemonThread); return thread; }); return new LimitingScheduler(executor, maxTelemetryBuffer); } /** * Provide a {@link NotificationHandler} to the {@link TelemetryClient} for handling {@link * ResponseException} * * @param notificationHandler The {@link NotificationHandler} to use */ public void withNotificationHandler(NotificationHandler notificationHandler) { this.notificationHandler = notificationHandler; } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy