Skip to content

Conversation

@piochelepiotr
Copy link
Collaborator

@piochelepiotr piochelepiotr commented Oct 15, 2025

Problem

Kafka exposes its cluster ID via JMX, but as an attribute value rather than in the bean path:

  • Bean: kafka.server:type=KafkaServer,name=ClusterId
  • Attribute: Value → returns the actual cluster ID (e.g., "kafka-prod-cluster-01")

Currently, there's no way to extract this value and tag all Kafka metrics with it. Users must either:

  1. Hardcode the cluster ID for each environment (error-prone, not maintainable)
  2. Don't use cluster_id tag: End up mixing metrics for topics with the same name, in different clusters. Can't build reliable features on top of these metrics (For Data Streams Monitoring)

This becomes particularly problematic when monitoring multiple Kafka clusters or across different environments (dev/staging/prod).

Solution

This PR adds support for configuration-level dynamic tags that can extract values from JMX bean attributes at runtime and apply them to all matching metrics.

Syntax:

instances:
  - host: kafka-broker
    port: 9999
    conf:
      - include:
          domain: kafka.server
          attribute:
            MessagesInPerSec:
              metric_type: rate
         dynamic_tags:
           - tag_name: kafka_cluster_id
              bean_name: kafka.server:type=KafkaServer,name=ClusterId
              attribute: Value

Example: Kafka Cluster ID

All Kafka metrics automatically tagged with the cluster ID:

kafka.messages_in[cluster_id:kafka-prod-cluster-01,...] = 1500 msg/s
kafka.bytes_in[cluster_id:kafka-prod-cluster-01,...] = 150MB/s

@piochelepiotr piochelepiotr force-pushed the piotr.wolski/add-dynamic-tags branch from 6a260b9 to 882f7c8 Compare October 15, 2025 21:11
@piochelepiotr piochelepiotr force-pushed the piotr.wolski/add-dynamic-tags branch from 882f7c8 to 9961226 Compare October 15, 2025 21:17
String tagName = entry.getKey();
String tagValue = entry.getValue();

if (tagValue != null && tagValue.contains("#") && tagValue.startsWith("$")) {
Copy link
Collaborator Author

@piochelepiotr piochelepiotr Oct 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I want to make sure normal tags are not being mistaken for JMX tags, but I think this condition is strict enough that it won't happen. A tag starting with $ is not valid anyway.
Also, the parsing is in a try / catch, the catch adds it to normal tags.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I want to make sure normal tags are not being mistaken for JMX tags,

More robust way would be to have a distinct configuration section for computed tags, and would avoid the need for bespoke syntax.

Also, jmxfetch integrations are configured in yaml, where "#" denotes a comment. Would it be possible to use a different separator? Or perhaps even be explicit and use two keys: bean_name and attribute.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense! You actually made me think about backward compatibility (I want to make sure users can update their integration configurations without worrying about breaking their integration).

See PR: DataDog/integrations-core#21687

Adding a dynamic_tags section at the same level as the include section is backwards compatible (I can't add the dynamic_tags section inside the include section). I find the current format pretty clean, and backwards compatible.

@piochelepiotr piochelepiotr marked this pull request as ready for review October 16, 2025 02:50
@piochelepiotr piochelepiotr requested a review from a team as a code owner October 16, 2025 02:50
String tagName = entry.getKey();
String tagValue = entry.getValue();

if (tagValue != null && tagValue.contains("#") && tagValue.startsWith("$")) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I want to make sure normal tags are not being mistaken for JMX tags,

More robust way would be to have a distinct configuration section for computed tags, and would avoid the need for bespoke syntax.

Also, jmxfetch integrations are configured in yaml, where "#" denotes a comment. Would it be possible to use a different separator? Or perhaps even be explicit and use two keys: bean_name and attribute.

return getInclude() != null && !getInclude().isEmptyFilter();
}

/** Resolve dynamic tags for this configuration using cached values. */
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you please make the comment more explicit? What does "Resolve" mean in this case, what are the inputs and outputs?

Object attrObj = config.get("attribute");

if (tagNameObj == null || beanObj == null || attrObj == null) {
log.warn("Invalid dynamic tag config: missing 'tag_name', 'bean' or 'attribute' key");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's be explicit what is missing.


Map<String, Object> config = (Map<String, Object>) tagConfig;
Object tagNameObj = config.get("tag_name");
Object beanObj = config.get("bean");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I may be wrong, but I think this refers to the same string as bean_name in the filter section. Would it make sense to use the same key name here?

Suggested change
Object beanObj = config.get("bean");
Object beanObj = config.get("bean_name");

private Filter include;
private Filter exclude;
private List<DynamicTag> dynamicTags = null;
private Map<String, String> resolvedDynamicTags = null;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Up until now, this object represented only user configuration with no runtime data. Would it be possible to keep it this way?

We can keep any runtime data inside Instance and JmxAttribute. For example JmxAttribute can retrieve tag values from the instance cache once we found a matching configuration.

/** Sets a matching configuration for the attribute. */
public void setMatchingConf(Configuration matchingConf) {
public void setMatchingConf(Configuration matchingConf,
Map<String, String> resolvedDynamicTags) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's use a separate setter for dynamic tags.

}

for (DynamicTag dynamicTag : allDynamicTags) {
String cacheKey = dynamicTag.getBeanName() + "#" + dynamicTag.getAttributeName();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is duplicated here and in the next method. Would it be possible to make it a method of DynamicTag?

if (!this.dynamicTagsCache.containsKey(cacheKey)) {
Map.Entry<String, String> resolved = dynamicTag.resolve(connection);
if (resolved != null) {
this.dynamicTagsCache.put(cacheKey, resolved);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If dynamic tag references a non-existing attribute, we would try to retrieve it multiple times (once for each config it is part of). Would it be possible to cache negative lookups as well, so we don't spam the logs with identical errors?

@piochelepiotr
Copy link
Collaborator Author

/merge

@dd-devflow-routing-codex
Copy link

dd-devflow-routing-codex bot commented Oct 24, 2025

View all feedbacks in Devflow UI.

2025-10-24 15:59:08 UTC ℹ️ Start processing command /merge


2025-10-24 15:59:13 UTC ℹ️ MergeQueue: pull request added to the queue

The expected merge time in master is approximately 0s (p90).


2025-10-24 16:13:35 UTC ℹ️ MergeQueue: This merge request was merged

@dd-mergequeue dd-mergequeue bot merged commit efd946a into master Oct 24, 2025
19 checks passed
@jszwedko jszwedko deleted the piotr.wolski/add-dynamic-tags branch October 27, 2025 16:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants