diff --git a/.chloggen/sapmexporter-prioritize-token-in-context.yaml b/.chloggen/sapmexporter-prioritize-token-in-context.yaml new file mode 100644 index 000000000000..2814b123ab2b --- /dev/null +++ b/.chloggen/sapmexporter-prioritize-token-in-context.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: 'sapmexporter' + +# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`). +note: 'Prioritize token in context when accesstokenpassthrough is enabled' + +# Mandatory: One or more tracking issues related to the change. You can use the PR number here if no issue exists. +issues: [35123] + +# (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: + +# 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/exporter/sapmexporter/exporter.go b/exporter/sapmexporter/exporter.go index 763f3acdb147..9893d1c189a8 100644 --- a/exporter/sapmexporter/exporter.go +++ b/exporter/sapmexporter/exporter.go @@ -10,6 +10,7 @@ import ( "github.com/jaegertracing/jaeger/model" sapmclient "github.com/signalfx/sapm-proto/client" + "go.opentelemetry.io/collector/client" "go.opentelemetry.io/collector/component" "go.opentelemetry.io/collector/consumer" "go.opentelemetry.io/collector/consumer/consumererror" @@ -42,7 +43,6 @@ func (se *sapmExporter) Shutdown(context.Context) error { } func newSAPMExporter(cfg *Config, params exporter.Settings) (sapmExporter, error) { - client, err := sapmclient.New(cfg.clientOptions()...) if err != nil { return sapmExporter{}, err @@ -71,7 +71,6 @@ func newSAPMTracesExporter(cfg *Config, set exporter.Settings) (exporter.Traces, exporterhelper.WithRetry(cfg.BackOffConfig), exporterhelper.WithTimeout(cfg.TimeoutSettings), ) - if err != nil { return nil, err } @@ -95,8 +94,8 @@ func (se *sapmExporter) pushTraceData(ctx context.Context, td ptrace.Traces) err return nil } - // All metrics in the pmetric.Metrics will have the same access token because of the BatchPerResourceMetrics. - accessToken := se.retrieveAccessToken(rss.At(0)) + accessToken := se.retrieveAccessToken(ctx, rss.At(0)) + batches, err := jaeger.ProtoFromTraces(td) if err != nil { return consumererror.NewPermanent(err) @@ -126,12 +125,18 @@ func (se *sapmExporter) pushTraceData(ctx context.Context, td ptrace.Traces) err return nil } -func (se *sapmExporter) retrieveAccessToken(md ptrace.ResourceSpans) string { +func (se *sapmExporter) retrieveAccessToken(ctx context.Context, md ptrace.ResourceSpans) string { if !se.config.AccessTokenPassthrough { // Nothing to do if token is pass through not configured or resource is nil. return "" } + cl := client.FromContext(ctx) + ss := cl.Metadata.Get(splunk.SFxAccessTokenHeader) + if len(ss) > 0 { + return ss[0] + } + attrs := md.Resource().Attributes() if accessToken, ok := attrs.Get(splunk.SFxAccessTokenLabel); ok { return accessToken.Str() diff --git a/exporter/sapmexporter/exporter_test.go b/exporter/sapmexporter/exporter_test.go index 9830ab2ce1c9..e70c3b7835b3 100644 --- a/exporter/sapmexporter/exporter_test.go +++ b/exporter/sapmexporter/exporter_test.go @@ -18,6 +18,7 @@ import ( splunksapm "github.com/signalfx/sapm-proto/gen" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "go.opentelemetry.io/collector/client" "go.opentelemetry.io/collector/exporter/exportertest" "go.opentelemetry.io/collector/pdata/ptrace" @@ -215,6 +216,79 @@ func TestSAPMClientTokenUsageAndErrorMarshalling(t *testing.T) { } } +func TestSAPMClientTokenAccess(t *testing.T) { + tests := []struct { + name string + inContext bool + accessTokenPassthrough bool + }{ + { + name: "Token in context with passthrough", + inContext: true, + accessTokenPassthrough: true, + }, + { + name: "Token in attributes with passthrough", + inContext: false, + accessTokenPassthrough: true, + }, + { + name: "Token in config wihout passthrough", + inContext: false, + accessTokenPassthrough: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tracesReceived := false + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + expectedToken := "ClientAccessToken" + if tt.accessTokenPassthrough && tt.inContext { + expectedToken = "SplunkAccessToken" + } else if tt.accessTokenPassthrough && !tt.inContext { + expectedToken = "TraceAccessToken0" + } + assert.Contains(t, r.Header.Get("x-sf-token"), expectedToken) + status := 200 + w.WriteHeader(status) + tracesReceived = true + })) + defer func() { + assert.True(t, tracesReceived, "Test server never received traces.") + }() + defer server.Close() + + cfg := &Config{ + Endpoint: server.URL, + AccessToken: "ClientAccessToken", + AccessTokenPassthroughConfig: splunk.AccessTokenPassthroughConfig{ + AccessTokenPassthrough: tt.accessTokenPassthrough, + }, + } + params := exportertest.NewNopSettings() + + se, err := newSAPMExporter(cfg, params) + assert.NoError(t, err) + assert.NotNil(t, se, "failed to create trace exporter") + + trace, testTraceErr := buildTestTrace() + require.NoError(t, testTraceErr) + + ctx := context.Background() + if tt.inContext { + ctx = client.NewContext( + ctx, + client.Info{Metadata: client.NewMetadata( + map[string][]string{splunk.SFxAccessTokenHeader: {"SplunkAccessToken"}}, + )}, + ) + } + err = se.pushTraceData(ctx, trace) + require.NoError(t, err) + }) + } +} + func decompress(body io.Reader, compression string) ([]byte, error) { switch compression { case "": diff --git a/exporter/sapmexporter/go.mod b/exporter/sapmexporter/go.mod index bac86da830a6..87fe05287b6a 100644 --- a/exporter/sapmexporter/go.mod +++ b/exporter/sapmexporter/go.mod @@ -11,6 +11,7 @@ require ( github.com/open-telemetry/opentelemetry-collector-contrib/pkg/translator/jaeger v0.109.0 github.com/signalfx/sapm-proto v0.14.0 github.com/stretchr/testify v1.9.0 + go.opentelemetry.io/collector/client v1.15.0 go.opentelemetry.io/collector/component v0.109.0 go.opentelemetry.io/collector/config/configopaque v1.15.0 go.opentelemetry.io/collector/config/configretry v1.15.0 diff --git a/exporter/sapmexporter/go.sum b/exporter/sapmexporter/go.sum index 75ffc3ffaa05..0d3c925a8199 100644 --- a/exporter/sapmexporter/go.sum +++ b/exporter/sapmexporter/go.sum @@ -120,6 +120,8 @@ go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= go.opentelemetry.io/collector v0.109.0 h1:ULnMWuwcy4ix1oP5RFFRcmpEbaU5YabW6nWcLMQQRo0= go.opentelemetry.io/collector v0.109.0/go.mod h1:gheyquSOc5E9Y+xsPmpA+PBrpPc+msVsIalY76/ZvnQ= +go.opentelemetry.io/collector/client v1.15.0 h1:SMUKTntljRmFvB8nCVf6KjbEQ/qm63wi+huDx+Bc/po= +go.opentelemetry.io/collector/client v1.15.0/go.mod h1:m0MdKbzRIVgyGu70qbJ6TwBmKtblk7cmPqspM45a5yY= go.opentelemetry.io/collector/component v0.109.0 h1:AU6eubP1htO8Fvm86uWn66Kw0DMSFhgcRM2cZZTYfII= go.opentelemetry.io/collector/component v0.109.0/go.mod h1:jRVFY86GY6JZ61SXvUN69n7CZoTjDTqWyNC+wJJvzOw= go.opentelemetry.io/collector/config/configopaque v1.15.0 h1:J1rmPR1WGro7BNCgni3o+VDoyB7ZqH2/SG1YK+6ujCw=