mirror of
https://github.com/open-telemetry/opentelemetry-go.git
synced 2025-01-12 02:28:07 +02:00
Merge pull request #796 from XSAM/feature/jaeger-exporter-env
Add environment variables for Jaeger exporter
This commit is contained in:
commit
4bf35c611b
163
exporters/trace/jaeger/env.go
Normal file
163
exporters/trace/jaeger/env.go
Normal file
@ -0,0 +1,163 @@
|
||||
// Copyright The OpenTelemetry Authors
|
||||
//
|
||||
// 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 jaeger
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"go.opentelemetry.io/otel/api/global"
|
||||
"go.opentelemetry.io/otel/api/kv"
|
||||
"go.opentelemetry.io/otel/api/kv/value"
|
||||
)
|
||||
|
||||
// Environment variable names
|
||||
const (
|
||||
// The service name.
|
||||
envServiceName = "JAEGER_SERVICE_NAME"
|
||||
// Whether the exporter is disabled or not. (default false).
|
||||
envDisabled = "JAEGER_DISABLED"
|
||||
// A comma separated list of name=value tracer-level tags, which get added to all reported spans.
|
||||
// The value can also refer to an environment variable using the format ${envVarName:defaultValue}.
|
||||
envTags = "JAEGER_TAGS"
|
||||
// The HTTP endpoint for sending spans directly to a collector,
|
||||
// i.e. http://jaeger-collector:14268/api/traces.
|
||||
envEndpoint = "JAEGER_ENDPOINT"
|
||||
// Username to send as part of "Basic" authentication to the collector endpoint.
|
||||
envUser = "JAEGER_USER"
|
||||
// Password to send as part of "Basic" authentication to the collector endpoint.
|
||||
envPassword = "JAEGER_PASSWORD"
|
||||
)
|
||||
|
||||
// CollectorEndpointFromEnv return environment variable value of JAEGER_ENDPOINT
|
||||
func CollectorEndpointFromEnv() string {
|
||||
return os.Getenv(envEndpoint)
|
||||
}
|
||||
|
||||
// WithCollectorEndpointOptionFromEnv uses environment variables to set the username and password
|
||||
// if basic auth is required.
|
||||
func WithCollectorEndpointOptionFromEnv() CollectorEndpointOption {
|
||||
return func(o *CollectorEndpointOptions) {
|
||||
if e := os.Getenv(envUser); e != "" {
|
||||
o.username = e
|
||||
}
|
||||
if e := os.Getenv(envPassword); e != "" {
|
||||
o.password = os.Getenv(envPassword)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// WithDisabledFromEnv uses environment variables and overrides disabled field.
|
||||
func WithDisabledFromEnv() Option {
|
||||
return func(o *options) {
|
||||
if e := os.Getenv(envDisabled); e != "" {
|
||||
if v, err := strconv.ParseBool(e); err == nil {
|
||||
o.Disabled = v
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ProcessFromEnv parse environment variables into jaeger exporter's Process.
|
||||
// It will return a nil tag slice if the environment variable JAEGER_TAGS is malformed.
|
||||
func ProcessFromEnv() Process {
|
||||
var p Process
|
||||
if e := os.Getenv(envServiceName); e != "" {
|
||||
p.ServiceName = e
|
||||
}
|
||||
if e := os.Getenv(envTags); e != "" {
|
||||
tags, err := parseTags(e)
|
||||
if err != nil {
|
||||
global.Handle(err)
|
||||
} else {
|
||||
p.Tags = tags
|
||||
}
|
||||
}
|
||||
|
||||
return p
|
||||
}
|
||||
|
||||
// WithProcessFromEnv uses environment variables and overrides jaeger exporter's Process.
|
||||
func WithProcessFromEnv() Option {
|
||||
return func(o *options) {
|
||||
p := ProcessFromEnv()
|
||||
if p.ServiceName != "" {
|
||||
o.Process.ServiceName = p.ServiceName
|
||||
}
|
||||
if len(p.Tags) != 0 {
|
||||
o.Process.Tags = p.Tags
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var errTagValueNotFound = errors.New("missing tag value")
|
||||
var errTagEnvironmentDefaultValueNotFound = errors.New("missing default value for tag environment value")
|
||||
|
||||
// parseTags parses the given string into a collection of Tags.
|
||||
// Spec for this value:
|
||||
// - comma separated list of key=value
|
||||
// - value can be specified using the notation ${envVar:defaultValue}, where `envVar`
|
||||
// is an environment variable and `defaultValue` is the value to use in case the env var is not set
|
||||
func parseTags(sTags string) ([]kv.KeyValue, error) {
|
||||
pairs := strings.Split(sTags, ",")
|
||||
tags := make([]kv.KeyValue, len(pairs))
|
||||
for i, p := range pairs {
|
||||
field := strings.SplitN(p, "=", 2)
|
||||
if len(field) != 2 {
|
||||
return nil, errTagValueNotFound
|
||||
}
|
||||
k, v := strings.TrimSpace(field[0]), strings.TrimSpace(field[1])
|
||||
|
||||
if strings.HasPrefix(v, "${") && strings.HasSuffix(v, "}") {
|
||||
ed := strings.SplitN(v[2:len(v)-1], ":", 2)
|
||||
if len(ed) != 2 {
|
||||
return nil, errTagEnvironmentDefaultValueNotFound
|
||||
}
|
||||
e, d := ed[0], ed[1]
|
||||
v = os.Getenv(e)
|
||||
if v == "" && d != "" {
|
||||
v = d
|
||||
}
|
||||
}
|
||||
|
||||
tags[i] = parseKeyValue(k, v)
|
||||
}
|
||||
|
||||
return tags, nil
|
||||
}
|
||||
|
||||
func parseKeyValue(k, v string) kv.KeyValue {
|
||||
return kv.KeyValue{
|
||||
Key: kv.Key(k),
|
||||
Value: parseValue(v),
|
||||
}
|
||||
}
|
||||
|
||||
func parseValue(str string) value.Value {
|
||||
if v, err := strconv.ParseInt(str, 10, 64); err == nil {
|
||||
return value.Int64(v)
|
||||
}
|
||||
if v, err := strconv.ParseFloat(str, 64); err == nil {
|
||||
return value.Float64(v)
|
||||
}
|
||||
if v, err := strconv.ParseBool(str); err == nil {
|
||||
return value.Bool(v)
|
||||
}
|
||||
|
||||
// Fallback
|
||||
return value.String(str)
|
||||
}
|
513
exporters/trace/jaeger/env_test.go
Normal file
513
exporters/trace/jaeger/env_test.go
Normal file
@ -0,0 +1,513 @@
|
||||
// Copyright The OpenTelemetry Authors
|
||||
//
|
||||
// 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 jaeger
|
||||
|
||||
import (
|
||||
"math"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"go.opentelemetry.io/otel/api/kv"
|
||||
"go.opentelemetry.io/otel/api/kv/value"
|
||||
ottest "go.opentelemetry.io/otel/internal/testing"
|
||||
)
|
||||
|
||||
func Test_parseTags(t *testing.T) {
|
||||
envStore, err := ottest.SetEnvVariables(map[string]string{
|
||||
"existing": "not-default",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
defer func() {
|
||||
require.NoError(t, envStore.Restore())
|
||||
}()
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
tagStr string
|
||||
expectedTags []kv.KeyValue
|
||||
expectedError error
|
||||
}{
|
||||
{
|
||||
name: "string",
|
||||
tagStr: "key=value",
|
||||
expectedTags: []kv.KeyValue{
|
||||
{
|
||||
Key: "key",
|
||||
Value: value.String("value"),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "int64",
|
||||
tagStr: "k=9223372036854775807,k2=-9223372036854775808",
|
||||
expectedTags: []kv.KeyValue{
|
||||
{
|
||||
Key: "k",
|
||||
Value: value.Int64(math.MaxInt64),
|
||||
},
|
||||
{
|
||||
Key: "k2",
|
||||
Value: value.Int64(math.MinInt64),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "float64",
|
||||
tagStr: "k=1.797693134862315708145274237317043567981e+308,k2=4.940656458412465441765687928682213723651e-324,k3=-1.2",
|
||||
expectedTags: []kv.KeyValue{
|
||||
{
|
||||
Key: "k",
|
||||
Value: value.Float64(math.MaxFloat64),
|
||||
},
|
||||
{
|
||||
Key: "k2",
|
||||
Value: value.Float64(math.SmallestNonzeroFloat64),
|
||||
},
|
||||
{
|
||||
Key: "k3",
|
||||
Value: value.Float64(-1.2),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "multiple type values",
|
||||
tagStr: "k=v,k2=123, k3=v3 ,k4=-1.2, k5=${existing:default},k6=${nonExisting:default}",
|
||||
expectedTags: []kv.KeyValue{
|
||||
{
|
||||
Key: "k",
|
||||
Value: value.String("v"),
|
||||
},
|
||||
{
|
||||
Key: "k2",
|
||||
Value: value.Int64(123),
|
||||
},
|
||||
{
|
||||
Key: "k3",
|
||||
Value: value.String("v3"),
|
||||
},
|
||||
{
|
||||
Key: "k4",
|
||||
Value: value.Float64(-1.2),
|
||||
},
|
||||
{
|
||||
Key: "k5",
|
||||
Value: value.String("not-default"),
|
||||
},
|
||||
{
|
||||
Key: "k6",
|
||||
Value: value.String("default"),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "malformed: only have key",
|
||||
tagStr: "key",
|
||||
expectedError: errTagValueNotFound,
|
||||
},
|
||||
{
|
||||
name: "malformed: environment key has no default value",
|
||||
tagStr: "key=${foo}",
|
||||
expectedError: errTagEnvironmentDefaultValueNotFound,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
tags, err := parseTags(tc.tagStr)
|
||||
if tc.expectedError == nil {
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tc.expectedTags, tags)
|
||||
} else {
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, tc.expectedError, err)
|
||||
assert.Equal(t, tc.expectedTags, tags)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_parseValue(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
str string
|
||||
expected value.Value
|
||||
}{
|
||||
{
|
||||
name: "bool: true",
|
||||
str: "true",
|
||||
expected: value.Bool(true),
|
||||
},
|
||||
{
|
||||
name: "bool: false",
|
||||
str: "false",
|
||||
expected: value.Bool(false),
|
||||
},
|
||||
{
|
||||
name: "int64: 012340",
|
||||
str: "012340",
|
||||
expected: value.Int64(12340),
|
||||
},
|
||||
{
|
||||
name: "int64: -012340",
|
||||
str: "-012340",
|
||||
expected: value.Int64(-12340),
|
||||
},
|
||||
{
|
||||
name: "int64: 0",
|
||||
str: "0",
|
||||
expected: value.Int64(0),
|
||||
},
|
||||
{
|
||||
name: "float64: -0.1",
|
||||
str: "-0.1",
|
||||
expected: value.Float64(-0.1),
|
||||
},
|
||||
{
|
||||
name: "float64: 00.001",
|
||||
str: "00.001",
|
||||
expected: value.Float64(0.001),
|
||||
},
|
||||
{
|
||||
name: "float64: 1E23",
|
||||
str: "1E23",
|
||||
expected: value.Float64(1e23),
|
||||
},
|
||||
{
|
||||
name: "string: foo",
|
||||
str: "foo",
|
||||
expected: value.String("foo"),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
v := parseValue(tc.str)
|
||||
assert.Equal(t, tc.expected, v)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewRawExporterWithEnv(t *testing.T) {
|
||||
const (
|
||||
collectorEndpoint = "http://localhost"
|
||||
username = "user"
|
||||
password = "password"
|
||||
serviceName = "test-service"
|
||||
disabled = "false"
|
||||
tags = "key=value"
|
||||
)
|
||||
|
||||
envStore, err := ottest.SetEnvVariables(map[string]string{
|
||||
envEndpoint: collectorEndpoint,
|
||||
envUser: username,
|
||||
envPassword: password,
|
||||
envDisabled: disabled,
|
||||
envServiceName: serviceName,
|
||||
envTags: tags,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
defer func() {
|
||||
require.NoError(t, envStore.Restore())
|
||||
}()
|
||||
|
||||
// Create Jaeger Exporter with environment variables
|
||||
exp, err := NewRawExporter(
|
||||
WithCollectorEndpoint(CollectorEndpointFromEnv(), WithCollectorEndpointOptionFromEnv()),
|
||||
WithDisabled(true),
|
||||
WithDisabledFromEnv(),
|
||||
WithProcessFromEnv(),
|
||||
)
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, false, exp.o.Disabled)
|
||||
assert.EqualValues(t, serviceName, exp.process.ServiceName)
|
||||
assert.Len(t, exp.process.Tags, 1)
|
||||
|
||||
require.IsType(t, &collectorUploader{}, exp.uploader)
|
||||
uploader := exp.uploader.(*collectorUploader)
|
||||
assert.Equal(t, collectorEndpoint, uploader.endpoint)
|
||||
assert.Equal(t, username, uploader.username)
|
||||
assert.Equal(t, password, uploader.password)
|
||||
}
|
||||
|
||||
func TestNewRawExporterWithEnvImplicitly(t *testing.T) {
|
||||
const (
|
||||
collectorEndpoint = "http://localhost"
|
||||
username = "user"
|
||||
password = "password"
|
||||
serviceName = "test-service"
|
||||
disabled = "false"
|
||||
tags = "key=value"
|
||||
)
|
||||
|
||||
envStore, err := ottest.SetEnvVariables(map[string]string{
|
||||
envEndpoint: collectorEndpoint,
|
||||
envUser: username,
|
||||
envPassword: password,
|
||||
envDisabled: disabled,
|
||||
envServiceName: serviceName,
|
||||
envTags: tags,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
defer func() {
|
||||
require.NoError(t, envStore.Restore())
|
||||
}()
|
||||
|
||||
// Create Jaeger Exporter with environment variables
|
||||
exp, err := NewRawExporter(
|
||||
WithCollectorEndpoint("should be overwritten"),
|
||||
WithDisabled(true),
|
||||
)
|
||||
|
||||
assert.NoError(t, err)
|
||||
// NewRawExporter will ignore Disabled env
|
||||
assert.Equal(t, true, exp.o.Disabled)
|
||||
assert.EqualValues(t, serviceName, exp.process.ServiceName)
|
||||
assert.Len(t, exp.process.Tags, 1)
|
||||
|
||||
require.IsType(t, &collectorUploader{}, exp.uploader)
|
||||
uploader := exp.uploader.(*collectorUploader)
|
||||
assert.Equal(t, collectorEndpoint, uploader.endpoint)
|
||||
assert.Equal(t, username, uploader.username)
|
||||
assert.Equal(t, password, uploader.password)
|
||||
}
|
||||
|
||||
func TestCollectorEndpointFromEnv(t *testing.T) {
|
||||
const (
|
||||
collectorEndpoint = "http://localhost"
|
||||
)
|
||||
|
||||
envStore, err := ottest.SetEnvVariables(map[string]string{
|
||||
envEndpoint: collectorEndpoint,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
defer func() {
|
||||
require.NoError(t, envStore.Restore())
|
||||
}()
|
||||
|
||||
assert.Equal(t, collectorEndpoint, CollectorEndpointFromEnv())
|
||||
}
|
||||
|
||||
func TestWithCollectorEndpointOptionFromEnv(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
envUsername string
|
||||
envPassword string
|
||||
collectorEndpointOptions CollectorEndpointOptions
|
||||
expectedCollectorEndpointOptions CollectorEndpointOptions
|
||||
}{
|
||||
{
|
||||
name: "overrides value via environment variables",
|
||||
envUsername: "username",
|
||||
envPassword: "password",
|
||||
collectorEndpointOptions: CollectorEndpointOptions{
|
||||
username: "foo",
|
||||
password: "bar",
|
||||
},
|
||||
expectedCollectorEndpointOptions: CollectorEndpointOptions{
|
||||
username: "username",
|
||||
password: "password",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "environment variables is empty, will not overwrite value",
|
||||
envUsername: "",
|
||||
envPassword: "",
|
||||
collectorEndpointOptions: CollectorEndpointOptions{
|
||||
username: "foo",
|
||||
password: "bar",
|
||||
},
|
||||
expectedCollectorEndpointOptions: CollectorEndpointOptions{
|
||||
username: "foo",
|
||||
password: "bar",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
envStore := ottest.NewEnvStore()
|
||||
envStore.Record(envUser)
|
||||
envStore.Record(envPassword)
|
||||
defer func() {
|
||||
require.NoError(t, envStore.Restore())
|
||||
}()
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
require.NoError(t, os.Setenv(envUser, tc.envUsername))
|
||||
require.NoError(t, os.Setenv(envPassword, tc.envPassword))
|
||||
|
||||
f := WithCollectorEndpointOptionFromEnv()
|
||||
f(&tc.collectorEndpointOptions)
|
||||
|
||||
assert.Equal(t, tc.expectedCollectorEndpointOptions, tc.collectorEndpointOptions)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestWithDisabledFromEnv(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
env string
|
||||
options options
|
||||
expectedOptions options
|
||||
}{
|
||||
{
|
||||
name: "overwriting",
|
||||
env: "true",
|
||||
options: options{},
|
||||
expectedOptions: options{Disabled: true},
|
||||
},
|
||||
{
|
||||
name: "no overwriting",
|
||||
env: "",
|
||||
options: options{Disabled: true},
|
||||
expectedOptions: options{Disabled: true},
|
||||
},
|
||||
}
|
||||
|
||||
envStore := ottest.NewEnvStore()
|
||||
envStore.Record(envDisabled)
|
||||
defer func() {
|
||||
require.NoError(t, envStore.Restore())
|
||||
}()
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
require.NoError(t, os.Setenv(envDisabled, tc.env))
|
||||
|
||||
f := WithDisabledFromEnv()
|
||||
f(&tc.options)
|
||||
|
||||
assert.Equal(t, tc.expectedOptions, tc.options)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestProcessFromEnv(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
serviceName string
|
||||
tags string
|
||||
expectedProcess Process
|
||||
}{
|
||||
{
|
||||
name: "set process",
|
||||
serviceName: "test-service",
|
||||
tags: "key=value,key2=123",
|
||||
expectedProcess: Process{
|
||||
ServiceName: "test-service",
|
||||
Tags: []kv.KeyValue{
|
||||
kv.String("key", "value"),
|
||||
kv.Int64("key2", 123),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "malformed tags",
|
||||
serviceName: "test-service",
|
||||
tags: "key",
|
||||
expectedProcess: Process{
|
||||
ServiceName: "test-service",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
envStore, err := ottest.SetEnvVariables(map[string]string{
|
||||
envServiceName: tc.serviceName,
|
||||
envTags: tc.tags,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
p := ProcessFromEnv()
|
||||
assert.Equal(t, tc.expectedProcess, p)
|
||||
|
||||
require.NoError(t, envStore.Restore())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestWithProcessFromEnv(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
envServiceName string
|
||||
envTags string
|
||||
options options
|
||||
expectedOptions options
|
||||
}{
|
||||
{
|
||||
name: "overwriting",
|
||||
envServiceName: "service-name",
|
||||
envTags: "key=value",
|
||||
options: options{
|
||||
Process: Process{
|
||||
ServiceName: "old-name",
|
||||
Tags: []kv.KeyValue{
|
||||
kv.String("old-key", "old-value"),
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedOptions: options{
|
||||
Process: Process{
|
||||
ServiceName: "service-name",
|
||||
Tags: []kv.KeyValue{
|
||||
kv.String("key", "value"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "no overwriting",
|
||||
envServiceName: "",
|
||||
envTags: "",
|
||||
options: options{
|
||||
Process: Process{
|
||||
ServiceName: "old-name",
|
||||
Tags: []kv.KeyValue{
|
||||
kv.String("old-key", "old-value"),
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedOptions: options{
|
||||
Process: Process{
|
||||
ServiceName: "old-name",
|
||||
Tags: []kv.KeyValue{
|
||||
kv.String("old-key", "old-value"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
envStore := ottest.NewEnvStore()
|
||||
envStore.Record(envServiceName)
|
||||
envStore.Record(envTags)
|
||||
defer func() {
|
||||
require.NoError(t, envStore.Restore())
|
||||
}()
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
require.NoError(t, os.Setenv(envServiceName, tc.envServiceName))
|
||||
require.NoError(t, os.Setenv(envTags, tc.envTags))
|
||||
|
||||
f := WithProcessFromEnv()
|
||||
f(&tc.options)
|
||||
|
||||
assert.Equal(t, tc.expectedOptions, tc.options)
|
||||
})
|
||||
}
|
||||
}
|
@ -18,13 +18,13 @@ import (
|
||||
"context"
|
||||
"encoding/binary"
|
||||
|
||||
"go.opentelemetry.io/otel/api/kv/value"
|
||||
|
||||
"google.golang.org/api/support/bundler"
|
||||
"google.golang.org/grpc/codes"
|
||||
|
||||
"go.opentelemetry.io/otel/api/global"
|
||||
"go.opentelemetry.io/otel/api/kv"
|
||||
"go.opentelemetry.io/otel/api/kv/value"
|
||||
apitrace "go.opentelemetry.io/otel/api/trace"
|
||||
gen "go.opentelemetry.io/otel/exporters/trace/jaeger/internal/gen-go/jaeger"
|
||||
export "go.opentelemetry.io/otel/sdk/export/trace"
|
||||
sdktrace "go.opentelemetry.io/otel/sdk/trace"
|
||||
@ -39,7 +39,7 @@ type options struct {
|
||||
// Process contains the information about the exporting process.
|
||||
Process Process
|
||||
|
||||
//BufferMaxCount defines the total number of traces that can be buffered in memory
|
||||
// BufferMaxCount defines the total number of traces that can be buffered in memory
|
||||
BufferMaxCount int
|
||||
|
||||
Config *sdktrace.Config
|
||||
@ -47,6 +47,8 @@ type options struct {
|
||||
// RegisterGlobal is set to true if the trace provider of the new pipeline should be
|
||||
// registered as Global Trace Provider
|
||||
RegisterGlobal bool
|
||||
|
||||
Disabled bool
|
||||
}
|
||||
|
||||
// WithProcess sets the process with the information about the exporting process.
|
||||
@ -56,7 +58,7 @@ func WithProcess(process Process) Option {
|
||||
}
|
||||
}
|
||||
|
||||
//WithBufferMaxCount defines the total number of traces that can be buffered in memory
|
||||
// WithBufferMaxCount defines the total number of traces that can be buffered in memory
|
||||
func WithBufferMaxCount(bufferMaxCount int) Option {
|
||||
return func(o *options) {
|
||||
o.BufferMaxCount = bufferMaxCount
|
||||
@ -78,8 +80,16 @@ func RegisterAsGlobal() Option {
|
||||
}
|
||||
}
|
||||
|
||||
func WithDisabled(disabled bool) Option {
|
||||
return func(o *options) {
|
||||
o.Disabled = disabled
|
||||
}
|
||||
}
|
||||
|
||||
// NewRawExporter returns a trace.Exporter implementation that exports
|
||||
// the collected spans to Jaeger.
|
||||
//
|
||||
// It will IGNORE Disabled option.
|
||||
func NewRawExporter(endpointOption EndpointOption, opts ...Option) (*Exporter, error) {
|
||||
uploader, err := endpointOption()
|
||||
if err != nil {
|
||||
@ -87,6 +97,7 @@ func NewRawExporter(endpointOption EndpointOption, opts ...Option) (*Exporter, e
|
||||
}
|
||||
|
||||
o := options{}
|
||||
opts = append(opts, WithProcessFromEnv())
|
||||
for _, opt := range opts {
|
||||
opt(&o)
|
||||
}
|
||||
@ -129,7 +140,16 @@ func NewRawExporter(endpointOption EndpointOption, opts ...Option) (*Exporter, e
|
||||
|
||||
// NewExportPipeline sets up a complete export pipeline
|
||||
// with the recommended setup for trace provider
|
||||
func NewExportPipeline(endpointOption EndpointOption, opts ...Option) (*sdktrace.Provider, func(), error) {
|
||||
func NewExportPipeline(endpointOption EndpointOption, opts ...Option) (apitrace.Provider, func(), error) {
|
||||
o := options{}
|
||||
opts = append(opts, WithDisabledFromEnv())
|
||||
for _, opt := range opts {
|
||||
opt(&o)
|
||||
}
|
||||
if o.Disabled {
|
||||
return &apitrace.NoopProvider{}, func() {}, nil
|
||||
}
|
||||
|
||||
exporter, err := NewRawExporter(endpointOption, opts...)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
|
@ -17,18 +17,21 @@ package jaeger
|
||||
import (
|
||||
"context"
|
||||
"encoding/binary"
|
||||
"os"
|
||||
"sort"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"google.golang.org/grpc/codes"
|
||||
|
||||
"go.opentelemetry.io/otel/api/global"
|
||||
"go.opentelemetry.io/otel/api/kv"
|
||||
apitrace "go.opentelemetry.io/otel/api/trace"
|
||||
gen "go.opentelemetry.io/otel/exporters/trace/jaeger/internal/gen-go/jaeger"
|
||||
ottest "go.opentelemetry.io/otel/internal/testing"
|
||||
export "go.opentelemetry.io/otel/sdk/export/trace"
|
||||
"go.opentelemetry.io/otel/sdk/resource"
|
||||
sdktrace "go.opentelemetry.io/otel/sdk/trace"
|
||||
@ -105,6 +108,16 @@ func TestNewRawExporter(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestNewRawExporterShouldFailIfCollectorEndpointEmpty(t *testing.T) {
|
||||
// Record and restore env
|
||||
envStore := ottest.NewEnvStore()
|
||||
envStore.Record(envEndpoint)
|
||||
defer func() {
|
||||
require.NoError(t, envStore.Restore())
|
||||
}()
|
||||
|
||||
// If the user sets the environment variable JAEGER_ENDPOINT, endpoint will always get a value.
|
||||
require.NoError(t, os.Unsetenv(envEndpoint))
|
||||
|
||||
_, err := NewRawExporter(
|
||||
WithCollectorEndpoint(""),
|
||||
)
|
||||
@ -303,3 +316,31 @@ func Test_spanDataToThrift(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewExporterPipelineWithDisabled(t *testing.T) {
|
||||
tp, fn, err := NewExportPipeline(
|
||||
WithCollectorEndpoint("http://localhost:14268/api/traces"),
|
||||
WithDisabled(true),
|
||||
)
|
||||
defer fn()
|
||||
assert.NoError(t, err)
|
||||
assert.IsType(t, &apitrace.NoopProvider{}, tp)
|
||||
}
|
||||
|
||||
func TestNewExporterPipelineWithDisabledFromEnv(t *testing.T) {
|
||||
envStore, err := ottest.SetEnvVariables(map[string]string{
|
||||
envDisabled: "true",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
envStore.Record(envDisabled)
|
||||
defer func() {
|
||||
require.NoError(t, envStore.Restore())
|
||||
}()
|
||||
|
||||
tp, fn, err := NewExportPipeline(
|
||||
WithCollectorEndpoint("http://localhost:14268/api/traces"),
|
||||
)
|
||||
defer fn()
|
||||
assert.NoError(t, err)
|
||||
assert.IsType(t, &apitrace.NoopProvider{}, tp)
|
||||
}
|
||||
|
@ -55,6 +55,11 @@ func WithAgentEndpoint(agentEndpoint string) EndpointOption {
|
||||
// For example, http://localhost:14268/api/traces
|
||||
func WithCollectorEndpoint(collectorEndpoint string, options ...CollectorEndpointOption) EndpointOption {
|
||||
return func() (batchUploader, error) {
|
||||
// Overwrite collector endpoint if environment variables are available.
|
||||
if e := CollectorEndpointFromEnv(); e != "" {
|
||||
collectorEndpoint = e
|
||||
}
|
||||
|
||||
if collectorEndpoint == "" {
|
||||
return nil, errors.New("collectorEndpoint must not be empty")
|
||||
}
|
||||
@ -62,6 +67,8 @@ func WithCollectorEndpoint(collectorEndpoint string, options ...CollectorEndpoin
|
||||
o := &CollectorEndpointOptions{
|
||||
httpClient: http.DefaultClient,
|
||||
}
|
||||
|
||||
options = append(options, WithCollectorEndpointOptionFromEnv())
|
||||
for _, opt := range options {
|
||||
opt(o)
|
||||
}
|
||||
|
98
internal/testing/env.go
Normal file
98
internal/testing/env.go
Normal file
@ -0,0 +1,98 @@
|
||||
// Copyright The OpenTelemetry Authors
|
||||
//
|
||||
// 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 testing
|
||||
|
||||
import (
|
||||
"os"
|
||||
)
|
||||
|
||||
type Env struct {
|
||||
Name string
|
||||
Value string
|
||||
Exists bool
|
||||
}
|
||||
|
||||
// EnvStore stores and recovers environment variables.
|
||||
type EnvStore interface {
|
||||
// Records the environment variable into the store.
|
||||
Record(key string)
|
||||
|
||||
// Restore recover the environment variables in the store.
|
||||
Restore() error
|
||||
}
|
||||
|
||||
var _ EnvStore = (*envStore)(nil)
|
||||
|
||||
type envStore struct {
|
||||
store map[string]Env
|
||||
}
|
||||
|
||||
func (s *envStore) add(env Env) {
|
||||
s.store[env.Name] = env
|
||||
}
|
||||
|
||||
func (s *envStore) Restore() error {
|
||||
var err error
|
||||
for _, v := range s.store {
|
||||
if v.Exists {
|
||||
err = os.Setenv(v.Name, v.Value)
|
||||
} else {
|
||||
err = os.Unsetenv(v.Name)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *envStore) setEnv(key, value string) error {
|
||||
s.Record(key)
|
||||
|
||||
err := os.Setenv(key, value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *envStore) Record(key string) {
|
||||
originValue, exists := os.LookupEnv(key)
|
||||
s.add(Env{
|
||||
Name: key,
|
||||
Value: originValue,
|
||||
Exists: exists,
|
||||
})
|
||||
}
|
||||
|
||||
func NewEnvStore() EnvStore {
|
||||
return newEnvStore()
|
||||
}
|
||||
|
||||
func newEnvStore() *envStore {
|
||||
return &envStore{store: make(map[string]Env)}
|
||||
}
|
||||
|
||||
func SetEnvVariables(env map[string]string) (EnvStore, error) {
|
||||
envStore := newEnvStore()
|
||||
|
||||
for k, v := range env {
|
||||
err := envStore.setEnv(k, v)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return envStore, nil
|
||||
}
|
234
internal/testing/env_test.go
Normal file
234
internal/testing/env_test.go
Normal file
@ -0,0 +1,234 @@
|
||||
// Copyright The OpenTelemetry Authors
|
||||
//
|
||||
// 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 testing
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/stretchr/testify/suite"
|
||||
)
|
||||
|
||||
type EnvStoreTestSuite struct {
|
||||
suite.Suite
|
||||
}
|
||||
|
||||
func (s *EnvStoreTestSuite) Test_add() {
|
||||
envStore := newEnvStore()
|
||||
|
||||
e := Env{
|
||||
Name: "name",
|
||||
Value: "value",
|
||||
Exists: true,
|
||||
}
|
||||
envStore.add(e)
|
||||
envStore.add(e)
|
||||
|
||||
s.Assert().Len(envStore.store, 1)
|
||||
}
|
||||
|
||||
func (s *EnvStoreTestSuite) TestRecord() {
|
||||
testCases := []struct {
|
||||
name string
|
||||
env Env
|
||||
expectedEnvStore *envStore
|
||||
}{
|
||||
{
|
||||
name: "record exists env",
|
||||
env: Env{
|
||||
Name: "name",
|
||||
Value: "value",
|
||||
Exists: true,
|
||||
},
|
||||
expectedEnvStore: &envStore{store: map[string]Env{
|
||||
"name": {
|
||||
Name: "name",
|
||||
Value: "value",
|
||||
Exists: true,
|
||||
},
|
||||
}},
|
||||
},
|
||||
{
|
||||
name: "record exists env, but its value is empty",
|
||||
env: Env{
|
||||
Name: "name",
|
||||
Value: "",
|
||||
Exists: true,
|
||||
},
|
||||
expectedEnvStore: &envStore{store: map[string]Env{
|
||||
"name": {
|
||||
Name: "name",
|
||||
Value: "",
|
||||
Exists: true,
|
||||
},
|
||||
}},
|
||||
},
|
||||
{
|
||||
name: "record not exists env",
|
||||
env: Env{
|
||||
Name: "name",
|
||||
Exists: false,
|
||||
},
|
||||
expectedEnvStore: &envStore{store: map[string]Env{
|
||||
"name": {
|
||||
Name: "name",
|
||||
Exists: false,
|
||||
},
|
||||
}},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
s.Run(tc.name, func() {
|
||||
if tc.env.Exists {
|
||||
s.Assert().NoError(os.Setenv(tc.env.Name, tc.env.Value))
|
||||
}
|
||||
|
||||
envStore := newEnvStore()
|
||||
envStore.Record(tc.env.Name)
|
||||
|
||||
s.Assert().Equal(tc.expectedEnvStore, envStore)
|
||||
|
||||
if tc.env.Exists {
|
||||
s.Assert().NoError(os.Unsetenv(tc.env.Name))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (s *EnvStoreTestSuite) TestRestore() {
|
||||
testCases := []struct {
|
||||
name string
|
||||
env Env
|
||||
expectedEnvValue string
|
||||
expectedEnvExists bool
|
||||
}{
|
||||
{
|
||||
name: "exists env",
|
||||
env: Env{
|
||||
Name: "name",
|
||||
Value: "value",
|
||||
Exists: true,
|
||||
},
|
||||
expectedEnvValue: "value",
|
||||
expectedEnvExists: true,
|
||||
},
|
||||
{
|
||||
name: "no exists env",
|
||||
env: Env{
|
||||
Name: "name",
|
||||
Exists: false,
|
||||
},
|
||||
expectedEnvExists: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
s.Run(tc.name, func() {
|
||||
envStore := newEnvStore()
|
||||
envStore.add(tc.env)
|
||||
|
||||
// Backup
|
||||
backup := newEnvStore()
|
||||
backup.Record(tc.env.Name)
|
||||
|
||||
s.Require().NoError(os.Unsetenv(tc.env.Name))
|
||||
|
||||
s.Assert().NoError(envStore.Restore())
|
||||
v, exists := os.LookupEnv(tc.env.Name)
|
||||
s.Assert().Equal(tc.expectedEnvValue, v)
|
||||
s.Assert().Equal(tc.expectedEnvExists, exists)
|
||||
|
||||
// Restore
|
||||
s.Require().NoError(backup.Restore())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (s *EnvStoreTestSuite) Test_setEnv() {
|
||||
testCases := []struct {
|
||||
name string
|
||||
key string
|
||||
value string
|
||||
expectedEnvStore *envStore
|
||||
expectedEnvValue string
|
||||
expectedEnvExists bool
|
||||
}{
|
||||
{
|
||||
name: "normal",
|
||||
key: "name",
|
||||
value: "value",
|
||||
expectedEnvStore: &envStore{store: map[string]Env{
|
||||
"name": {
|
||||
Name: "name",
|
||||
Value: "other value",
|
||||
Exists: true,
|
||||
},
|
||||
}},
|
||||
expectedEnvValue: "value",
|
||||
expectedEnvExists: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
s.Run(tc.name, func() {
|
||||
envStore := newEnvStore()
|
||||
|
||||
// Backup
|
||||
backup := newEnvStore()
|
||||
backup.Record(tc.key)
|
||||
|
||||
s.Require().NoError(os.Setenv(tc.key, "other value"))
|
||||
|
||||
s.Assert().NoError(envStore.setEnv(tc.key, tc.value))
|
||||
s.Assert().Equal(tc.expectedEnvStore, envStore)
|
||||
v, exists := os.LookupEnv(tc.key)
|
||||
s.Assert().Equal(tc.expectedEnvValue, v)
|
||||
s.Assert().Equal(tc.expectedEnvExists, exists)
|
||||
|
||||
// Restore
|
||||
s.Require().NoError(backup.Restore())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestEnvStoreTestSuite(t *testing.T) {
|
||||
suite.Run(t, new(EnvStoreTestSuite))
|
||||
}
|
||||
|
||||
func TestSetEnvVariables(t *testing.T) {
|
||||
envs := map[string]string{
|
||||
"name1": "value1",
|
||||
"name2": "value2",
|
||||
}
|
||||
|
||||
// Backup
|
||||
backup := newEnvStore()
|
||||
for k := range envs {
|
||||
backup.Record(k)
|
||||
}
|
||||
defer func() {
|
||||
require.NoError(t, backup.Restore())
|
||||
}()
|
||||
|
||||
store, err := SetEnvVariables(envs)
|
||||
assert.NoError(t, err)
|
||||
require.IsType(t, &envStore{}, store)
|
||||
concreteStore := store.(*envStore)
|
||||
assert.Len(t, concreteStore.store, 2)
|
||||
assert.Equal(t, backup, concreteStore)
|
||||
}
|
Loading…
Reference in New Issue
Block a user