Skip to content
Open
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
8 changes: 6 additions & 2 deletions enrichments/enricher.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,13 @@
package enrichments

import (
"github.com/elastic/opentelemetry-lib/enrichments/config"
"github.com/elastic/opentelemetry-lib/enrichments/internal/elastic"
"github.com/ua-parser/uap-go/uaparser"
"go.opentelemetry.io/collector/pdata/plog"
"go.opentelemetry.io/collector/pdata/pmetric"
"go.opentelemetry.io/collector/pdata/ptrace"

"github.com/elastic/opentelemetry-lib/enrichments/config"
"github.com/elastic/opentelemetry-lib/enrichments/internal/elastic"
)

// Enricher enriches the OTel traces with attributes required to power
Expand All @@ -40,6 +41,7 @@ type Enricher struct {
// functionalities in the Elastic UI. The traces are processed as per the
// Elastic's definition of transactions and spans. The traces passed to
// this function are mutated.
// Any existing attributes will not be enriched or modified.
func (e *Enricher) EnrichTraces(pt ptrace.Traces) {
resSpans := pt.ResourceSpans()
for i := 0; i < resSpans.Len(); i++ {
Expand All @@ -59,6 +61,7 @@ func (e *Enricher) EnrichTraces(pt ptrace.Traces) {

// EnrichLogs enriches the OTel logs with attributes required to power
// functionalities in the Elastic UI. The logs passed to this function are mutated.
// Any existing attributes will not be enriched or modified.
func (e *Enricher) EnrichLogs(pl plog.Logs) {
resLogs := pl.ResourceLogs()
for i := 0; i < resLogs.Len(); i++ {
Expand All @@ -80,6 +83,7 @@ func (e *Enricher) EnrichLogs(pl plog.Logs) {

// EnrichMetrics enriches the OTel metrics with attributes required to power
// functionalities in the Elastic UI. The metrics passed to this function are mutated.
// Any existing attributes will not be enriched or modified.
func (e *Enricher) EnrichMetrics(pl pmetric.Metrics) {
resMetrics := pl.ResourceMetrics()
for i := 0; i < resMetrics.Len(); i++ {
Expand Down
42 changes: 42 additions & 0 deletions enrichments/internal/attribute/attribute.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// Licensed to Elasticsearch B.V. under one or more contributor
// license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright
// ownership. Elasticsearch B.V. licenses this file to you under
// the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.

package attribute

import (
"go.opentelemetry.io/collector/pdata/pcommon"
)

// IsEmpty returns true if the attribute does not exist or is empty.
// For string attributes, returns true if the attribute does not exist or is empty.
// For slice attributes, returns true if the attribute does not exist or has length 0.
// For other types, returns true if the attribute does not exist.
func IsEmpty(attrs pcommon.Map, key string) bool {
value, exists := attrs.Get(key)
if !exists {
return true
}

switch value.Type() {
case pcommon.ValueTypeStr:
return value.Str() == ""
case pcommon.ValueTypeSlice:
return value.Slice().Len() == 0
default:
return false
}
}
139 changes: 139 additions & 0 deletions enrichments/internal/attribute/attribute_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
// Licensed to Elasticsearch B.V. under one or more contributor
// license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright
// ownership. Elasticsearch B.V. licenses this file to you under
// the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.

package attribute

import (
"testing"

"go.opentelemetry.io/collector/pdata/pcommon"
)

func TestIsEmpty(t *testing.T) {
tests := []struct {
name string
setup func(pcommon.Map)
key string
expected bool
}{
{
name: "attribute does not exist",
setup: func(attrs pcommon.Map) {},
key: "nonexistent",
expected: true,
},
{
name: "string attribute is empty",
setup: func(attrs pcommon.Map) {
attrs.PutStr("key", "")
},
key: "key",
expected: true,
},
{
name: "string attribute is not empty",
setup: func(attrs pcommon.Map) {
attrs.PutStr("key", "value")
},
key: "key",
expected: false,
},
{
name: "int attribute exists",
setup: func(attrs pcommon.Map) {
attrs.PutInt("key", 42)
},
key: "key",
expected: false,
},
{
name: "int attribute with zero value exists",
setup: func(attrs pcommon.Map) {
attrs.PutInt("key", 0)
},
key: "key",
expected: false,
},
{
name: "bool attribute exists",
setup: func(attrs pcommon.Map) {
attrs.PutBool("key", true)
},
key: "key",
expected: false,
},
{
name: "bool attribute with false value exists",
setup: func(attrs pcommon.Map) {
attrs.PutBool("key", false)
},
key: "key",
expected: false,
},
{
name: "double attribute exists",
setup: func(attrs pcommon.Map) {
attrs.PutDouble("key", 3.14)
},
key: "key",
expected: false,
},
{
name: "double attribute with zero value exists",
setup: func(attrs pcommon.Map) {
attrs.PutDouble("key", 0.0)
},
key: "key",
expected: false,
},
{
name: "slice attribute does not exist",
setup: func(attrs pcommon.Map) {},
key: "key",
expected: true,
},
{
name: "slice attribute is empty",
setup: func(attrs pcommon.Map) {
attrs.PutEmptySlice("key")
},
key: "key",
expected: true,
},
{
name: "slice attribute is not empty",
setup: func(attrs pcommon.Map) {
slice := attrs.PutEmptySlice("key")
slice.AppendEmpty().SetStr("value1")
slice.AppendEmpty().SetStr("value2")
},
key: "key",
expected: false,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
attrs := pcommon.NewMap()
tt.setup(attrs)
result := IsEmpty(attrs, tt.key)
if result != tt.expected {
t.Errorf("IsEmpty() = %v, expected %v", result, tt.expected)
}
})
}
}
7 changes: 3 additions & 4 deletions enrichments/internal/elastic/log.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,14 @@ package elastic
import (
"github.com/elastic/opentelemetry-lib/elasticattr"
"github.com/elastic/opentelemetry-lib/enrichments/config"
"github.com/elastic/opentelemetry-lib/enrichments/internal/attribute"
"github.com/elastic/opentelemetry-lib/enrichments/internal/elastic/mobile"
"go.opentelemetry.io/collector/pdata/plog"
)

func EnrichLog(resourceAttrs map[string]any, log plog.LogRecord, cfg config.Config) {
if cfg.Log.ProcessorEvent.Enabled {
if _, exists := log.Attributes().Get(elasticattr.ProcessorEvent); !exists {
log.Attributes().PutStr(elasticattr.ProcessorEvent, "log")
}
if cfg.Log.ProcessorEvent.Enabled && attribute.IsEmpty(log.Attributes(), elasticattr.ProcessorEvent) {
log.Attributes().PutStr(elasticattr.ProcessorEvent, "log")
}
eventName, ok := getEventName(log)
if ok {
Expand Down
7 changes: 3 additions & 4 deletions enrichments/internal/elastic/metric.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,12 @@ package elastic
import (
"github.com/elastic/opentelemetry-lib/elasticattr"
"github.com/elastic/opentelemetry-lib/enrichments/config"
"github.com/elastic/opentelemetry-lib/enrichments/internal/attribute"
"go.opentelemetry.io/collector/pdata/pmetric"
)

func EnrichMetric(metric pmetric.ResourceMetrics, cfg config.Config) {
if cfg.Metric.ProcessorEvent.Enabled {
if _, exists := metric.Resource().Attributes().Get(elasticattr.ProcessorEvent); !exists {
metric.Resource().Attributes().PutStr(elasticattr.ProcessorEvent, "metric")
}
if cfg.Metric.ProcessorEvent.Enabled && attribute.IsEmpty(metric.Resource().Attributes(), elasticattr.ProcessorEvent) {
metric.Resource().Attributes().PutStr(elasticattr.ProcessorEvent, "metric")
}
}
93 changes: 93 additions & 0 deletions enrichments/internal/elastic/metric_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
// Licensed to Elasticsearch B.V. under one or more contributor
// license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright
// ownership. Elasticsearch B.V. licenses this file to you under
// the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.

package elastic

import (
"testing"

"github.com/stretchr/testify/assert"
"go.opentelemetry.io/collector/pdata/pmetric"

"github.com/elastic/opentelemetry-lib/elasticattr"
"github.com/elastic/opentelemetry-lib/enrichments/config"
)

func TestEnrichMetric(t *testing.T) {
getMetric := func() pmetric.ResourceMetrics {
metrics := pmetric.NewMetrics()
resourceMetrics := metrics.ResourceMetrics().AppendEmpty()

// Add a metric data point to make it a valid metric
scopeMetrics := resourceMetrics.ScopeMetrics().AppendEmpty()
metric := scopeMetrics.Metrics().AppendEmpty()
metric.SetName("test.metric")
metric.SetUnit("1")
metric.SetEmptyGauge()
metric.Gauge().DataPoints().AppendEmpty().SetDoubleValue(1.0)

return resourceMetrics
}

for _, tc := range []struct {
name string
input pmetric.ResourceMetrics
config config.Config
expectedAttrs map[string]any
}{
{
name: "existing_attributes_not_overridden",
input: func() pmetric.ResourceMetrics {
resourceMetrics := getMetric()
resource := resourceMetrics.Resource()

// Set existing attributes that should not be overridden
resource.Attributes().PutStr(elasticattr.ProcessorEvent, "existing-processor-event")
resource.Attributes().PutStr(elasticattr.AgentName, "existing-agent-name")
resource.Attributes().PutStr(elasticattr.AgentVersion, "existing-agent-version")

return resourceMetrics
}(),
config: config.Enabled(),
expectedAttrs: map[string]any{
elasticattr.ProcessorEvent: "existing-processor-event",
elasticattr.AgentName: "existing-agent-name",
elasticattr.AgentVersion: "existing-agent-version",
},
},
} {
t.Run(tc.name, func(t *testing.T) {
expectedResourceMetrics := pmetric.NewResourceMetrics()
tc.input.Resource().CopyTo(expectedResourceMetrics.Resource())

// Merge with the expected attributes
for k, v := range tc.expectedAttrs {
expectedResourceMetrics.Resource().Attributes().PutEmpty(k).FromRaw(v)
}

// Enrich the metric
EnrichMetric(tc.input, tc.config)
EnrichResource(tc.input.Resource(), tc.config.Resource)

// Verify attributes match expected
actualAttrs := tc.input.Resource().Attributes().AsRaw()
expectedAttrs := expectedResourceMetrics.Resource().Attributes().AsRaw()

assert.Equal(t, expectedAttrs, actualAttrs, "resource attributes should match expected")
})
}
}
Loading