Skip to content

Commit c7adcaf

Browse files
rmfitzpatrickwyTrivail
authored andcommitted
Add SAPM AccessTokenPassthrough (#349)
Adding the `access_token_passthrough` config option and functionality to SAPM receiver and exporter similar to that of the SignalFx metric receiver and exporter from f361e4d. These changes add a `com.splunk.signalfx.access_token` attribute with a value taken from `X-Sf-Token` headers, if provided, that will be used for subsequent trace submissions in the SAPM exporter by its client. **Testing:** Added and modified unit tests and e2e run configuration. **Documentation:** Updated relevant readmes.
1 parent a0ee87a commit c7adcaf

File tree

25 files changed

+562
-42
lines changed

25 files changed

+562
-42
lines changed

exporter/sapmexporter/README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,18 @@ idle HTTP connection the exporter can keep open.
1515
- `num_workers` (default = 8): NumWorkers is the number of workers that should be used to
1616
export traces. Exporter can make as many requests in parallel as the number of workers. Note
1717
that this will likely be removed in future in favour of processors handling parallel exporting.
18+
- `access_token_passthrough`: (default = `true`) Whether to use `"com.splunk.signalfx.access_token"`
19+
trace resource attribute, if any, as SFx access token. In either case this attribute will be deleted
20+
during final translation. Intended to be used in tandem with identical configuration option for
21+
[SAPM receiver](../../receiver/sapmreceiver/README.md) to preserve trace origin.
1822

1923
Example:
2024

2125
```yaml
2226
exporters:
2327
sapm:
2428
access_token: YOUR_ACCESS_TOKEN
29+
access_token_passthrough: true
2530
endpoint: https://ingest.YOUR_SIGNALFX_REALM.signalfx.com/v2/trace
2631
max_connections: 100
2732
num_workers: 8

exporter/sapmexporter/config.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ import (
2020

2121
sapmclient "github.com/signalfx/sapm-proto/client"
2222
"go.opentelemetry.io/collector/config/configmodels"
23+
24+
"github.com/open-telemetry/opentelemetry-collector-contrib/internal/common/splunk"
2325
)
2426

2527
const (
@@ -47,6 +49,8 @@ type Config struct {
4749

4850
// Disable GZip compression.
4951
DisableCompression bool `mapstructure:"disable_compression"`
52+
53+
splunk.AccessTokenPassthroughConfig `mapstructure:",squash"`
5054
}
5155

5256
func (c *Config) validate() error {

exporter/sapmexporter/config_test.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ import (
2222
"github.com/stretchr/testify/require"
2323
"go.opentelemetry.io/collector/config"
2424
"go.opentelemetry.io/collector/config/configmodels"
25+
26+
"github.com/open-telemetry/opentelemetry-collector-contrib/internal/common/splunk"
2527
)
2628

2729
func TestLoadConfig(t *testing.T) {
@@ -50,5 +52,27 @@ func TestLoadConfig(t *testing.T) {
5052
AccessToken: "abcd1234",
5153
NumWorkers: 3,
5254
MaxConnections: 45,
55+
AccessTokenPassthroughConfig: splunk.AccessTokenPassthroughConfig{
56+
AccessTokenPassthrough: false,
57+
},
5358
})
5459
}
60+
61+
func TestInvalidConfig(t *testing.T) {
62+
invalid := Config{
63+
AccessToken: "abcd1234",
64+
NumWorkers: 3,
65+
MaxConnections: 45,
66+
}
67+
noEndpointErr := invalid.validate()
68+
require.Error(t, noEndpointErr)
69+
70+
invalid = Config{
71+
Endpoint: ":123:456",
72+
AccessToken: "abcd1234",
73+
NumWorkers: 3,
74+
MaxConnections: 45,
75+
}
76+
invalidURLErr := invalid.validate()
77+
require.Error(t, invalidURLErr)
78+
}

exporter/sapmexporter/exporter.go

Lines changed: 78 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -25,53 +25,115 @@ import (
2525
"go.opentelemetry.io/collector/exporter/exporterhelper"
2626
"go.opentelemetry.io/collector/translator/trace/jaeger"
2727
"go.uber.org/zap"
28+
29+
"github.com/open-telemetry/opentelemetry-collector-contrib/internal/common/splunk"
2830
)
2931

3032
// sapmExporter is a wrapper struct of SAPM exporter
3133
type sapmExporter struct {
3234
client *sapmclient.Client
3335
logger *zap.Logger
36+
config *Config
3437
}
3538

3639
func (se *sapmExporter) Shutdown(context.Context) error {
3740
se.client.Stop()
3841
return nil
3942
}
4043

41-
func newSAPMTraceExporter(cfg *Config, params component.ExporterCreateParams) (component.TraceExporter, error) {
44+
func newSAPMExporter(cfg *Config, params component.ExporterCreateParams) (sapmExporter, error) {
4245
err := cfg.validate()
4346
if err != nil {
44-
return nil, err
47+
return sapmExporter{}, err
4548
}
4649

4750
client, err := sapmclient.New(cfg.clientOptions()...)
4851
if err != nil {
49-
return nil, err
52+
return sapmExporter{}, err
5053
}
51-
se := sapmExporter{
54+
return sapmExporter{
5255
client: client,
5356
logger: params.Logger,
57+
config: cfg,
58+
}, err
59+
}
60+
61+
func newSAPMTraceExporter(cfg *Config, params component.ExporterCreateParams) (component.TraceExporter, error) {
62+
se, err := newSAPMExporter(cfg, params)
63+
if err != nil {
64+
return nil, err
5465
}
66+
5567
return exporterhelper.NewTraceExporter(
5668
cfg,
5769
se.pushTraceData,
5870
exporterhelper.WithShutdown(se.Shutdown))
5971
}
6072

61-
// pushTraceData exports traces in SAPM proto and returns number of dropped spans and error if export failed
62-
func (se *sapmExporter) pushTraceData(ctx context.Context, td pdata.Traces) (droppedSpansCount int, err error) {
63-
batches, err := jaeger.InternalTracesToJaegerProto(td)
64-
if err != nil {
65-
return td.SpanCount(), consumererror.Permanent(err)
73+
// tracesByAccessToken takes a pdata.Traces struct and will iterate through its ResourceSpans' attributes,
74+
// regrouping by any SFx access token label value if Config.AccessTokenPassthrough is enabled. It will delete any
75+
// set token label in any case to prevent serialization.
76+
// It returns a map of newly constructed pdata.Traces keyed by access token, defaulting to empty string.
77+
func (se *sapmExporter) tracesByAccessToken(td pdata.Traces) map[string]pdata.Traces {
78+
tracesByToken := make(map[string]pdata.Traces, 1)
79+
resourceSpans := td.ResourceSpans()
80+
for i := 0; i < resourceSpans.Len(); i++ {
81+
resourceSpan := resourceSpans.At(i)
82+
if resourceSpan.IsNil() {
83+
// Invalid trace so nothing to export
84+
continue
85+
}
86+
87+
accessToken := ""
88+
if !resourceSpan.Resource().IsNil() {
89+
attrs := resourceSpan.Resource().Attributes()
90+
attributeValue, ok := attrs.Get(splunk.SFxAccessTokenLabel)
91+
if ok {
92+
attrs.Delete(splunk.SFxAccessTokenLabel)
93+
if se.config.AccessTokenPassthrough {
94+
accessToken = attributeValue.StringVal()
95+
}
96+
}
97+
}
98+
99+
traceForToken, ok := tracesByToken[accessToken]
100+
if !ok {
101+
traceForToken = pdata.NewTraces()
102+
tracesByToken[accessToken] = traceForToken
103+
}
104+
105+
// Append ResourceSpan to trace for this access token
106+
traceForTokenSize := traceForToken.ResourceSpans().Len()
107+
traceForToken.ResourceSpans().Resize(traceForTokenSize + 1)
108+
traceForToken.ResourceSpans().At(traceForTokenSize).InitEmpty()
109+
resourceSpan.CopyTo(traceForToken.ResourceSpans().At(traceForTokenSize))
66110
}
67-
err = se.client.Export(ctx, batches)
68-
if err != nil {
69-
if sendErr, ok := err.(*sapmclient.ErrSend); ok {
70-
if sendErr.Permanent {
71-
return 0, consumererror.Permanent(sendErr)
111+
112+
return tracesByToken
113+
}
114+
115+
// pushTraceData exports traces in SAPM proto by associated SFx access token and returns number of dropped spans
116+
// and the last experienced error if any translation or export failed
117+
func (se *sapmExporter) pushTraceData(ctx context.Context, td pdata.Traces) (droppedSpansCount int, err error) {
118+
traces := se.tracesByAccessToken(td)
119+
droppedSpansCount = 0
120+
for accessToken, trace := range traces {
121+
batches, translateErr := jaeger.InternalTracesToJaegerProto(trace)
122+
if translateErr != nil {
123+
droppedSpansCount += trace.SpanCount()
124+
err = consumererror.Permanent(translateErr)
125+
continue
126+
}
127+
128+
exportErr := se.client.ExportWithAccessToken(ctx, batches, accessToken)
129+
if exportErr != nil {
130+
if sendErr, ok := exportErr.(*sapmclient.ErrSend); ok {
131+
if sendErr.Permanent {
132+
err = consumererror.Permanent(sendErr)
133+
}
72134
}
135+
droppedSpansCount += trace.SpanCount()
73136
}
74-
return td.SpanCount(), err
75137
}
76-
return 0, nil
138+
return
77139
}

0 commit comments

Comments
 (0)