Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add WithResourceAttributes option #522

Merged
merged 11 commits into from
Nov 23, 2023
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ OpenTelemetry Go Automatic Instrumentation adheres to [Semantic Versioning](http

## [Unreleased]

### Added

- Add `WithResourceAttributes` `InstrumentationOption` to configure `Instrumentation` to add additional resource attributes. ([#522](https://github.com/open-telemetry/opentelemetry-go-instrumentation/pull/522))

### Changed

- The instrumentation scope name for the `database/sql` instrumentation is now `go.opentelemtry.io/auto/database/sql`. (#507)
Expand Down
57 changes: 43 additions & 14 deletions instrumentation.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,14 @@ import (
"github.com/go-logr/logr"
"github.com/go-logr/stdr"
"github.com/go-logr/zapr"
"go.uber.org/zap"

"go.opentelemetry.io/contrib/exporters/autoexport"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp"
"go.opentelemetry.io/otel/sdk/resource"
"go.opentelemetry.io/otel/sdk/trace"
semconv "go.opentelemetry.io/otel/semconv/v1.21.0"
"go.uber.org/zap"

"go.opentelemetry.io/auto/internal/pkg/instrumentation"
"go.opentelemetry.io/auto/internal/pkg/opentelemetry"
Expand Down Expand Up @@ -163,10 +165,11 @@ type InstrumentationOption interface {
}

type instConfig struct {
sampler trace.Sampler
traceExp trace.SpanExporter
target process.TargetArgs
serviceName string
sampler trace.Sampler
traceExp trace.SpanExporter
target process.TargetArgs
serviceName string
additionalResAttrs []attribute.KeyValue
}

func newInstConfig(ctx context.Context, opts []InstrumentationOption) (instConfig, error) {
Expand Down Expand Up @@ -239,14 +242,22 @@ func (c instConfig) res() *resource.Resource {
runVer, runtime.GOOS, runtime.GOARCH,
)

return resource.NewWithAttributes(
semconv.SchemaURL,
attrs := []attribute.KeyValue{
semconv.ServiceNameKey.String(c.serviceName),
semconv.TelemetrySDKLanguageGo,
semconv.TelemetryAutoVersionKey.String(Version()),
semconv.ProcessRuntimeName(runName),
semconv.ProcessRuntimeVersion(runVer),
semconv.ProcessRuntimeDescription(runDesc),
}

if len(c.additionalResAttrs) > 0 {
attrs = append(attrs, c.additionalResAttrs...)
}

return resource.NewWithAttributes(
semconv.SchemaURL,
attrs...,
)
}

Expand Down Expand Up @@ -342,36 +353,45 @@ func WithEnv() InstrumentationOption {
c.traceExp, e = autoexport.NewSpanExporter(ctx)
err = errors.Join(err, e)
}
if v, ok := lookupServiceName(); ok {
c.serviceName = v
if name, attrs, ok := lookupResourceData(); ok {
c.serviceName = name
c.additionalResAttrs = append(c.additionalResAttrs, attrs...)
}
return c, err
})
}

func lookupServiceName() (string, bool) {
func lookupResourceData() (string, []attribute.KeyValue, bool) {
// Prioritize OTEL_SERVICE_NAME over OTEL_RESOURCE_ATTRIBUTES value.
svcName := ""
if v, ok := lookupEnv(envServiceNameKey); ok {
return v, ok
svcName = v
}

v, ok := lookupEnv(envResourceAttrKey)
if !ok {
return "", false
return svcName, nil, svcName != ""
}

var attrs []attribute.KeyValue
for _, keyval := range strings.Split(strings.TrimSpace(v), ",") {
key, val, found := strings.Cut(keyval, "=")
if !found {
continue
}
key = strings.TrimSpace(key)
if key == string(semconv.ServiceNameKey) {
return strings.TrimSpace(val), true
svcName = strings.TrimSpace(val)
} else {
attrs = append(attrs, attribute.String(key, strings.TrimSpace(val)))
}
}

return "", false
if svcName == "" {
return "", nil, false
}

return svcName, attrs, true
}

// WithTraceExporter returns an [InstrumentationOption] that will configure an
Expand All @@ -396,3 +416,12 @@ func WithSampler(sampler trace.Sampler) InstrumentationOption {
return c, nil
})
}

// WithResourceAttributes returns an [InstrumentationOption] that will configure
// an [Instrumentation] to add the provided attributes to the OpenTelemetry resource.
func WithResourceAttributes(attrs ...attribute.KeyValue) InstrumentationOption {
return fnOpt(func(_ context.Context, c instConfig) (instConfig, error) {
c.additionalResAttrs = append(c.additionalResAttrs, attrs...)
return c, nil
})
}
45 changes: 45 additions & 0 deletions instrumentation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ import (

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

"go.opentelemetry.io/otel/attribute"
semconv "go.opentelemetry.io/otel/semconv/v1.21.0"
)

Expand Down Expand Up @@ -127,6 +129,49 @@ func TestOptionPrecedence(t *testing.T) {
})
}

func TestWithResourceAttributes(t *testing.T) {
t.Run("By Code", func(t *testing.T) {
attr1 := semconv.K8SContainerName("test_container_name")
attr2 := semconv.K8SPodName("test_pod_name")
attr3 := semconv.K8SNamespaceName("test_namespace_name")

c, err := newInstConfig(context.Background(), []InstrumentationOption{WithResourceAttributes(attr1, attr2), WithResourceAttributes(attr3)})
require.NoError(t, err)
assert.Equal(t, []attribute.KeyValue{attr1, attr2, attr3}, c.additionalResAttrs)
})

t.Run("By Env", func(t *testing.T) {
nameAttr := semconv.ServiceName("test_service")
attr2 := semconv.K8SPodName("test_pod_name")
attr3 := semconv.K8SNamespaceName("test_namespace_name")

mockEnv(t, map[string]string{
"OTEL_RESOURCE_ATTRIBUTES": fmt.Sprintf("%s=%s,%s=%s,%s=%s", nameAttr.Key, nameAttr.Value.AsString(), attr2.Key, attr2.Value.AsString(), attr3.Key, attr3.Value.AsString()),
})

c, err := newInstConfig(context.Background(), []InstrumentationOption{WithEnv()})
require.NoError(t, err)
assert.Equal(t, nameAttr.Value.AsString(), c.serviceName)
assert.Equal(t, []attribute.KeyValue{attr2, attr3}, c.additionalResAttrs)
})

t.Run("By Code and Env", func(t *testing.T) {
nameAttr := semconv.ServiceName("test_service")
attr2 := semconv.K8SPodName("test_pod_name")
attr3 := semconv.K8SNamespaceName("test_namespace_name")

mockEnv(t, map[string]string{
"OTEL_RESOURCE_ATTRIBUTES": fmt.Sprintf("%s=%s,%s=%s", nameAttr.Key, nameAttr.Value.AsString(), attr2.Key, attr2.Value.AsString()),
})

// Use WithResourceAttributes to config the additional resource attributes
c, err := newInstConfig(context.Background(), []InstrumentationOption{WithEnv(), WithResourceAttributes(attr3)})
require.NoError(t, err)
assert.Equal(t, nameAttr.Value.AsString(), c.serviceName)
assert.Equal(t, []attribute.KeyValue{attr2, attr3}, c.additionalResAttrs)
})
}

func mockEnv(t *testing.T, env map[string]string) {
orig := lookupEnv
t.Cleanup(func() { lookupEnv = orig })
Expand Down