Skip to content

Commit

Permalink
Use Splunk realm for endpoint if defined (#725)
Browse files Browse the repository at this point in the history
  • Loading branch information
MrAlias authored Apr 1, 2022
1 parent 703c2a9 commit 06ad318
Show file tree
Hide file tree
Showing 5 changed files with 178 additions and 17 deletions.
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
4 changes: 4 additions & 0 deletions distro/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,10 @@ telemetry sent directly to Splunk Observability Cloud.
- `OTEL_EXPORTER_OTLP_ENDPOINT`
- `OTEL_EXPORTER_OTLP_TRACES_ENDPOINT`
- For the `jaeger-thrift-splunk` exporter: `OTEL_EXPORTER_JAEGER_ENDPOINT`
- `SPLUNK_REALM`:
- If the exporter endpoint is not set with one of the environment variable
above, this value will be used to define the remote endpoint. This value
needs to be the name of your organization's realm, for example, `us0`.

### `WithPropagator`

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
65 changes: 51 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 && notNone(realm) {
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,27 @@ 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 && notNone(realm) {
return fmt.Sprintf(realmEndpointFormat, realm)
}

// Use Splunk specific default (locally running collector).
return defaultJaegerEndpoint
}

// notNone returns if s is not empty or set to none.
func notNone(s string) bool {
return s != "" && s != "none"
}
90 changes: 90 additions & 0 deletions distro/exporter_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
// 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 (
noneRealm = "none"
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, noneRealm))
t.Run("none realm", func(t *testing.T) {
// Revert to default.
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, noneRealm))
t.Run("none realm", func(t *testing.T) {
// Revert to default.
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(""))
})
}

0 comments on commit 06ad318

Please sign in to comment.