Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion processor/k8sattributesprocessor/config.schema.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ $defs:
description: KeyRegex is a regular expression used to extract a Key that matches the regex. Out of Key or KeyRegex, only one option is expected to be configured at a time.
type: string
tag_name:
description: 'TagName represents the name of the resource attribute that will be added to logs, metrics or spans. When not specified, a default tag name will be used of the format: - k8s.pod.annotations.<annotation key> (or k8s.pod.annotation.<annotation key> when k8sattr.labelsAnnotationsSingular.allow is enabled) - k8s.pod.labels.<label key> (or k8s.pod.label.<label key> when k8sattr.labelsAnnotationsSingular.allow is enabled) For example, if tag_name is not specified and the key is git_sha, then the attribute name will be `k8s.pod.annotations.git_sha` (or `k8s.pod.annotation.git_sha` with the feature gate). When key_regex is present, tag_name supports back reference to both named capturing and positioned capturing. For example, if your pod spec contains the following labels, app.kubernetes.io/component: mysql app.kubernetes.io/version: 5.7.21 and you''d like to add tags for all labels with prefix app.kubernetes.io/ and also trim the prefix, then you can specify the following extraction rules: extract: labels: - tag_name: $$1 key_regex: kubernetes.io/(.*) this will add the `component` and `version` tags to the spans or metrics.'
description: 'TagName represents the name of the resource attribute that will be added to logs, metrics or spans. When not specified, a default tag name will be used of the format: - k8s.pod.annotations.<annotation key> (or k8s.pod.annotation.<annotation key> when processor.k8sattributes.EmitV1K8sConventions is enabled) - k8s.pod.labels.<label key> (or k8s.pod.label.<label key> when processor.k8sattributes.EmitV1K8sConventions is enabled) For example, if tag_name is not specified and the key is git_sha, then the attribute name will be `k8s.pod.annotations.git_sha` (or `k8s.pod.annotation.git_sha` with the feature gate). When key_regex is present, tag_name supports back reference to both named capturing and positioned capturing. For example, if your pod spec contains the following labels, app.kubernetes.io/component: mysql app.kubernetes.io/version: 5.7.21 and you''d like to add tags for all labels with prefix app.kubernetes.io/ and also trim the prefix, then you can specify the following extraction rules: extract: labels: - tag_name: $$1 key_regex: kubernetes.io/(.*) this will add the `component` and `version` tags to the spans or metrics.'
type: string
field_filter_config:
description: FieldFilterConfig allows specifying exactly one filter by a field. It can be used to represent a label or generic field filter.
Expand Down
50 changes: 50 additions & 0 deletions processor/k8sattributesprocessor/internal/kube/attributes.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

package kube // import "github.com/open-telemetry/opentelemetry-collector-contrib/processor/k8sattributesprocessor/internal/kube"

import (
"fmt"

"go.opentelemetry.io/otel/attribute"
)

// AttributesFunction is a function type that takes a key and value and returns an attribute.KeyValue.
// This is the signature used by semantic convention functions like conventions.K8SPodLabel.
type AttributesFunction func(key, val string) attribute.KeyValue

// K8SPodLabels returns an attribute KeyValue for pod labels using the plural form.
// Helper functions for plural forms (labels/annotations) that follow the same pattern as semconv functions.
// These are used for backward compatibility with historical attribute naming.
// From historical reasons some of workloads are using `*.labels.*` and `*.annotations.*` instead of
// `*.label.*` and `*.annotation.*`
// Semantic conventions define `*.label.*` and `*.annotation.*`
// More information - https://github.com/open-telemetry/opentelemetry-collector-contrib/issues/37957
func K8SPodLabels(key, val string) attribute.KeyValue {
return attribute.String(fmt.Sprintf("k8s.pod.labels.%s", key), val)
}

// K8SPodAnnotations returns an attribute KeyValue for pod annotations using the plural form
func K8SPodAnnotations(key, val string) attribute.KeyValue {
return attribute.String(fmt.Sprintf("k8s.pod.annotations.%s", key), val)
}

// K8SNodeLabels returns an attribute KeyValue for node labels using the plural form
func K8SNodeLabels(key, val string) attribute.KeyValue {
return attribute.String(fmt.Sprintf("k8s.node.labels.%s", key), val)
}

// K8SNodeAnnotations returns an attribute KeyValue for node annotations using the plural form
func K8SNodeAnnotations(key, val string) attribute.KeyValue {
return attribute.String(fmt.Sprintf("k8s.node.annotations.%s", key), val)
}

// K8SNamespaceLabels returns an attribute KeyValue for namespace labels using the plural form
func K8SNamespaceLabels(key, val string) attribute.KeyValue {
return attribute.String(fmt.Sprintf("k8s.namespace.labels.%s", key), val)
}

