{"id":6045,"date":"2026-05-02T12:03:49","date_gmt":"2026-05-02T12:03:49","guid":{"rendered":"https:\/\/dalelane.co.uk\/blog\/?p=6045"},"modified":"2026-05-02T12:03:49","modified_gmt":"2026-05-02T12:03:49","slug":"instrumenting-a-kafka-connect-connector-with-metrics","status":"publish","type":"post","link":"https:\/\/dalelane.co.uk\/blog\/?p=6045","title":{"rendered":"Instrumenting a Kafka Connect connector with metrics"},"content":{"rendered":"<p><strong>Metrics can help provide operational insight over Kafka Connect connectors, informing users of how to better configure them. With simple updates, a Kafka Connect connector can be instrumented to make this possible by emitting useful metrics.<\/strong><\/p>\n<p><a href=\"https:\/\/dalelane.co.uk\/blog\/?p=5052\">A couple years ago<\/a>, I created <a href=\"https:\/\/github.com\/dalelane\/kafka-connect-developers-intro-tutorial\">a simple skeleton Connect connector project<\/a> to help developers at a hackathon create their first Kafka connector.<\/p>\n<p><a href=\"https:\/\/github.com\/dalelane\/kafka-connect-developers-intro-tutorial\/commit\/97c1d8604fc1fe9690a82d1b3b5bc09f997f2b53\">I&#8217;ve updated<\/a> the <a href=\"https:\/\/github.com\/dalelane\/kafka-connect-developers-intro-tutorial\/blob\/main\/my-connector-project\/src\/main\/java\/com\/ibm\/eventstreams\/kafkaconnect\/connectors\/source\/MySourceConnector.java\">source connector<\/a> from that sample to emit metrics. In this post, I&#8217;ll walk through what I did, as an example for how to add metrics to your own Kafka connector.<\/p>\n<p><!--more--><em>To add some context that might help when I go through what metrics I added, this connector periodically polled <a href=\"https:\/\/www.weatherapi.com\/\">a weather API<\/a> for weather reports, and emitted a Kafka event when the weather report details changed.<\/em><\/p>\n<h3>Check for support<\/h3>\n<p>Support for Connector metrics is <a href=\"https:\/\/cwiki.apache.org\/confluence\/display\/KAFKA\/KIP-877%3A+Mechanism+for+plugins+and+connectors+to+register+metrics\">a relatively recent addition<\/a>, so if your Connector might be used in Connect runtimes with versions before 4.2, then you need to handle the support not being there.<\/p>\n<p><script src=\"https:\/\/emgithub.com\/embed-v2.js?target=https%3A%2F%2Fgithub.com%2Fdalelane%2Fkafka-connect-developers-intro-tutorial%2Fblob%2F97c1d8604fc1fe9690a82d1b3b5bc09f997f2b53%2Fmy-connector-project%2Fsrc%2Fmain%2Fjava%2Fcom%2Fibm%2Feventstreams%2Fkafkaconnect%2Fconnectors%2Fsource%2FMySourceTask.java%23L66-L73&#038;style=default&#038;type=code&#038;showBorder=on&#038;showFileMeta=on&#038;fetchFromJsDelivr=on&#038;maxHeight=450\"><\/script><\/p>\n<p>The simplest way to do this, as <a href=\"https:\/\/github.com\/apache\/kafka\/blob\/4.2.0\/connect\/api\/src\/main\/java\/org\/apache\/kafka\/connect\/source\/SourceTaskContext.java#L68-L88\">recommended in the Kafka Javadoc<\/a>, is to handle the <code style=\"font-weight: bold; color: #770000;\">NoSuchMethodError<\/code> or <code style=\"font-weight: bold; color: #770000;\">NoClassDefFoundError<\/code> exceptions that will be thrown when this happens, and gate all the metrics setup behind that.<\/p>\n<h3>Get access to the metrics for your plugin<\/h3>\n<p>Connectors, converters, and transforms are all Connect plugins. Any of these can emit metrics.<\/p>\n<p><script src=\"https:\/\/emgithub.com\/embed-v2.js?target=https%3A%2F%2Fgithub.com%2Fdalelane%2Fkafka-connect-developers-intro-tutorial%2Fblob%2F97c1d8604fc1fe9690a82d1b3b5bc09f997f2b53%2Fmy-connector-project%2Fsrc%2Fmain%2Fjava%2Fcom%2Fibm%2Feventstreams%2Fkafkaconnect%2Fconnectors%2Fsource%2FMyDataFetcherMetrics.java%23L57-L63&#038;style=default&#038;type=code&#038;showBorder=on&#038;showFileMeta=on&#038;fetchFromJsDelivr=on&#038;maxHeight=450\"><\/script><\/p>\n<p>The starting point for registering new metrics for your Connector is to get access to the <code style=\"font-weight: bold; color: #770000;\">PluginMetrics<\/code> instance for your connector.<\/p>\n<h3>Count how many times your Connector does things<\/h3>\n<p>The sample connector polls an API, so the simplest metric was to count how many times it has called the API.<\/p>\n<p><script src=\"https:\/\/emgithub.com\/embed-v2.js?target=https%3A%2F%2Fgithub.com%2Fdalelane%2Fkafka-connect-developers-intro-tutorial%2Fblob%2F97c1d8604fc1fe9690a82d1b3b5bc09f997f2b53%2Fmy-connector-project%2Fsrc%2Fmain%2Fjava%2Fcom%2Fibm%2Feventstreams%2Fkafkaconnect%2Fconnectors%2Fsource%2FMyDataFetcherMetrics.java%23L65-L70&#038;style=default&#038;type=code&#038;showBorder=on&#038;showFileMeta=on&#038;fetchFromJsDelivr=on&#038;maxHeight=450\"><\/script><\/p>\n<p>There are a couple steps to doing this:<\/p>\n<ol>\n<li><code style=\"font-weight: bold; color: #770000;\">addSensor<\/code> &#8211; register what you will be recording<\/li>\n<li><code style=\"font-weight: bold; color: #770000;\">add<\/code> metrics to the sensor that you want Connect to compute and emit<\/li>\n<\/ol>\n<p>I went with a CumulativeCount for this one, but there are <a href=\"https:\/\/github.com\/apache\/kafka\/tree\/trunk\/clients\/src\/main\/java\/org\/apache\/kafka\/common\/metrics\/stats\">many other types<\/a> to choose from.<\/p>\n<p><script src=\"https:\/\/emgithub.com\/embed-v2.js?target=https%3A%2F%2Fgithub.com%2Fdalelane%2Fkafka-connect-developers-intro-tutorial%2Fblob%2F97c1d8604fc1fe9690a82d1b3b5bc09f997f2b53%2Fmy-connector-project%2Fsrc%2Fmain%2Fjava%2Fcom%2Fibm%2Feventstreams%2Fkafkaconnect%2Fconnectors%2Fsource%2FMyDataFetcherMetrics.java%23L112-L117&#038;style=default&#038;type=code&#038;showBorder=on&#038;showFileMeta=on&#038;fetchFromJsDelivr=on&#038;maxHeight=450\"><\/script><\/p>\n<p>Keep access to the sensor, and now every time my source task calls the weather API, I just need to call <code style=\"font-weight: bold; color: #770000;\">record()<\/code> on the sensor.<\/p>\n<h3>Provide insight to inform connector config and tuning<\/h3>\n<p>This sample Connector is <a href=\"https:\/\/github.com\/dalelane\/kafka-connect-developers-intro-tutorial\/blob\/900aca09bd7a29ac7e417508ddb536c340873cb3\/my-connector-project\/src\/main\/java\/com\/ibm\/eventstreams\/kafkaconnect\/connectors\/source\/MyDataFetcher.java#L86\">polling a REST API<\/a>, and <a href=\"https:\/\/github.com\/dalelane\/kafka-connect-developers-intro-tutorial\/blob\/900aca09bd7a29ac7e417508ddb536c340873cb3\/my-connector-project\/src\/main\/java\/com\/ibm\/eventstreams\/kafkaconnect\/connectors\/source\/MyDataFetcher.java#L97\">ignoring any responses that have already been sent<\/a> to Kafka. This means it gives the user the job of making a sensible <a href=\"https:\/\/github.com\/dalelane\/kafka-connect-developers-intro-tutorial\/blob\/900aca09bd7a29ac7e417508ddb536c340873cb3\/source-connector.properties#L5\">choice about how frequently to poll<\/a>, but without an easy way to know how to make that choice.<\/p>\n<p>Metrics could help with this. By emitting a count of how many API calls are being made and ignored, the user can see whether they&#8217;ve made the right choice.<\/p>\n<p>Lots of Connectors have a similar requirement to this &#8211; a user configuration option that will effect the Connector&#8217;s performance or the impact it has on source systems, but without clear visibility needed to best set that config option.<\/p>\n<p><script src=\"https:\/\/emgithub.com\/embed-v2.js?target=https%3A%2F%2Fgithub.com%2Fdalelane%2Fkafka-connect-developers-intro-tutorial%2Fblob%2F97c1d8604fc1fe9690a82d1b3b5bc09f997f2b53%2Fmy-connector-project%2Fsrc%2Fmain%2Fjava%2Fcom%2Fibm%2Feventstreams%2Fkafkaconnect%2Fconnectors%2Fsource%2FMyDataFetcherMetrics.java%23L72-L77&#038;style=default&#038;type=code&#038;showBorder=on&#038;showFileMeta=on&#038;fetchFromJsDelivr=on&#038;maxHeight=450\"><\/script><\/p>\n<p>In this case, it was as simple as preparing a sensor to count ignored API call responses, adding a count metric to it&#8230;<\/p>\n<p><script src=\"https:\/\/emgithub.com\/embed-v2.js?target=https%3A%2F%2Fgithub.com%2Fdalelane%2Fkafka-connect-developers-intro-tutorial%2Fblob%2Fmain%2Fmy-connector-project%2Fsrc%2Fmain%2Fjava%2Fcom%2Fibm%2Feventstreams%2Fkafkaconnect%2Fconnectors%2Fsource%2FMyDataFetcherMetrics.java%23L134&#038;style=default&#038;type=code&#038;showBorder=on&#038;showFileMeta=on&#038;fetchFromJsDelivr=on&#038;maxHeight=450\"><\/script><\/p>\n<p>&#8230;and then calling <code style=\"font-weight: bold; color: #770000;\">record()<\/code> on the sensor every time the Connector ignores an API response.<\/p>\n<h3>Compute multiple metrics for one sensor<\/h3>\n<p>You don&#8217;t have to choose only one <a href=\"https:\/\/github.com\/apache\/kafka\/tree\/trunk\/clients\/src\/main\/java\/org\/apache\/kafka\/common\/metrics\/stats\">type of metric<\/a> for what you record in a sensor.<\/p>\n<p>For example, I created a sensor that I would use to record any time that the API call failed or returned an error.<\/p>\n<p><script src=\"https:\/\/emgithub.com\/embed-v2.js?target=https%3A%2F%2Fgithub.com%2Fdalelane%2Fkafka-connect-developers-intro-tutorial%2Fblob%2F97c1d8604fc1fe9690a82d1b3b5bc09f997f2b53%2Fmy-connector-project%2Fsrc%2Fmain%2Fjava%2Fcom%2Fibm%2Feventstreams%2Fkafkaconnect%2Fconnectors%2Fsource%2FMyDataFetcherMetrics.java%23L79-L89&#038;style=default&#038;type=code&#038;showBorder=on&#038;showFileMeta=on&#038;fetchFromJsDelivr=on&#038;maxHeight=450\"><\/script><\/p>\n<p>I created two metrics for this:<\/p>\n<ul>\n<li>a <a href=\"https:\/\/github.com\/apache\/kafka\/blob\/trunk\/clients\/src\/main\/java\/org\/apache\/kafka\/common\/metrics\/stats\/Rate.java\">rate<\/a> metric to track the rate of API errors over an hour<\/li>\n<li>a <a href=\"https:\/\/github.com\/apache\/kafka\/blob\/trunk\/clients\/src\/main\/java\/org\/apache\/kafka\/common\/metrics\/stats\/CumulativeCount.java\">count<\/a> metric to track the number of API errors seen so far <\/li>\n<\/ul>\n<p><script src=\"https:\/\/emgithub.com\/embed-v2.js?target=https%3A%2F%2Fgithub.com%2Fdalelane%2Fkafka-connect-developers-intro-tutorial%2Fblob%2F97c1d8604fc1fe9690a82d1b3b5bc09f997f2b53%2Fmy-connector-project%2Fsrc%2Fmain%2Fjava%2Fcom%2Fibm%2Feventstreams%2Fkafkaconnect%2Fconnectors%2Fsource%2FMyDataFetcherMetrics.java%23L122&#038;style=default&#038;type=code&#038;showBorder=on&#038;showFileMeta=on&#038;fetchFromJsDelivr=on&#038;maxHeight=450\"><\/script><\/p>\n<p>I still only need to record an API failure once, but this one event is computed and emitted as two types of metrics.<\/p>\n<h3>Recording values in metrics<\/h3>\n<p>You don&#8217;t have to solely record that something has happened in metrics, you can record values with them, too.<\/p>\n<p>For example, how long do the API calls take? I added <code style=\"font-weight: bold; color: #770000;\">System.nanoTime()<\/code> calls before and after the API poll:<\/p>\n<p><script src=\"https:\/\/emgithub.com\/embed-v2.js?target=https%3A%2F%2Fgithub.com%2Fdalelane%2Fkafka-connect-developers-intro-tutorial%2Fblob%2Fmain%2Fmy-connector-project%2Fsrc%2Fmain%2Fjava%2Fcom%2Fibm%2Feventstreams%2Fkafkaconnect%2Fconnectors%2Fsource%2FMyDataFetcher.java%23L92-L94&#038;style=default&#038;type=code&#038;showBorder=on&#038;showFileMeta=on&#038;fetchFromJsDelivr=on&#038;maxHeight=450\"><\/script><\/p>\n<p>By recording this API response time, I can use metrics to keep track on whether the API behaviour is changing or introducing problems.<\/p>\n<p><script src=\"https:\/\/emgithub.com\/embed-v2.js?target=https%3A%2F%2Fgithub.com%2Fdalelane%2Fkafka-connect-developers-intro-tutorial%2Fblob%2F97c1d8604fc1fe9690a82d1b3b5bc09f997f2b53%2Fmy-connector-project%2Fsrc%2Fmain%2Fjava%2Fcom%2Fibm%2Feventstreams%2Fkafkaconnect%2Fconnectors%2Fsource%2FMyDataFetcherMetrics.java%23L91-L107&#038;style=default&#038;type=code&#038;showBorder=on&#038;showFileMeta=on&#038;fetchFromJsDelivr=on&#038;maxHeight=450\"><\/script><\/p>\n<p>I created a sensor for API response times, and registered a few metrics:<\/p>\n<ul>\n<li><a href=\"https:\/\/github.com\/apache\/kafka\/blob\/trunk\/clients\/src\/main\/java\/org\/apache\/kafka\/common\/metrics\/stats\/Avg.java\">average<\/a> &#8211; average API response time within the time window<\/li>\n<li><a href=\"https:\/\/github.com\/apache\/kafka\/blob\/trunk\/clients\/src\/main\/java\/org\/apache\/kafka\/common\/metrics\/stats\/Min.java\">minimum<\/a> &#8211; fastest API response time within the time window<\/li>\n<li><a href=\"https:\/\/github.com\/apache\/kafka\/blob\/trunk\/clients\/src\/main\/java\/org\/apache\/kafka\/common\/metrics\/stats\/Max.java\">maximum<\/a> &#8211; slowest API response time within the time window<\/li>\n<\/ul>\n<p>The only difference in how to record this is to include the response time value in the <code style=\"font-weight: bold; color: #770000;\">record<\/code> calls to the sensor.<\/p>\n<p><script src=\"https:\/\/emgithub.com\/embed-v2.js?target=https%3A%2F%2Fgithub.com%2Fdalelane%2Fkafka-connect-developers-intro-tutorial%2Fblob%2F97c1d8604fc1fe9690a82d1b3b5bc09f997f2b53%2Fmy-connector-project%2Fsrc%2Fmain%2Fjava%2Fcom%2Fibm%2Feventstreams%2Fkafkaconnect%2Fconnectors%2Fsource%2FMyDataFetcher.java%23L96&#038;style=default&#038;type=code&#038;showBorder=on&#038;showFileMeta=on&#038;fetchFromJsDelivr=on&#038;maxHeight=450\"><\/script><br \/>\n<script src=\"https:\/\/emgithub.com\/embed-v2.js?target=https%3A%2F%2Fgithub.com%2Fdalelane%2Fkafka-connect-developers-intro-tutorial%2Fblob%2F97c1d8604fc1fe9690a82d1b3b5bc09f997f2b53%2Fmy-connector-project%2Fsrc%2Fmain%2Fjava%2Fcom%2Fibm%2Feventstreams%2Fkafkaconnect%2Fconnectors%2Fsource%2FMyDataFetcherMetrics.java%23L138-L143&#038;style=default&#038;type=code&#038;showBorder=on&#038;showFileMeta=on&#038;fetchFromJsDelivr=on&#038;maxHeight=450\"><\/script><\/p>\n<h3>Try it out<\/h3>\n<p>If you&#8217;d like to try this sample out for yourself from scratch:<\/p>\n<ol>\n<li><a href=\"https:\/\/github.com\/dalelane\/kafka-connect-developers-intro-tutorial\/blob\/main\/01-download-kafka.sh\"><code style=\"font-weight: bold; color: #770000;\">01-download-kafka.sh<\/code><\/a> &#8211; download a recent version of Kafka<\/li>\n<li><a href=\"https:\/\/github.com\/dalelane\/kafka-connect-developers-intro-tutorial\/blob\/main\/02-run-kafka.sh\"><code style=\"font-weight: bold; color: #770000;\">02-run-kafka.sh<\/code><\/a> &#8211; start Kafka running<\/li>\n<li><a href=\"https:\/\/github.com\/dalelane\/kafka-connect-developers-intro-tutorial\/blob\/main\/05-build-connectors.sh\"><code style=\"font-weight: bold; color: #770000;\">05-build-connectors.sh<\/code><\/a> &#8211; build the sample connector<\/li>\n<li><a href=\"https:\/\/github.com\/dalelane\/kafka-connect-developers-intro-tutorial\/blob\/main\/08-consume-my-topic.sh\"><code style=\"font-weight: bold; color: #770000;\">08-consume-my-topic.sh<\/code><\/a> &#8211; optionally, start consuming from the topic to see the weather reports as they arrive<\/li>\n<li>get an API key from <a href=\"https:\/\/www.weatherapi.com\/\">weatherapi.com<\/a> and add it to the <a href=\"https:\/\/github.com\/dalelane\/kafka-connect-developers-intro-tutorial\/blob\/main\/source-connector.properties#L3\">connector config<\/a><\/li>\n<li><a href=\"https:\/\/github.com\/dalelane\/kafka-connect-developers-intro-tutorial\/blob\/main\/09-run-source-connector.sh\"><code style=\"font-weight: bold; color: #770000;\">09-run-source-connector.sh<\/code><\/a> &#8211; start the connector running<\/li>\n<li><code style=\"font-weight: bold; color: #770000;\">jconsole<\/code> &#8211; fire up jconsole to see the metrics the Connector is emitting<\/li>\n<\/ol>\n<p><img decoding=\"async\" src=\"https:\/\/images.dalelane.co.uk\/2026-05-02-connect-metrics\/metrics.jpg\" style=\"border: thin black solid; width: 100%; max-width: 600px;\"\/><\/p>\n<p>You&#8217;ll find your metrics at: <strong>kafka.connect<\/strong> -&gt; <strong>plugins<\/strong> -&gt; <strong>your-connector-id<\/strong> -&gt; <strong>tasknum<\/strong><\/p>\n<p><img decoding=\"async\" src=\"https:\/\/images.dalelane.co.uk\/2026-05-02-connect-metrics\/values.jpg\" style=\"border: thin black solid; width: 100%; max-width: 600px;\"\/><\/p>\n<p>And you&#8217;ll see values for all the metrics I&#8217;ve described above.<\/p>\n<h3>Don&#8217;t reinvent the metrics wheel<\/h3>\n<p>A tip when doing this for your own connectors is to keep an eye on the metrics that are already available out of the box from the Connect framework, and the Kafka consumers\/producers used by your connector.<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/images.dalelane.co.uk\/2026-05-02-connect-metrics\/screenshot.jpg\" style=\"border: thin black solid; width: 100%; max-width: 600px;\"\/><\/p>\n<p>A lot of metrics that you might be tempted to try and create in your specific connector might already be available to you from the framework as generic metrics. Instead of recreating those, focus on metrics that give insight into the unique behaviour and implementation of your own connector.<\/p>\n<h3>Alerting, visualisations, dashboards<\/h3>\n<p>jconsole is useful for a quick and easy test on my laptop, but obviously I&#8217;d <a href=\"https:\/\/dalelane.co.uk\/blog\/?p=5914\">usually use something like a Grafana \/ Prometheus stack<\/a> to track these metrics for real.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Metrics can help provide operational insight over Kafka Connect connectors, informing users of how to better configure them. With simple updates, a Kafka Connect connector can be instrumented to make this possible by emitting useful metrics. A couple years ago, I created a simple skeleton Connect connector project to help developers at a hackathon create [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":6049,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[7],"tags":[593,584],"class_list":["post-6045","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-code","tag-apachekafka","tag-kafka"],"_links":{"self":[{"href":"https:\/\/dalelane.co.uk\/blog\/index.php?rest_route=\/wp\/v2\/posts\/6045","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/dalelane.co.uk\/blog\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/dalelane.co.uk\/blog\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/dalelane.co.uk\/blog\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/dalelane.co.uk\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=6045"}],"version-history":[{"count":4,"href":"https:\/\/dalelane.co.uk\/blog\/index.php?rest_route=\/wp\/v2\/posts\/6045\/revisions"}],"predecessor-version":[{"id":6050,"href":"https:\/\/dalelane.co.uk\/blog\/index.php?rest_route=\/wp\/v2\/posts\/6045\/revisions\/6050"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/dalelane.co.uk\/blog\/index.php?rest_route=\/wp\/v2\/media\/6049"}],"wp:attachment":[{"href":"https:\/\/dalelane.co.uk\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=6045"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/dalelane.co.uk\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=6045"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/dalelane.co.uk\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=6045"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}