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

feat: Add WithResourceOption for additional resource configuration #48

Merged
merged 8 commits into from
Jul 27, 2023
11 changes: 10 additions & 1 deletion examples/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,21 @@ import (
"log"

"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/sdk/resource"

"github.com/honeycombio/otel-config-go/otelconfig"
)

func main() {
otelShutdown, err := otelconfig.ConfigureOpenTelemetry()
otelShutdown, err := otelconfig.ConfigureOpenTelemetry(
otelconfig.WithResourceOption(
resource.WithAttributes(
attribute.String("resource.example_set_in_code", "CODE"),
attribute.String("resource.example_clobber", "CODE_WON"),
),
),
)

if err != nil {
log.Fatalf("error setting up OTel SDK - %e", err)
Expand Down
24 changes: 21 additions & 3 deletions otelconfig/otelconfig.go
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,14 @@ func WithResourceAttributes(attributes map[string]string) Option {
}
}

// WithResourceOption configures options on the resource; These are appended
// after the default options and can override them.
func WithResourceOption(option resource.Option) Option {
return func(c *Config) {
c.ResourceOptions = append(c.ResourceOptions, option)
}
}

// WithPropagators configures propagators.
func WithPropagators(propagators []string) Option {
return func(c *Config) {
Expand Down Expand Up @@ -316,6 +324,7 @@ type Config struct {
ResourceAttributes map[string]string
SpanProcessors []trace.SpanProcessor
Sampler trace.Sampler
ResourceOptions []resource.Option
Resource *resource.Resource
Logger Logger `json:"-"`
ShutdownFunctions []func(c *Config) error `json:"-"`
Expand Down Expand Up @@ -411,13 +420,22 @@ func newResource(c *Config) *resource.Resource {

attributes = append(r.Attributes(), attributes...)

// These detectors can't actually fail, ignoring the error.
r, _ = resource.New(
context.Background(),
baseOptions := []resource.Option{
resource.WithSchemaURL(semconv.SchemaURL),
resource.WithAttributes(attributes...),
}

options := append(baseOptions, c.ResourceOptions...)

r, err := resource.New(
context.Background(),
options...,
)

if err != nil {
c.Logger.Debugf("error applying resource options: %s", err)
}

// Note: There are new detectors we may wish to take advantage
// of, now available in the default SDK (e.g., WithProcess(),
// WithOSType(), ...).
Expand Down
93 changes: 77 additions & 16 deletions otelconfig/otelconfig_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package otelconfig
import (
"context"
"encoding/json"
"errors"
"fmt"
"net"
"net/http"
Expand Down Expand Up @@ -334,21 +335,23 @@ func TestEnvironmentVariables(t *testing.T) {
setEnvironment()
logger := &testLogger{}
handler := &testErrorHandler{}
config := newConfig(
testConfig := newConfig(
WithLogger(logger),
WithErrorHandler(handler),
)

attributes := []attribute.KeyValue{
expectedConfiguredResource := resource.NewWithAttributes(
semconv.SchemaURL,
attribute.String("host.name", host()),
attribute.String("resource.clobber", "ENV_WON"),
attribute.String("service.name", "test-service-name"),
attribute.String("service.version", "test-service-version"),
attribute.String("telemetry.sdk.name", "otelconfig"),
attribute.String("telemetry.sdk.language", "go"),
attribute.String("telemetry.sdk.version", version),
}
)

expected := &Config{
expectedConfig := &Config{
ExporterEndpoint: "http://generic-url",
ExporterEndpointInsecure: true,
TracesExporterEndpoint: "http://traces-url",
Expand All @@ -365,23 +368,32 @@ func TestEnvironmentVariables(t *testing.T) {
TracesHeaders: map[string]string{},
MetricsHeaders: map[string]string{},
ResourceAttributes: map[string]string{},
ResourceAttributesFromEnv: "service.name=test-service-name-b",
ResourceAttributesFromEnv: "service.name=test-service-name-b,resource.clobber=ENV_WON",
Propagators: []string{"b3", "w3c"},
Resource: resource.NewWithAttributes(semconv.SchemaURL, attributes...),
Resource: expectedConfiguredResource,
Logger: logger,
ExporterProtocol: "grpc",
errorHandler: handler,
Sampler: trace.AlwaysSample(),
}
assert.Equal(t, expected, config)
assert.Equal(t, expectedConfig, testConfig)
unsetEnvironment()
}

type testDetector struct{}

var _ resource.Detector = (*testDetector)(nil)

// Detect implements resource.Detector.
func (testDetector) Detect(ctx context.Context) (*resource.Resource, error) {
return resource.New(ctx)
}

func TestConfigurationOverrides(t *testing.T) {
setEnvironment()
logger := &testLogger{}
handler := &testErrorHandler{}
config := newConfig(
testConfig := newConfig(
WithServiceName("override-service-name"),
WithServiceVersion("override-service-version"),
WithExporterEndpoint("https://override-generic-url"),
Expand All @@ -397,18 +409,25 @@ func TestConfigurationOverrides(t *testing.T) {
WithExporterProtocol("http/protobuf"),
WithMetricsExporterProtocol("http/protobuf"),
WithTracesExporterProtocol("http/protobuf"),
WithResourceOption(resource.WithAttributes(
attribute.String("host.name", "hardcoded-hostname"),
attribute.String("resource.clobber", "CODE_WON"),
)),
WithResourceOption(resource.WithDetectors(&testDetector{})),
)

attributes := []attribute.KeyValue{
attribute.String("host.name", host()),
expectedConfiguredResource := resource.NewWithAttributes(
semconv.SchemaURL,
attribute.String("host.name", "hardcoded-hostname"),
attribute.String("resource.clobber", "CODE_WON"),
attribute.String("service.name", "override-service-name"),
attribute.String("service.version", "override-service-version"),
attribute.String("telemetry.sdk.name", "otelconfig"),
attribute.String("telemetry.sdk.language", "go"),
attribute.String("telemetry.sdk.version", version),
}
)

expected := &Config{
expectedConfig := &Config{
ServiceName: "override-service-name",
ServiceVersion: "override-service-version",
ExporterEndpoint: "https://override-generic-url",
Expand All @@ -424,17 +443,24 @@ func TestConfigurationOverrides(t *testing.T) {
TracesHeaders: map[string]string{},
MetricsHeaders: map[string]string{},
ResourceAttributes: map[string]string{},
ResourceAttributesFromEnv: "service.name=test-service-name-b",
ResourceAttributesFromEnv: "service.name=test-service-name-b,resource.clobber=ENV_WON",
Propagators: []string{"b3"},
Resource: resource.NewWithAttributes(semconv.SchemaURL, attributes...),
Resource: expectedConfiguredResource,
Logger: logger,
ExporterProtocol: "http/protobuf",
TracesExporterProtocol: "http/protobuf",
MetricsExporterProtocol: "http/protobuf",
errorHandler: handler,
Sampler: trace.AlwaysSample(),
ResourceOptions: []resource.Option{
resource.WithAttributes(
attribute.String("host.name", "hardcoded-hostname"),
attribute.String("resource.clobber", "CODE_WON"),
),
resource.WithDetectors(&testDetector{}),
},
}
assert.Equal(t, expected, config)
assert.Equal(t, expectedConfig, testConfig)
unsetEnvironment()
}

Expand Down Expand Up @@ -663,6 +689,41 @@ func TestConfigWithResourceAttributes(t *testing.T) {
defer shutdown()
}

func TestConfigWithResourceAttributesError(t *testing.T) {
stopper := dummyGRPCListener()
defer stopper()

logger := &testLogger{}
faultyResourceDetector := resource.StringDetector("", "", func() (string, error) {
return "", errors.New("faulty resource detector")
})

shutdown, _ := ConfigureOpenTelemetry(
WithLogger(logger),
WithResourceAttributes(map[string]string{
"attr1": "val1",
"attr2": "val2",
}),
WithResourceOption(resource.WithDetectors(faultyResourceDetector)),
WithShutdown(func(c *Config) error {
attrs := attribute.NewSet(c.Resource.Attributes()...)
v, ok := attrs.Value("attr1")
assert.Equal(t, "val1", v.AsString())
assert.True(t, ok)

v, ok = attrs.Value("attr2")
assert.Equal(t, "val2", v.AsString())
assert.True(t, ok)

logger.requireContains(t, "faulty resource detector")

return nil
}),
withTestExporters(),
)
defer shutdown()
}

func TestThatEndpointsFallBackCorrectly(t *testing.T) {
unsetEnvironment()
testCases := []struct {
Expand Down Expand Up @@ -918,7 +979,7 @@ func setEnvironment() {
setenv("OTEL_METRICS_ENABLED", "false")
setenv("OTEL_LOG_LEVEL", "debug")
setenv("OTEL_PROPAGATORS", "b3,w3c")
setenv("OTEL_RESOURCE_ATTRIBUTES", "service.name=test-service-name-b")
setenv("OTEL_RESOURCE_ATTRIBUTES", "service.name=test-service-name-b,resource.clobber=ENV_WON")
setenv("OTEL_EXPORTER_OTLP_PROTOCOL", "grpc")
}

Expand Down
1 change: 1 addition & 0 deletions smoke-tests/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ version: '3.0'
x-env-base: &env_base
OTEL_EXPORTER_OTLP_ENDPOINT: http://collector:4317
OTEL_EXPORTER_OTLP_INSECURE: "true"
OTEL_RESOURCE_ATTRIBUTES: resource.example_set_in_env=ENV,resource.example_clobber=ENV_WON
OTEL_SERVICE_NAME: "my-go-app"
OTEL_METRICS_ENABLED: "false"
DEBUG: "true"
Expand Down
15 changes: 15 additions & 0 deletions smoke-tests/smoke-sdk-grpc.bats
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,18 @@ teardown_file() {
result=$(span_names_for ${TRACER_NAME})
assert_equal "$result" '"doing-things"'
}

@test "Resource attributes can be set via environment variable" {
env_result=$(spans_received | jq ".resource.attributes[] | select(.key == \"resource.example_set_in_env\") | .value.stringValue")
assert_equal "$env_result" '"ENV"'
}

@test "Resource attributes can be set in code" {
code_result=$(spans_received | jq ".resource.attributes[] | select(.key == \"resource.example_set_in_code\") | .value.stringValue")
assert_equal "$code_result" '"CODE"'
}

@test "Resource attributes set in code win over matching key set in environment" {
clobber_result=$(spans_received | jq ".resource.attributes[] | select(.key == \"resource.example_clobber\") | .value.stringValue")
assert_equal "$clobber_result" '"CODE_WON"'
}
15 changes: 15 additions & 0 deletions smoke-tests/smoke-sdk-http.bats
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,18 @@ teardown_file() {
result=$(span_names_for ${TRACER_NAME})
assert_equal "$result" '"doing-things"'
}

@test "Resource attributes can be set via environment variable" {
env_result=$(spans_received | jq ".resource.attributes[] | select(.key == \"resource.example_set_in_env\") | .value.stringValue")
assert_equal "$env_result" '"ENV"'
}

@test "Resource attributes can be set in code" {
code_result=$(spans_received | jq ".resource.attributes[] | select(.key == \"resource.example_set_in_code\") | .value.stringValue")
assert_equal "$code_result" '"CODE"'
}

@test "Resource attributes set in code win over matching key set in environment" {
clobber_result=$(spans_received | jq ".resource.attributes[] | select(.key == \"resource.example_clobber\") | .value.stringValue")
assert_equal "$clobber_result" '"CODE_WON"'
}