// K8SNamespaceAnnotations returns an attribute KeyValue for namespace annotations using the plural form
func K8SNamespaceAnnotations(key, val string) attribute.KeyValue {
return attribute.String(fmt.Sprintf("k8s.namespace.annotations.%s", key), val)
}
71 changes: 20 additions & 51 deletions processor/k8sattributesprocessor/internal/kube/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,37 +33,6 @@ import (
"github.com/open-telemetry/opentelemetry-collector-contrib/processor/k8sattributesprocessor/internal/metadata"
)

const (
// From historical reasons some of workloads are using `*.labels.*` and `*.annotations.*` instead of
// `*.label.*` and `*.annotation.*`
// Sematic conventions define `*.label.*` and `*.annotation.*`
// More information - https://github.com/open-telemetry/opentelemetry-collector-contrib/issues/37957
K8sPodLabelsKey = "k8s.pod.labels.%s"
K8sPodLabelKey = "k8s.pod.label.%s"
K8sPodAnnotationsKey = "k8s.pod.annotations.%s"
K8sPodAnnotationKey = "k8s.pod.annotation.%s"
K8sNodeLabelsKey = "k8s.node.labels.%s"
K8sNodeLabelKey = "k8s.node.label.%s"
K8sNodeAnnotationsKey = "k8s.node.annotations.%s"
K8sNodeAnnotationKey = "k8s.node.annotation.%s"
K8sNamespaceLabelsKey = "k8s.namespace.labels.%s"
K8sNamespaceLabelKey = "k8s.namespace.label.%s"
K8sNamespaceAnnotationsKey = "k8s.namespace.annotations.%s"
K8sNamespaceAnnotationKey = "k8s.namespace.annotation.%s"
// Semconv attributes https://github.com/open-telemetry/semantic-conventions/blob/main/docs/resource/k8s.md#deployment
K8sDeploymentLabel = "k8s.deployment.label.%s"
K8sDeploymentAnnotation = "k8s.deployment.annotation.%s"
// Semconv attributes https://github.com/open-telemetry/semantic-conventions/blob/main/docs/resource/k8s.md#statefulset
K8sStatefulSetLabel = "k8s.statefulset.label.%s"
K8sStatefulSetAnnotation = "k8s.statefulset.annotation.%s"
// Semconv attributes https://github.com/open-telemetry/semantic-conventions/blob/main/docs/resource/k8s.md#daemonset
K8sDaemonSetLabel = "k8s.daemonset.label.%s"
K8sDaemonSetAnnotation = "k8s.daemonset.annotation.%s"
// Semconv attributes https://github.com/open-telemetry/semantic-conventions/blob/main/docs/resource/k8s.md#job
K8sJobLabel = "k8s.job.label.%s"
K8sJobAnnotation = "k8s.job.annotation.%s"
)

