Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
- Add the `WithLogger` option to `github.com/signalfx/splunk-otel-go/distro`
along with parsing of the `OTEL_LOG_LEVEL` environment variable to set the
logging level of the default logger used. (#336)
- The `SPLUNK_REALM` environment variable is now supported. If set, the
exporter will use the corresponding Splunk ingest endpoint. (#725)

### Changed

Expand Down
34 changes: 31 additions & 3 deletions distro/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,14 +41,19 @@ const (
otelTracesExporterKey = "OTEL_TRACES_EXPORTER"

// OpenTelemetry exporter endpoints.
otelExporterJaegerEndpointKey = "OTEL_EXPORTER_JAEGER_ENDPOINT"
otelExporterJaegerEndpointKey = "OTEL_EXPORTER_JAEGER_ENDPOINT"
otelExporterOTLPEndpointKey = "OTEL_EXPORTER_OTLP_ENDPOINT"
otelExporterOTLPTracesEndpointKey = "OTEL_EXPORTER_OTLP_TRACES_ENDPOINT"

// Logging level to set when using the default logger.
otelLogLevelKey = "OTEL_LOG_LEVEL"

// splunkMetricsEndpointKey defines the endpoint Splunk specific metrics
// are sent. This is not currently supported.
splunkMetricsEndpointKey = "SPLUNK_METRICS_ENDPOINT"

// splunkRealmKey defines the Splunk realm to build an endpoint from.
splunkRealmKey = "SPLUNK_REALM"
)

// Default configuration values.
Expand All @@ -59,6 +64,9 @@ const (
defaultLogLevel = "info"

defaultJaegerEndpoint = "http://127.0.0.1:9080/v1/trace"

realmEndpointFormat = "https://ingest.%s.signalfx.com/v2/trace"
otlpRealmEndpointFormat = realmEndpointFormat + "/otlp"
)

type exporterConfig struct {
Expand Down Expand Up @@ -209,8 +217,28 @@ func (fn optionFunc) apply(c *config) {
fn(c)
}

// WithEndpoint configures the endpoint telemetry is sent to. Passing an empty
// string results in the default value being used.
// WithEndpoint configures the endpoint telemetry is sent to.
//
// If the SPLUNK_REALM environment variable is defined and this option is not
// provided the splunk remote endpoint for that realm will be used. The value
// depends on what exporter is used. For the otlp exporter it will be
// https://ingest.${SPLUNK_REALM}.signalfx.com/v2/trace/otlp and for the
// Jaeger thrift exporter it will be
// https://ingest.${SPLUNK_REALM}.signalfx.com/v2/trace.
//
// If the OpenTelemetry endpoint environment variables are defined and this
// option is not provided, those values will be used (for the appropriate
// exporter). These environment variables will take precedence over the
// SPLUNK_REALM environment variable. See the OpenTelemetry documentation for
// more information on these environment variables:
// https://github.com/open-telemetry/opentelemetry-specification/blob/v1.9.0/specification/sdk-environment-variables.md
// and
// https://github.com/open-telemetry/opentelemetry-specification/blob/v1.9.0/specification/protocol/exporter.md
//
// Passing an empty string or not providing this option at all will result in
// the default value being used. That value depends on what exporter is used.
// For the otlp exporter it will be http://localhost:4317, and for the Jaeger
// thrift exporter it will be http://127.0.0.1:9080/v1/trace.
func WithEndpoint(endpoint string) Option {
return optionFunc(func(c *config) {
c.ExportConfig.Endpoint = endpoint
Expand Down
60 changes: 46 additions & 14 deletions distro/exporter.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ package distro

import (
"context"
"fmt"
"net/http"
"os"

Expand All @@ -41,8 +42,8 @@ var exporters = map[string]traceExporterFunc{
func newOTLPExporter(c *exporterConfig) (trace.SpanExporter, error) {
var opts []otlptracegrpc.Option

if c.Endpoint != "" {
opts = append(opts, otlptracegrpc.WithEndpoint(c.Endpoint))
if e := otlpEndpoint(c.Endpoint); e != "" {
opts = append(opts, otlptracegrpc.WithEndpoint(e))
}

if c.TLSConfig != nil {
Expand All @@ -61,21 +62,33 @@ func newOTLPExporter(c *exporterConfig) (trace.SpanExporter, error) {
return otlptracegrpc.New(context.Background(), opts...)
}

func otlpEndpoint(configured string) string {
if configured != "" {
return configured
}

// Allow the exporter to interpret these environment variables directly.
envs := []string{otelExporterOTLPEndpointKey, otelExporterOTLPTracesEndpointKey}
for _, env := range envs {
if _, ok := os.LookupEnv(env); ok {
return ""
}
}

// Use the realm only if OTEL_EXPORTER_OTLP*_ENDPOINT are not defined.
if realm, ok := os.LookupEnv(splunkRealmKey); ok {
return fmt.Sprintf(otlpRealmEndpointFormat, realm)
}

// The OTel default is the same as Splunk's.
return ""
}

func newJaegerThriftExporter(c *exporterConfig) (trace.SpanExporter, error) {
var opts []jaeger.CollectorEndpointOption

if c.Endpoint == "" {
if endpoint := func() string {
// Allow the exporter to use environment variables.
if _, ok := os.LookupEnv(otelExporterJaegerEndpointKey); ok {
return ""
}
return defaultJaegerEndpoint
}(); endpoint != "" {
opts = append(opts, jaeger.WithEndpoint(endpoint))
}
} else {
opts = append(opts, jaeger.WithEndpoint(c.Endpoint))
if e := jaegerEndpoint(c.Endpoint); e != "" {
opts = append(opts, jaeger.WithEndpoint(e))
}

if c.AccessToken != "" {
Expand All @@ -97,3 +110,22 @@ func newJaegerThriftExporter(c *exporterConfig) (trace.SpanExporter, error) {
jaeger.WithCollectorEndpoint(opts...),
)
}

func jaegerEndpoint(configured string) string {
if configured != "" {
return configured
}

// Allow the exporter to interpret this environment variable directly.
if _, ok := os.LookupEnv(otelExporterJaegerEndpointKey); ok {
return ""
}

// Use the realm only if OTEL_EXPORTER_JAGER_ENDPOINT is not defined.
if realm, ok := os.LookupEnv(splunkRealmKey); ok {
return fmt.Sprintf(realmEndpointFormat, realm)
}

// Use Splunk specific default (locally running collector).
return defaultJaegerEndpoint
}
77 changes: 77 additions & 0 deletions distro/exporter_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
// Copyright Splunk Inc.
//
// Licensed 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 distro

import (
"fmt"
"testing"

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

const (
invalidRealm = "not-a-valid-realm"
fakeEndpoint = "some non-zero value"
)

func TestOTLPEndpoint(t *testing.T) {
t.Run("configured", func(t *testing.T) {
assert.Equal(t, fakeEndpoint, otlpEndpoint(fakeEndpoint))
})

t.Run("default", func(t *testing.T) {
assert.Equal(t, "", otlpEndpoint(""))
})

t.Cleanup(Setenv(splunkRealmKey, invalidRealm))
t.Run("realm", func(t *testing.T) {
want := fmt.Sprintf(otlpRealmEndpointFormat, invalidRealm)
assert.Equal(t, want, otlpEndpoint(""))
})

t.Run(otelExporterOTLPEndpointKey, func(t *testing.T) {
t.Cleanup(Setenv(otelExporterOTLPEndpointKey, fakeEndpoint))
// SPLUNK_REALM is still set, make sure it does not take precedence.
assert.Equal(t, "", otlpEndpoint(""))
})

t.Run(otelExporterOTLPTracesEndpointKey, func(t *testing.T) {
t.Cleanup(Setenv(otelExporterOTLPTracesEndpointKey, "some non-zero value"))
// SPLUNK_REALM is still set, make sure it does not take precedence.
assert.Equal(t, "", otlpEndpoint(""))
})
}

func TestJaegerEndpoint(t *testing.T) {
t.Run("configured", func(t *testing.T) {
assert.Equal(t, fakeEndpoint, jaegerEndpoint(fakeEndpoint))
})

t.Run("default", func(t *testing.T) {
assert.Equal(t, defaultJaegerEndpoint, jaegerEndpoint(""))
})

t.Cleanup(Setenv(splunkRealmKey, invalidRealm))
t.Run("realm", func(t *testing.T) {
want := fmt.Sprintf(realmEndpointFormat, invalidRealm)
assert.Equal(t, want, jaegerEndpoint(""))
})

t.Run(otelExporterJaegerEndpointKey, func(t *testing.T) {
t.Cleanup(Setenv(otelExporterJaegerEndpointKey, fakeEndpoint))
// SPLUNK_REALM is still set, make sure it does not take precedence.
assert.Equal(t, "", jaegerEndpoint(""))
})
}