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

no.nav.common.kafka.producer.util.KafkaProducerClientWithMetrics Maven / Gradle / Ivy

package no.nav.common.kafka.producer.util;

import io.micrometer.core.instrument.Counter;
import io.micrometer.core.instrument.Gauge;
import io.micrometer.core.instrument.MeterRegistry;
import no.nav.common.kafka.producer.KafkaProducerClient;
import no.nav.common.kafka.producer.KafkaProducerClientImpl;
import org.apache.kafka.clients.producer.Callback;
import org.apache.kafka.clients.producer.Producer;
import org.apache.kafka.clients.producer.ProducerRecord;
import org.apache.kafka.clients.producer.RecordMetadata;

import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.Future;

public class KafkaProducerClientWithMetrics implements KafkaProducerClient {

    public final static String KAFKA_PRODUCER_STATUS_COUNTER = "kafka_producer_status";

    public final static String KAFKA_PRODUCER_CURRENT_OFFSET_GAUGE = "kafka_producer_current_offset";

    private final KafkaProducerClient client;

    private final MeterRegistry meterRegistry;

    private final Map statusCounterMap = new HashMap<>();

    private final Map currentOffsetGaugeMap = new HashMap<>();

    private final Map currentOffsetMap = new HashMap<>();

    public KafkaProducerClientWithMetrics(Properties properties, MeterRegistry meterRegistry) {
        this.client = new KafkaProducerClientImpl<>(properties);
        this.meterRegistry = meterRegistry;
    }

    public KafkaProducerClientWithMetrics(KafkaProducerClient client, MeterRegistry meterRegistry) {
        this.client = client;
        this.meterRegistry = meterRegistry;
    }

    @Override
    public void close() {
        client.close();
    }

    @Override
    public RecordMetadata sendSync(ProducerRecord record) {
        try {
            RecordMetadata recordMetadata = client.sendSync(record);
            updateLatestOffset(recordMetadata);
            incrementRecordCount(record, false);
            return recordMetadata;
        } catch (Exception e) {
            incrementRecordCount(record, true);
            throw e;
        }
    }

    @Override
    public Future send(ProducerRecord record) {
        return send(record, null);
    }

    @Override
    public Future send(ProducerRecord record, Callback callback) {
        Callback metricCallback = (metadata, exception) -> {
            boolean failed = exception != null;

            incrementRecordCount(record, failed);

            if (metadata != null) {
                updateLatestOffset(metadata);
            }

            if (callback != null) {
                callback.onCompletion(metadata, exception);
            }
        };

        return client.send(record, metricCallback);
    }

    @Override
    public Producer getProducer() {
        return client.getProducer();
    }

    private void updateLatestOffset(RecordMetadata metadata) {
        String key = metadata.topic() + "-" + metadata.partition();

        long currentOffset = metadata.hasOffset()
                ? metadata.offset()
                : 0;

        currentOffsetMap.put(key, currentOffset);

        currentOffsetGaugeMap.computeIfAbsent(key, (k) ->
                Gauge.builder(KAFKA_PRODUCER_CURRENT_OFFSET_GAUGE, () -> {
                    Long offset = currentOffsetMap.get(key);
                    return offset != null ? offset : 0;
                })
                .tag("topic", metadata.topic())
                .tag("partition", String.valueOf(metadata.partition()))
                .register(meterRegistry));
    }

    private void incrementRecordCount(ProducerRecord record, boolean failed) {
        String key = record.topic() + "-" + failed;
        statusCounterMap.computeIfAbsent(key, (k) ->
                Counter.builder(KAFKA_PRODUCER_STATUS_COUNTER)
                        .tag("topic", record.topic())
                        .tag("status", failed ? "failed" : "ok")
                        .register(meterRegistry))
                .increment();
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy