diff --git a/.chloggen/k8sobjects-extract-attributes.yaml b/.chloggen/k8sobjects-extract-attributes.yaml new file mode 100755 index 000000000000..4fe06dcd738e --- /dev/null +++ b/.chloggen/k8sobjects-extract-attributes.yaml @@ -0,0 +1,27 @@ +# Use this changelog template to create an entry for release notes. + +# One of 'breaking', 'deprecation', 'new_component', 'enhancement', 'bug_fix' +change_type: enhancement + +# The name of the component, or a single word describing the area of concern, (e.g. filelogreceiver) +component: receiver/k8sobjects + +# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`). +note: Add automatic resource attribute extraction for k8s events + +# Mandatory: One or more tracking issues related to the change. You can use the PR number here if no issue exists. +issues: [27069] + +# (Optional) One or more lines of additional information to render under the primary note. +# These lines will be padded with 2 spaces and then inserted directly into the document. +# Use pipe (|) for multiline entries. +subtext: For k8s events, extracts the k8s.namespace.name, k8s.{kind}.name, and k8s.{kind}.uid of the object associated to the event + +# If your change doesn't affect end users or the exported elements of any package, +# you should instead start your pull request title with [chore] or use the "Skip Changelog" label. +# Optional: The change log or logs in which this entry should be included. +# e.g. '[user]' or '[user, api]' +# Include 'user' if the change is relevant to end users. +# Include 'api' if there is a change to a library API. +# Default: '[user]' +change_logs: [] diff --git a/receiver/k8sobjectsreceiver/unstructured_to_logdata.go b/receiver/k8sobjectsreceiver/unstructured_to_logdata.go index 589b0547b369..7675b16d7994 100644 --- a/receiver/k8sobjectsreceiver/unstructured_to_logdata.go +++ b/receiver/k8sobjectsreceiver/unstructured_to_logdata.go @@ -5,6 +5,7 @@ package k8sobjectsreceiver // import "github.com/open-telemetry/opentelemetry-co import ( "fmt" + "strings" "time" "go.opentelemetry.io/collector/pdata/pcommon" @@ -58,6 +59,15 @@ func unstructuredListToLogData(event *unstructured.UnstructuredList, observedAt if namespace := e.GetNamespace(); namespace != "" { resourceAttrs.PutStr(semconv.AttributeK8SNamespaceName, namespace) } + + if config.gvr.Resource == "events" { + if config.gvr.Group == "events.k8s.io" { + extractResourceAttributesFromEvent(resourceAttrs, e.Object, "regarding") + } else { + extractResourceAttributesFromEvent(resourceAttrs, e.Object, "involvedObject") + } + } + sl := rl.ScopeLogs().AppendEmpty() logSlice = sl.LogRecords() namespaceResourceMap[e.GetNamespace()] = logSlice @@ -79,3 +89,25 @@ func unstructuredListToLogData(event *unstructured.UnstructuredList, observedAt } return out } + +func extractResourceAttributesFromEvent(resourceAttrs pcommon.Map, object map[string]any, location string) { + objKind, ok, _ := unstructured.NestedString(object, "object", "kind") + if ok && objKind == "Event" { + namespace, ok, _ := unstructured.NestedString(object, "object", location, "namespace") + if ok { + resourceAttrs.PutStr(semconv.AttributeK8SNamespaceName, namespace) + } + kind, ok, _ := unstructured.NestedString(object, "object", location, "kind") + if ok { + kind = strings.ToLower(kind) + regardingName, ok, _ := unstructured.NestedString(object, "object", location, "name") + if ok { + resourceAttrs.PutStr(fmt.Sprintf("k8s.%v.name", kind), regardingName) + } + regardingUID, ok, _ := unstructured.NestedString(object, "object", location, "uid") + if ok { + resourceAttrs.PutStr(fmt.Sprintf("k8s.%v.uid", kind), regardingUID) + } + } + } +} diff --git a/receiver/k8sobjectsreceiver/unstructured_to_logdata_test.go b/receiver/k8sobjectsreceiver/unstructured_to_logdata_test.go index d25c8116bcd8..ee2f09cee169 100644 --- a/receiver/k8sobjectsreceiver/unstructured_to_logdata_test.go +++ b/receiver/k8sobjectsreceiver/unstructured_to_logdata_test.go @@ -169,4 +169,85 @@ func TestUnstructuredListToLogData(t *testing.T) { assert.Equal(t, logRecords.At(0).ObservedTimestamp().AsTime().Unix(), observedAt.Unix()) }) + t.Run("Test object with regarding", func(t *testing.T) { + objects := unstructured.UnstructuredList{ + Items: []unstructured.Unstructured{}, + } + object := unstructured.Unstructured{} + object.Object = map[string]any{ + "object": map[string]any{ + "kind": "Event", + "regarding": map[string]any{ + "kind": "Pod", + "name": "my-pod-name", + "uid": "12345", + "namespace": "my-namespace", + }, + }, + } + objects.Items = append(objects.Items, object) + config := &K8sObjectsConfig{ + gvr: &schema.GroupVersionResource{ + Group: "events.k8s.io", + Version: "v1", + Resource: "events", + }, + } + logs := pullObjectsToLogData(&objects, time.Now(), config) + + assert.Equal(t, logs.LogRecordCount(), 1) + + resourceLogs := logs.ResourceLogs() + assert.Equal(t, resourceLogs.Len(), 1) + + rl := resourceLogs.At(0) + resourceAttributes := rl.Resource().Attributes() + podName, _ := resourceAttributes.Get(semconv.AttributeK8SPodName) + assert.Equal(t, "my-pod-name", podName.AsString()) + podUID, _ := resourceAttributes.Get(semconv.AttributeK8SPodUID) + assert.Equal(t, "12345", podUID.AsString()) + podNamespace, _ := resourceAttributes.Get(semconv.AttributeK8SNamespaceName) + assert.Equal(t, "my-namespace", podNamespace.AsString()) + }) + + t.Run("Test object with involvedObject", func(t *testing.T) { + objects := unstructured.UnstructuredList{ + Items: []unstructured.Unstructured{}, + } + object := unstructured.Unstructured{} + object.Object = map[string]any{ + "object": map[string]any{ + "kind": "Event", + "involvedObject": map[string]any{ + "kind": "Pod", + "name": "my-pod-name", + "uid": "12345", + "namespace": "my-namespace", + }, + }, + } + objects.Items = append(objects.Items, object) + config := &K8sObjectsConfig{ + gvr: &schema.GroupVersionResource{ + Group: "", + Version: "v1", + Resource: "events", + }, + } + logs := pullObjectsToLogData(&objects, time.Now(), config) + + assert.Equal(t, logs.LogRecordCount(), 1) + + resourceLogs := logs.ResourceLogs() + assert.Equal(t, resourceLogs.Len(), 1) + + rl := resourceLogs.At(0) + resourceAttributes := rl.Resource().Attributes() + podName, _ := resourceAttributes.Get(semconv.AttributeK8SPodName) + assert.Equal(t, "my-pod-name", podName.AsString()) + podUID, _ := resourceAttributes.Get(semconv.AttributeK8SPodUID) + assert.Equal(t, "12345", podUID.AsString()) + podNamespace, _ := resourceAttributes.Get(semconv.AttributeK8SNamespaceName) + assert.Equal(t, "my-namespace", podNamespace.AsString()) + }) }