// WatchClient is the main interface provided by this package to a kubernetes cluster.
type WatchClient struct {
m sync.RWMutex
Expand Down Expand Up @@ -923,19 +892,19 @@ func (c *WatchClient) extractPodAttributes(pod *api_v1.Pod) map[string]string {

for _, r := range c.Rules.Labels {
if !disableLegacy {
r.extractFromPodMetadata(pod.Labels, tags, K8sPodLabelsKey)
r.extractFromPodMetadata(pod.Labels, tags, K8SPodLabels)
}
if enableStable {
r.extractFromPodMetadata(pod.Labels, tags, K8sPodLabelKey)
r.extractFromPodMetadata(pod.Labels, tags, conventions.K8SPodLabel)
}
}

for _, r := range c.Rules.Annotations {
if !disableLegacy {
r.extractFromPodMetadata(pod.Annotations, tags, K8sPodAnnotationsKey)
r.extractFromPodMetadata(pod.Annotations, tags, K8SPodAnnotations)
}
if enableStable {
r.extractFromPodMetadata(pod.Annotations, tags, K8sPodAnnotationKey)
r.extractFromPodMetadata(pod.Annotations, tags, conventions.K8SPodAnnotation)
}
}

Expand Down Expand Up @@ -1172,19 +1141,19 @@ func (c *WatchClient) extractNamespaceAttributes(namespace *api_v1.Namespace) ma

for _, r := range c.Rules.Labels {
if !disableLegacy {
r.extractFromNamespaceMetadata(namespace.Labels, tags, K8sNamespaceLabelsKey)
r.extractFromNamespaceMetadata(namespace.Labels, tags, K8SNamespaceLabels)
}
if enableStable {
r.extractFromNamespaceMetadata(namespace.Labels, tags, K8sNamespaceLabelKey)
r.extractFromNamespaceMetadata(namespace.Labels, tags, conventions.K8SNamespaceLabel)
}
}

for _, r := range c.Rules.Annotations {
if !disableLegacy {
r.extractFromNamespaceMetadata(namespace.Annotations, tags, K8sNamespaceAnnotationsKey)
r.extractFromNamespaceMetadata(namespace.Annotations, tags, K8SNamespaceAnnotations)
}
if enableStable {
r.extractFromNamespaceMetadata(namespace.Annotations, tags, K8sNamespaceAnnotationKey)
r.extractFromNamespaceMetadata(namespace.Annotations, tags, conventions.K8SNamespaceAnnotation)
}
}

Expand All @@ -1199,19 +1168,19 @@ func (c *WatchClient) extractNodeAttributes(node *api_v1.Node) map[string]string

for _, r := range c.Rules.Labels {
if !disableLegacy {
r.extractFromNodeMetadata(node.Labels, tags, K8sNodeLabelsKey)
r.extractFromNodeMetadata(node.Labels, tags, K8SNodeLabels)
}
if enableStable {
r.extractFromNodeMetadata(node.Labels, tags, K8sNodeLabelKey)
r.extractFromNodeMetadata(node.Labels, tags, conventions.K8SNodeLabel)
}
}

for _, r := range c.Rules.Annotations {
if !disableLegacy {
r.extractFromNodeMetadata(node.Annotations, tags, K8sNodeAnnotationsKey)
r.extractFromNodeMetadata(node.Annotations, tags, K8SNodeAnnotations)
}
if enableStable {
r.extractFromNodeMetadata(node.Annotations, tags, K8sNodeAnnotationKey)
r.extractFromNodeMetadata(node.Annotations, tags, conventions.K8SNodeAnnotation)
}
}
return tags
Expand All @@ -1221,11 +1190,11 @@ func (c *WatchClient) extractDeploymentAttributes(d *apps_v1.Deployment) map[str
tags := map[string]string{}

for _, r := range c.Rules.Labels {
r.extractFromDeploymentMetadata(d.Labels, tags, K8sDeploymentLabel)
r.extractFromDeploymentMetadata(d.Labels, tags, conventions.K8SDeploymentLabel)
}

for _, r := range c.Rules.Annotations {
r.extractFromDeploymentMetadata(d.Annotations, tags, K8sDeploymentAnnotation)
r.extractFromDeploymentMetadata(d.Annotations, tags, conventions.K8SDeploymentAnnotation)
}

return tags
Expand All @@ -1235,11 +1204,11 @@ func (c *WatchClient) extractStatefulSetAttributes(d *apps_v1.StatefulSet) map[s
tags := map[string]string{}

for _, r := range c.Rules.Labels {
r.extractFromStatefulSetMetadata(d.Labels, tags, K8sStatefulSetLabel)
r.extractFromStatefulSetMetadata(d.Labels, tags, conventions.K8SStatefulSetLabel)
}

for _, r := range c.Rules.Annotations {
r.extractFromStatefulSetMetadata(d.Annotations, tags, K8sStatefulSetAnnotation)
r.extractFromStatefulSetMetadata(d.Annotations, tags, conventions.K8SStatefulSetAnnotation)
}

return tags
Expand All @@ -1249,11 +1218,11 @@ func (c *WatchClient) extractDaemonSetAttributes(d *apps_v1.DaemonSet) map[strin
tags := map[string]string{}

for _, r := range c.Rules.Labels {
r.extractFromDaemonSetMetadata(d.Labels, tags, K8sDaemonSetLabel)
r.extractFromDaemonSetMetadata(d.Labels, tags, conventions.K8SDaemonSetLabel)
}

for _, r := range c.Rules.Annotations {
r.extractFromDaemonSetMetadata(d.Annotations, tags, K8sDaemonSetAnnotation)
r.extractFromDaemonSetMetadata(d.Annotations, tags, conventions.K8SDaemonSetAnnotation)
}

return tags
Expand All @@ -1263,11 +1232,11 @@ func (c *WatchClient) extractJobAttributes(d *batch_v1.Job) map[string]string {
tags := map[string]string{}

for _, r := range c.Rules.Labels {
r.extractFromJobMetadata(d.Labels, tags, K8sJobLabel)
r.extractFromJobMetadata(d.Labels, tags, conventions.K8SJobLabel)
}

for _, r := range c.Rules.Annotations {
r.extractFromJobMetadata(d.Annotations, tags, K8sJobAnnotation)
r.extractFromJobMetadata(d.Annotations, tags, conventions.K8SJobAnnotation)
}

return tags
Expand Down
41 changes: 21 additions & 20 deletions processor/k8sattributesprocessor/internal/kube/kube.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
package kube // import "github.com/open-telemetry/opentelemetry-collector-contrib/processor/k8sattributesprocessor/internal/kube"

import (
"fmt"
"regexp"
"time"

Expand Down Expand Up @@ -314,68 +313,70 @@ type FieldExtractionRule struct {
From string
}

func (r *FieldExtractionRule) extractFromPodMetadata(metadata, tags map[string]string, formatter string) {
func (r *FieldExtractionRule) extractFromPodMetadata(metadata, tags map[string]string, attrFunc AttributesFunction) {
// By default if the From field is not set for labels and annotations we want to extract them from pod
if r.From == MetadataFromPod || r.From == "" {
r.extractFromMetadata(metadata, tags, formatter)
r.extractFromMetadata(metadata, tags, attrFunc)
}
}

func (r *FieldExtractionRule) extractFromNamespaceMetadata(metadata, tags map[string]string, formatter string) {
func (r *FieldExtractionRule) extractFromNamespaceMetadata(metadata, tags map[string]string, attrFunc AttributesFunction) {
if r.From == MetadataFromNamespace {
r.extractFromMetadata(metadata, tags, formatter)
r.extractFromMetadata(metadata, tags, attrFunc)
}
}

func (r *FieldExtractionRule) extractFromNodeMetadata(metadata, tags map[string]string, formatter string) {
func (r *FieldExtractionRule) extractFromNodeMetadata(metadata, tags map[string]string, attrFunc AttributesFunction) {
if r.From == MetadataFromNode {
r.extractFromMetadata(metadata, tags, formatter)
r.extractFromMetadata(metadata, tags, attrFunc)
}
}

func (r *FieldExtractionRule) extractFromDeploymentMetadata(metadata, tags map[string]string, formatter string) {
func (r *FieldExtractionRule) extractFromDeploymentMetadata(metadata, tags map[string]string, attrFunc AttributesFunction) {
if r.From == MetadataFromDeployment {
r.extractFromMetadata(metadata, tags, formatter)
r.extractFromMetadata(metadata, tags, attrFunc)
}
}

func (r *FieldExtractionRule) extractFromStatefulSetMetadata(metadata, tags map[string]string, formatter string) {
func (r *FieldExtractionRule) extractFromStatefulSetMetadata(metadata, tags map[string]string, attrFunc AttributesFunction) {
if r.From == MetadataFromStatefulSet {
r.extractFromMetadata(metadata, tags, formatter)
r.extractFromMetadata(metadata, tags, attrFunc)
}
}

func (r *FieldExtractionRule) extractFromDaemonSetMetadata(metadata, tags map[string]string, formatter string) {
func (r *FieldExtractionRule) extractFromDaemonSetMetadata(metadata, tags map[string]string, attrFunc AttributesFunction) {
if r.From == MetadataFromDaemonSet {
r.extractFromMetadata(metadata, tags, formatter)
r.extractFromMetadata(metadata, tags, attrFunc)
}
}

func (r *FieldExtractionRule) extractFromJobMetadata(metadata, tags map[string]string, formatter string) {
func (r *FieldExtractionRule) extractFromJobMetadata(metadata, tags map[string]string, attrFunc AttributesFunction) {
if r.From == MetadataFromJob {
r.extractFromMetadata(metadata, tags, formatter)
r.extractFromMetadata(metadata, tags, attrFunc)
}
}

func (r *FieldExtractionRule) extractFromMetadata(metadata, tags map[string]string, formatter string) {
func (r *FieldExtractionRule) extractFromMetadata(metadata, tags map[string]string, attrFunc AttributesFunction) {
if r.KeyRegex != nil {
for k, v := range metadata {
if r.KeyRegex.MatchString(k) && v != "" {
var name string
if r.HasKeyRegexReference {
var result []byte
name = string(r.KeyRegex.ExpandString(result, r.Name, k, r.KeyRegex.FindStringSubmatchIndex(k)))
tags[name] = v
} else {
name = fmt.Sprintf(formatter, k)
kv := attrFunc(k, v)
tags[string(kv.Key)] = kv.Value.AsString()
}
tags[name] = v
}
}
} else if v, ok := metadata[r.Key]; ok {
// Use formatter to determine attribute name if no custom name was specified
// Use attrFunc to determine attribute name if no custom name was specified
name := r.Name
if name == "" {
name = fmt.Sprintf(formatter, r.Key)
kv := attrFunc(r.Key, v)
name = string(kv.Key)
}
tags[name] = r.extractField(v)
}
Expand Down
Loading