io.opentelemetry.instrumentation.kafka.internal.OpenTelemetryMetricsReporter Maven / Gradle / Ivy
Show all versions of opentelemetry-kafka-clients-common Show documentation
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.instrumentation.kafka.internal;
import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.api.common.AttributeKey;
import io.opentelemetry.api.metrics.Meter;
import io.opentelemetry.api.metrics.MeterBuilder;
import io.opentelemetry.instrumentation.api.internal.EmbeddedInstrumentationProperties;
import io.opentelemetry.instrumentation.api.internal.GuardedBy;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.apache.kafka.common.metrics.KafkaMetric;
import org.apache.kafka.common.metrics.MetricsReporter;
/**
* A {@link MetricsReporter} which bridges Kafka metrics to OpenTelemetry metrics.
*
* To configure, use:
*
*
{@code
* // KafkaTelemetry.create(OpenTelemetry).metricConfigProperties()
* }
*
* This class is internal and is hence not for public use. Its APIs are unstable and can change
* at any time.
*/
public final class OpenTelemetryMetricsReporter implements MetricsReporter {
public static final String CONFIG_KEY_OPENTELEMETRY_SUPPLIER = "opentelemetry.supplier";
public static final String CONFIG_KEY_OPENTELEMETRY_INSTRUMENTATION_NAME =
"opentelemetry.instrumentation_name";
private static final Logger logger =
Logger.getLogger(OpenTelemetryMetricsReporter.class.getName());
private static volatile Listener listener;
private volatile Meter meter;
private final Object lock = new Object();
@GuardedBy("lock")
private final List registeredObservables = new ArrayList<>();
/**
* Reset for test by resetting the {@link #meter} to {@code null} and closing all registered
* instruments.
*/
void resetForTest() {
closeAllInstruments();
}
// Visible for test
List getRegisteredObservables() {
synchronized (lock) {
return new ArrayList<>(registeredObservables);
}
}
public OpenTelemetryMetricsReporter() {
if (listener != null) {
listener.metricsReporterCreated(this);
}
}
@Override
public void init(List metrics) {
metrics.forEach(this::metricChange);
}
@Override
public void metricChange(KafkaMetric metric) {
Meter currentMeter = meter;
if (currentMeter == null) {
// Ignore if meter hasn't been initialized in configure(Map> attributeKeys = registeredObservable.getAttributes().asMap().keySet();
synchronized (lock) {
for (Iterator it = registeredObservables.iterator(); it.hasNext(); ) {
RegisteredObservable curRegisteredObservable = it.next();
Set> curAttributeKeys =
curRegisteredObservable.getAttributes().asMap().keySet();
if (curRegisteredObservable.getKafkaMetricName().equals(metric.metricName())) {
logger.log(Level.FINEST, "Replacing instrument: {0}", curRegisteredObservable);
closeInstrument(curRegisteredObservable.getObservable());
it.remove();
} else if (curRegisteredObservable
.getInstrumentDescriptor()
.equals(registeredObservable.getInstrumentDescriptor())
&& attributeKeys.size() > curAttributeKeys.size()
&& attributeKeys.containsAll(curAttributeKeys)) {
logger.log(
Level.FINEST,
"Replacing instrument with higher dimension version: {0}",
curRegisteredObservable);
closeInstrument(curRegisteredObservable.getObservable());
it.remove();
}
}
registeredObservables.add(registeredObservable);
}
}
@Override
public void metricRemoval(KafkaMetric metric) {
logger.log(Level.FINEST, "Metric removed: {0}", metric.metricName());
synchronized (lock) {
for (Iterator it = registeredObservables.iterator(); it.hasNext(); ) {
RegisteredObservable current = it.next();
if (current.getKafkaMetricName().equals(metric.metricName())) {
closeInstrument(current.getObservable());
it.remove();
}
}
}
}
@Override
public void close() {
closeAllInstruments();
}
private void closeAllInstruments() {
synchronized (lock) {
for (Iterator it = registeredObservables.iterator(); it.hasNext(); ) {
closeInstrument(it.next().getObservable());
it.remove();
}
}
}
private static void closeInstrument(AutoCloseable observable) {
try {
observable.close();
} catch (Exception e) {
throw new IllegalStateException("Error occurred closing instrument", e);
}
}
@Override
public void configure(Map configs) {
OpenTelemetrySupplier openTelemetrySupplier =
getProperty(configs, CONFIG_KEY_OPENTELEMETRY_SUPPLIER, OpenTelemetrySupplier.class);
OpenTelemetry openTelemetry = openTelemetrySupplier.get();
String instrumentationName =
getProperty(configs, CONFIG_KEY_OPENTELEMETRY_INSTRUMENTATION_NAME, String.class);
String instrumentationVersion =
EmbeddedInstrumentationProperties.findVersion(instrumentationName);
MeterBuilder meterBuilder = openTelemetry.meterBuilder(instrumentationName);
if (instrumentationVersion != null) {
meterBuilder.setInstrumentationVersion(instrumentationVersion);
}
meter = meterBuilder.build();
}
@SuppressWarnings("unchecked")
private static T getProperty(Map configs, String key, Class requiredType) {
Object value = configs.get(key);
if (value == null) {
throw new IllegalStateException("Missing required configuration property: " + key);
}
if (!requiredType.isInstance(value)) {
throw new IllegalStateException(
"Configuration property " + key + " is not instance of " + requiredType.getSimpleName());
}
return (T) value;
}
// Visible for test
static void setListener(Listener listener) {
OpenTelemetryMetricsReporter.listener = listener;
}
// used for testing
interface Listener {
void metricsReporterCreated(OpenTelemetryMetricsReporter metricsReporter);
}
}