diff --git a/CHANGELOG.md b/CHANGELOG.md index 216d1297f32..e175f445b38 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,10 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm ## [Unreleased] +### Added + +- Add `WithEndpointURL` option to the `exporters/otlp/otlpmetric/otlpmetricgrpc`, `exporters/otlp/otlpmetric/otlpmetrichttp`, `exporters/otlp/otlptrace/otlptracegrpc` and `exporters/otlp/otlptrace/otlptracehttp` packages. (#4808) + ## [1.23.0-rc.1] 2024-01-18 This is a release candidate for the v1.23.0 release. diff --git a/exporters/otlp/otlpmetric/otlpmetricgrpc/client_test.go b/exporters/otlp/otlpmetric/otlpmetricgrpc/client_test.go index 03f07d69991..109d5e5a030 100644 --- a/exporters/otlp/otlpmetric/otlpmetricgrpc/client_test.go +++ b/exporters/otlp/otlpmetric/otlpmetricgrpc/client_test.go @@ -199,6 +199,20 @@ func TestConfig(t *testing.T) { return exp, coll } + t.Run("WithEndpointURL", func(t *testing.T) { + coll, err := otest.NewGRPCCollector("", nil) + require.NoError(t, err) + t.Cleanup(coll.Shutdown) + + ctx := context.Background() + exp, err := New(ctx, WithEndpointURL("http://"+coll.Addr().String())) + require.NoError(t, err) + t.Cleanup(func() { require.NoError(t, exp.Shutdown(ctx)) }) + + assert.NoError(t, exp.Export(ctx, &metricdata.ResourceMetrics{})) + assert.Len(t, coll.Collect().Dump(), 1) + }) + t.Run("WithHeaders", func(t *testing.T) { key := "my-custom-header" headers := map[string]string{key: "custom-value"} diff --git a/exporters/otlp/otlpmetric/otlpmetricgrpc/config.go b/exporters/otlp/otlpmetric/otlpmetricgrpc/config.go index fbd495e7d7d..9269f5a01b7 100644 --- a/exporters/otlp/otlpmetric/otlpmetricgrpc/config.go +++ b/exporters/otlp/otlpmetric/otlpmetricgrpc/config.go @@ -80,6 +80,9 @@ func WithInsecure() Option { // value will be used. If both are set, OTEL_EXPORTER_OTLP_METRICS_ENDPOINT // will take precedence. // +// If both this option and WithEndpointURL are used, the last used option will +// take precedence. +// // By default, if an environment variable is not set, and this option is not // passed, "localhost:4317" will be used. // @@ -88,6 +91,26 @@ func WithEndpoint(endpoint string) Option { return wrappedOption{oconf.WithEndpoint(endpoint)} } +// WithEndpointURL sets the target endpoint URL the Exporter will connect to. +// +// If the OTEL_EXPORTER_OTLP_ENDPOINT or OTEL_EXPORTER_OTLP_METRICS_ENDPOINT +// environment variable is set, and this option is not passed, that variable +// value will be used. If both are set, OTEL_EXPORTER_OTLP_METRICS_ENDPOINT +// will take precedence. +// +// If both this option and WithEndpoint are used, the last used option will +// take precedence. +// +// If an invalid URL is provided, the default value will be kept. +// +// By default, if an environment variable is not set, and this option is not +// passed, "localhost:4317" will be used. +// +// This option has no effect if WithGRPCConn is used. +func WithEndpointURL(u string) Option { + return wrappedOption{oconf.WithEndpointURL(u)} +} + // WithReconnectionPeriod set the minimum amount of time between connection // attempts to the target endpoint. // diff --git a/exporters/otlp/otlpmetric/otlpmetricgrpc/doc.go b/exporters/otlp/otlpmetric/otlpmetricgrpc/doc.go index 7be572a79d0..48e63689e00 100644 --- a/exporters/otlp/otlpmetric/otlpmetricgrpc/doc.go +++ b/exporters/otlp/otlpmetric/otlpmetricgrpc/doc.go @@ -28,7 +28,7 @@ The value may additionally a port, a scheme, and a path. The value accepts "http" and "https" scheme. The value should not contain a query string or fragment. OTEL_EXPORTER_OTLP_METRICS_ENDPOINT takes precedence over OTEL_EXPORTER_OTLP_ENDPOINT. -The configuration can be overridden by [WithEndpoint], [WithInsecure], [WithGRPCConn] options. +The configuration can be overridden by [WithEndpoint], [WithEndpointURL], [WithInsecure], and [WithGRPCConn] options. OTEL_EXPORTER_OTLP_INSECURE, OTEL_EXPORTER_OTLP_METRICS_INSECURE (default: "false") - setting "true" disables client transport security for the exporter's gRPC connection. diff --git a/exporters/otlp/otlpmetric/otlpmetricgrpc/internal/oconf/options.go b/exporters/otlp/otlpmetric/otlpmetricgrpc/internal/oconf/options.go index a85f2712224..e2d99dc22c1 100644 --- a/exporters/otlp/otlpmetric/otlpmetricgrpc/internal/oconf/options.go +++ b/exporters/otlp/otlpmetric/otlpmetricgrpc/internal/oconf/options.go @@ -20,6 +20,7 @@ package oconf // import "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlp import ( "crypto/tls" "fmt" + "net/url" "path" "strings" "time" @@ -31,6 +32,7 @@ import ( "google.golang.org/grpc/encoding/gzip" "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc/internal/retry" + "go.opentelemetry.io/otel/internal/global" "go.opentelemetry.io/otel/sdk/metric" ) @@ -279,6 +281,24 @@ func WithEndpoint(endpoint string) GenericOption { }) } +func WithEndpointURL(v string) GenericOption { + return newGenericOption(func(cfg Config) Config { + u, err := url.Parse(v) + if err != nil { + global.Error(err, "otlpmetric: parse endpoint url", "url", v) + return cfg + } + + cfg.Metrics.Endpoint = u.Host + cfg.Metrics.URLPath = u.Path + if u.Scheme != "https" { + cfg.Metrics.Insecure = true + } + + return cfg + }) +} + func WithCompression(compression Compression) GenericOption { return newGenericOption(func(cfg Config) Config { cfg.Metrics.Compression = compression diff --git a/exporters/otlp/otlpmetric/otlpmetricgrpc/internal/oconf/options_test.go b/exporters/otlp/otlpmetric/otlpmetricgrpc/internal/oconf/options_test.go index 64457efbf08..b4631fb22c0 100644 --- a/exporters/otlp/otlpmetric/otlpmetricgrpc/internal/oconf/options_test.go +++ b/exporters/otlp/otlpmetric/otlpmetricgrpc/internal/oconf/options_test.go @@ -102,6 +102,42 @@ func TestConfigs(t *testing.T) { assert.Equal(t, "someendpoint", c.Metrics.Endpoint) }, }, + { + name: "Test With Endpoint URL", + opts: []GenericOption{ + WithEndpointURL("http://someendpoint/somepath"), + }, + asserts: func(t *testing.T, c *Config, grpcOption bool) { + assert.Equal(t, "someendpoint", c.Metrics.Endpoint) + assert.Equal(t, "/somepath", c.Metrics.URLPath) + assert.Equal(t, true, c.Metrics.Insecure) + }, + }, + { + name: "Test With Secure Endpoint URL", + opts: []GenericOption{ + WithEndpointURL("https://someendpoint/somepath"), + }, + asserts: func(t *testing.T, c *Config, grpcOption bool) { + assert.Equal(t, "someendpoint", c.Metrics.Endpoint) + assert.Equal(t, "/somepath", c.Metrics.URLPath) + assert.Equal(t, false, c.Metrics.Insecure) + }, + }, + { + name: "Test With Invalid Endpoint URL", + opts: []GenericOption{ + WithEndpointURL("%invalid"), + }, + asserts: func(t *testing.T, c *Config, grpcOption bool) { + if grpcOption { + assert.Equal(t, "localhost:4317", c.Metrics.Endpoint) + } else { + assert.Equal(t, "localhost:4318", c.Metrics.Endpoint) + } + assert.Equal(t, "/v1/metrics", c.Metrics.URLPath) + }, + }, { name: "Test Environment Endpoint", env: map[string]string{ diff --git a/exporters/otlp/otlpmetric/otlpmetrichttp/client_test.go b/exporters/otlp/otlpmetric/otlpmetrichttp/client_test.go index a4ead01c1f1..f631c49fe2e 100644 --- a/exporters/otlp/otlpmetric/otlpmetrichttp/client_test.go +++ b/exporters/otlp/otlpmetric/otlpmetrichttp/client_test.go @@ -107,6 +107,20 @@ func TestConfig(t *testing.T) { return exp, coll } + t.Run("WithEndpointURL", func(t *testing.T) { + coll, err := otest.NewHTTPCollector("", nil) + require.NoError(t, err) + ctx := context.Background() + + exp, err := New(ctx, WithEndpointURL("http://"+coll.Addr().String())) + require.NoError(t, err) + t.Cleanup(func() { require.NoError(t, coll.Shutdown(ctx)) }) + t.Cleanup(func() { require.NoError(t, exp.Shutdown(ctx)) }) + + assert.NoError(t, exp.Export(ctx, &metricdata.ResourceMetrics{})) + assert.Len(t, coll.Collect().Dump(), 1) + }) + t.Run("WithHeaders", func(t *testing.T) { key := http.CanonicalHeaderKey("my-custom-header") headers := map[string]string{key: "custom-value"} diff --git a/exporters/otlp/otlpmetric/otlpmetrichttp/config.go b/exporters/otlp/otlpmetric/otlpmetrichttp/config.go index 6eae3e1bbda..5948e2d9d73 100644 --- a/exporters/otlp/otlpmetric/otlpmetrichttp/config.go +++ b/exporters/otlp/otlpmetric/otlpmetrichttp/config.go @@ -76,6 +76,26 @@ func WithEndpoint(endpoint string) Option { return wrappedOption{oconf.WithEndpoint(endpoint)} } +// WithEndpointURL sets the target endpoint URL the Exporter will connect to. +// +// If the OTEL_EXPORTER_OTLP_ENDPOINT or OTEL_EXPORTER_OTLP_METRICS_ENDPOINT +// environment variable is set, and this option is not passed, that variable +// value will be used. If both are set, OTEL_EXPORTER_OTLP_METRICS_ENDPOINT +// will take precedence. +// +// If both this option and WithEndpoint are used, the last used option will +// take precedence. +// +// If an invalid URL is provided, the default value will be kept. +// +// By default, if an environment variable is not set, and this option is not +// passed, "localhost:4317" will be used. +// +// This option has no effect if WithGRPCConn is used. +func WithEndpointURL(u string) Option { + return wrappedOption{oconf.WithEndpointURL(u)} +} + // WithCompression sets the compression strategy the Exporter will use to // compress the HTTP body. // diff --git a/exporters/otlp/otlpmetric/otlpmetrichttp/doc.go b/exporters/otlp/otlpmetric/otlpmetrichttp/doc.go index 94f8b250d3f..a707b5ebb71 100644 --- a/exporters/otlp/otlpmetric/otlpmetrichttp/doc.go +++ b/exporters/otlp/otlpmetric/otlpmetrichttp/doc.go @@ -26,14 +26,14 @@ The value must contain a scheme ("http" or "https") and host. The value may additionally contain a port and a path. The value should not contain a query string or fragment. The configuration can be overridden by OTEL_EXPORTER_OTLP_METRICS_ENDPOINT -environment variable and by [WithEndpoint], [WithInsecure] options. +environment variable and by [WithEndpoint], [WithEndpointURL], and [WithInsecure] options. OTEL_EXPORTER_OTLP_METRICS_ENDPOINT (default: "https://localhost:4318/v1/metrics") - target URL to which the exporter sends telemetry. The value must contain a scheme ("http" or "https") and host. The value may additionally contain a port and a path. The value should not contain a query string or fragment. -The configuration can be overridden by [WithEndpoint], [WitnInsecure], [WithURLPath] options. +The configuration can be overridden by [WithEndpoint], [WithEndpointURL], [WitnInsecure], and [WithURLPath] options. OTEL_EXPORTER_OTLP_HEADERS, OTEL_EXPORTER_OTLP_METRICS_HEADERS (default: none) - key-value pairs used as headers associated with HTTP requests. diff --git a/exporters/otlp/otlpmetric/otlpmetrichttp/internal/oconf/options.go b/exporters/otlp/otlpmetric/otlpmetrichttp/internal/oconf/options.go index 59468b9a5ed..253539d7a3c 100644 --- a/exporters/otlp/otlpmetric/otlpmetrichttp/internal/oconf/options.go +++ b/exporters/otlp/otlpmetric/otlpmetrichttp/internal/oconf/options.go @@ -20,6 +20,7 @@ package oconf // import "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlp import ( "crypto/tls" "fmt" + "net/url" "path" "strings" "time" @@ -31,6 +32,7 @@ import ( "google.golang.org/grpc/encoding/gzip" "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp/internal/retry" + "go.opentelemetry.io/otel/internal/global" "go.opentelemetry.io/otel/sdk/metric" ) @@ -279,6 +281,24 @@ func WithEndpoint(endpoint string) GenericOption { }) } +func WithEndpointURL(v string) GenericOption { + return newGenericOption(func(cfg Config) Config { + u, err := url.Parse(v) + if err != nil { + global.Error(err, "otlpmetric: parse endpoint url", "url", v) + return cfg + } + + cfg.Metrics.Endpoint = u.Host + cfg.Metrics.URLPath = u.Path + if u.Scheme != "https" { + cfg.Metrics.Insecure = true + } + + return cfg + }) +} + func WithCompression(compression Compression) GenericOption { return newGenericOption(func(cfg Config) Config { cfg.Metrics.Compression = compression diff --git a/exporters/otlp/otlpmetric/otlpmetrichttp/internal/oconf/options_test.go b/exporters/otlp/otlpmetric/otlpmetrichttp/internal/oconf/options_test.go index 1da4a6f2f63..c20869e60e8 100644 --- a/exporters/otlp/otlpmetric/otlpmetrichttp/internal/oconf/options_test.go +++ b/exporters/otlp/otlpmetric/otlpmetrichttp/internal/oconf/options_test.go @@ -102,6 +102,42 @@ func TestConfigs(t *testing.T) { assert.Equal(t, "someendpoint", c.Metrics.Endpoint) }, }, + { + name: "Test With Endpoint URL", + opts: []GenericOption{ + WithEndpointURL("http://someendpoint/somepath"), + }, + asserts: func(t *testing.T, c *Config, grpcOption bool) { + assert.Equal(t, "someendpoint", c.Metrics.Endpoint) + assert.Equal(t, "/somepath", c.Metrics.URLPath) + assert.Equal(t, true, c.Metrics.Insecure) + }, + }, + { + name: "Test With Secure Endpoint URL", + opts: []GenericOption{ + WithEndpointURL("https://someendpoint/somepath"), + }, + asserts: func(t *testing.T, c *Config, grpcOption bool) { + assert.Equal(t, "someendpoint", c.Metrics.Endpoint) + assert.Equal(t, "/somepath", c.Metrics.URLPath) + assert.Equal(t, false, c.Metrics.Insecure) + }, + }, + { + name: "Test With Invalid Endpoint URL", + opts: []GenericOption{ + WithEndpointURL("%invalid"), + }, + asserts: func(t *testing.T, c *Config, grpcOption bool) { + if grpcOption { + assert.Equal(t, "localhost:4317", c.Metrics.Endpoint) + } else { + assert.Equal(t, "localhost:4318", c.Metrics.Endpoint) + } + assert.Equal(t, "/v1/metrics", c.Metrics.URLPath) + }, + }, { name: "Test Environment Endpoint", env: map[string]string{ diff --git a/exporters/otlp/otlptrace/otlptracegrpc/client_test.go b/exporters/otlp/otlptrace/otlptracegrpc/client_test.go index aa531eb926e..d0d06506d06 100644 --- a/exporters/otlp/otlptrace/otlptracegrpc/client_test.go +++ b/exporters/otlp/otlptrace/otlptracegrpc/client_test.go @@ -97,6 +97,24 @@ func TestNewEndToEnd(t *testing.T) { } } +func TestWithEndpointURL(t *testing.T) { + mc := runMockCollector(t) + + ctx := context.Background() + exp := newGRPCExporter(t, ctx, "", []otlptracegrpc.Option{ + otlptracegrpc.WithEndpointURL("http://" + mc.endpoint), + }...) + t.Cleanup(func() { + ctx, cancel := contextWithTimeout(ctx, t, 10*time.Second) + defer cancel() + + require.NoError(t, exp.Shutdown(ctx)) + }) + + // RunEndToEndTest closes mc. + otlptracetest.RunEndToEndTest(ctx, t, exp, mc) +} + func newGRPCExporter(t *testing.T, ctx context.Context, endpoint string, additionalOpts ...otlptracegrpc.Option) *otlptrace.Exporter { opts := []otlptracegrpc.Option{ otlptracegrpc.WithInsecure(), diff --git a/exporters/otlp/otlptrace/otlptracegrpc/doc.go b/exporters/otlp/otlptrace/otlptracegrpc/doc.go index 1f514ef9ea3..a3c2690c5d0 100644 --- a/exporters/otlp/otlptrace/otlptracegrpc/doc.go +++ b/exporters/otlp/otlptrace/otlptracegrpc/doc.go @@ -28,7 +28,7 @@ The value may additionally a port, a scheme, and a path. The value accepts "http" and "https" scheme. The value should not contain a query string or fragment. OTEL_EXPORTER_OTLP_TRACES_ENDPOINT takes precedence over OTEL_EXPORTER_OTLP_ENDPOINT. -The configuration can be overridden by [WithEndpoint], [WithInsecure], [WithGRPCConn] options. +The configuration can be overridden by [WithEndpoint], [WithEndpointURL], [WithInsecure], and [WithGRPCConn] options. OTEL_EXPORTER_OTLP_INSECURE, OTEL_EXPORTER_OTLP_TRACES_INSECURE (default: "false") - setting "true" disables client transport security for the exporter's gRPC connection. diff --git a/exporters/otlp/otlptrace/otlptracegrpc/internal/otlpconfig/options.go b/exporters/otlp/otlptrace/otlptracegrpc/internal/otlpconfig/options.go index dddb1f334de..f0203cbe723 100644 --- a/exporters/otlp/otlptrace/otlptracegrpc/internal/otlpconfig/options.go +++ b/exporters/otlp/otlptrace/otlptracegrpc/internal/otlpconfig/options.go @@ -20,6 +20,7 @@ package otlpconfig // import "go.opentelemetry.io/otel/exporters/otlp/otlptrace/ import ( "crypto/tls" "fmt" + "net/url" "path" "strings" "time" @@ -32,6 +33,7 @@ import ( "go.opentelemetry.io/otel/exporters/otlp/otlptrace" "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc/internal/retry" + "go.opentelemetry.io/otel/internal/global" ) const ( @@ -265,6 +267,24 @@ func WithEndpoint(endpoint string) GenericOption { }) } +func WithEndpointURL(v string) GenericOption { + return newGenericOption(func(cfg Config) Config { + u, err := url.Parse(v) + if err != nil { + global.Error(err, "otlptrace: parse endpoint url", "url", v) + return cfg + } + + cfg.Traces.Endpoint = u.Host + cfg.Traces.URLPath = u.Path + if u.Scheme != "https" { + cfg.Traces.Insecure = true + } + + return cfg + }) +} + func WithCompression(compression Compression) GenericOption { return newGenericOption(func(cfg Config) Config { cfg.Traces.Compression = compression diff --git a/exporters/otlp/otlptrace/otlptracegrpc/internal/otlpconfig/options_test.go b/exporters/otlp/otlptrace/otlptracegrpc/internal/otlpconfig/options_test.go index 567f12f1018..254d38bef49 100644 --- a/exporters/otlp/otlptrace/otlptracegrpc/internal/otlpconfig/options_test.go +++ b/exporters/otlp/otlptrace/otlptracegrpc/internal/otlpconfig/options_test.go @@ -100,6 +100,42 @@ func TestConfigs(t *testing.T) { assert.Equal(t, "someendpoint", c.Traces.Endpoint) }, }, + { + name: "Test With Endpoint URL", + opts: []GenericOption{ + WithEndpointURL("http://someendpoint/somepath"), + }, + asserts: func(t *testing.T, c *Config, grpcOption bool) { + assert.Equal(t, "someendpoint", c.Traces.Endpoint) + assert.Equal(t, "/somepath", c.Traces.URLPath) + assert.Equal(t, true, c.Traces.Insecure) + }, + }, + { + name: "Test With Secure Endpoint URL", + opts: []GenericOption{ + WithEndpointURL("https://someendpoint/somepath"), + }, + asserts: func(t *testing.T, c *Config, grpcOption bool) { + assert.Equal(t, "someendpoint", c.Traces.Endpoint) + assert.Equal(t, "/somepath", c.Traces.URLPath) + assert.Equal(t, false, c.Traces.Insecure) + }, + }, + { + name: "Test With Invalid Endpoint URL", + opts: []GenericOption{ + WithEndpointURL("%invalid"), + }, + asserts: func(t *testing.T, c *Config, grpcOption bool) { + if grpcOption { + assert.Equal(t, "localhost:4317", c.Traces.Endpoint) + } else { + assert.Equal(t, "localhost:4318", c.Traces.Endpoint) + } + assert.Equal(t, "/v1/traces", c.Traces.URLPath) + }, + }, { name: "Test Environment Endpoint", env: map[string]string{ diff --git a/exporters/otlp/otlptrace/otlptracegrpc/options.go b/exporters/otlp/otlptrace/otlptracegrpc/options.go index 17ffeaf6ef0..0fddac75895 100644 --- a/exporters/otlp/otlptrace/otlptracegrpc/options.go +++ b/exporters/otlp/otlptrace/otlptracegrpc/options.go @@ -64,14 +64,44 @@ func WithInsecure() Option { return wrappedOption{otlpconfig.WithInsecure()} } -// WithEndpoint sets the target endpoint the exporter will connect to. If -// unset, localhost:4317 will be used as a default. +// WithEndpointURL sets the target endpoint URL the Exporter will connect to. +// +// If the OTEL_EXPORTER_OTLP_ENDPOINT or OTEL_EXPORTER_OTLP_METRICS_ENDPOINT +// environment variable is set, and this option is not passed, that variable +// value will be used. If both are set, OTEL_EXPORTER_OTLP_TRACES_ENDPOINT +// will take precedence. +// +// If both this option and WithEndpointURL are used, the last used option will +// take precedence. +// +// By default, if an environment variable is not set, and this option is not +// passed, "localhost:4317" will be used. // // This option has no effect if WithGRPCConn is used. func WithEndpoint(endpoint string) Option { return wrappedOption{otlpconfig.WithEndpoint(endpoint)} } +// WithEndpoint sets the target endpoint URL the Exporter will connect to. +// +// If the OTEL_EXPORTER_OTLP_ENDPOINT or OTEL_EXPORTER_OTLP_METRICS_ENDPOINT +// environment variable is set, and this option is not passed, that variable +// value will be used. If both are set, OTEL_EXPORTER_OTLP_TRACES_ENDPOINT +// will take precedence. +// +// If both this option and WithEndpoint are used, the last used option will +// take precedence. +// +// If an invalid URL is provided, the default value will be kept. +// +// By default, if an environment variable is not set, and this option is not +// passed, "localhost:4317" will be used. +// +// This option has no effect if WithGRPCConn is used. +func WithEndpointURL(u string) Option { + return wrappedOption{otlpconfig.WithEndpointURL(u)} +} + // WithReconnectionPeriod set the minimum amount of time between connection // attempts to the target endpoint. // diff --git a/exporters/otlp/otlptrace/otlptracehttp/client_test.go b/exporters/otlp/otlptrace/otlptracehttp/client_test.go index 63a4cd4f207..73bd3efaedd 100644 --- a/exporters/otlp/otlptrace/otlptracehttp/client_test.go +++ b/exporters/otlp/otlptrace/otlptracehttp/client_test.go @@ -51,15 +51,20 @@ var ( func TestEndToEnd(t *testing.T) { tests := []struct { - name string - opts []otlptracehttp.Option - mcCfg mockCollectorConfig - tls bool + name string + opts []otlptracehttp.Option + mcCfg mockCollectorConfig + tls bool + withURLEndpoint bool }{ { name: "no extra options", opts: nil, }, + { + name: "with URL endpoint", + withURLEndpoint: true, + }, { name: "with gzip compression", opts: []otlptracehttp.Option{ @@ -162,8 +167,12 @@ func TestEndToEnd(t *testing.T) { t.Run(tc.name, func(t *testing.T) { mc := runMockCollector(t, tc.mcCfg) defer mc.MustStop(t) - allOpts := []otlptracehttp.Option{ - otlptracehttp.WithEndpoint(mc.Endpoint()), + allOpts := []otlptracehttp.Option{} + + if tc.withURLEndpoint { + allOpts = append(allOpts, otlptracehttp.WithEndpointURL("http://"+mc.Endpoint())) + } else { + allOpts = append(allOpts, otlptracehttp.WithEndpoint(mc.Endpoint())) } if tc.tls { tlsConfig := mc.ClientTLSConfig() diff --git a/exporters/otlp/otlptrace/otlptracehttp/doc.go b/exporters/otlp/otlptrace/otlptracehttp/doc.go index 854cc38c8e4..cb4f19ad16b 100644 --- a/exporters/otlp/otlptrace/otlptracehttp/doc.go +++ b/exporters/otlp/otlptrace/otlptracehttp/doc.go @@ -26,14 +26,14 @@ The value must contain a scheme ("http" or "https") and host. The value may additionally contain a port and a path. The value should not contain a query string or fragment. The configuration can be overridden by OTEL_EXPORTER_OTLP_TRACES_ENDPOINT -environment variable and by [WithEndpoint], [WithInsecure] options. +environment variable and by [WithEndpoint], [WithEndpointURL], [WithInsecure] options. OTEL_EXPORTER_OTLP_TRACES_ENDPOINT (default: "https://localhost:4318/v1/traces") - target URL to which the exporter sends telemetry. The value must contain a scheme ("http" or "https") and host. The value may additionally contain a port and a path. The value should not contain a query string or fragment. -The configuration can be overridden by [WithEndpoint], [WitnInsecure], [WithURLPath] options. +The configuration can be overridden by [WithEndpoint], [WithEndpointURL], [WitnInsecure], and [WithURLPath] options. OTEL_EXPORTER_OTLP_HEADERS, OTEL_EXPORTER_OTLP_TRACES_HEADERS (default: none) - key-value pairs used as headers associated with HTTP requests. diff --git a/exporters/otlp/otlptrace/otlptracehttp/internal/otlpconfig/options.go b/exporters/otlp/otlptrace/otlptracehttp/internal/otlpconfig/options.go index 8401bd7f155..3b81641a764 100644 --- a/exporters/otlp/otlptrace/otlptracehttp/internal/otlpconfig/options.go +++ b/exporters/otlp/otlptrace/otlptracehttp/internal/otlpconfig/options.go @@ -20,6 +20,7 @@ package otlpconfig // import "go.opentelemetry.io/otel/exporters/otlp/otlptrace/ import ( "crypto/tls" "fmt" + "net/url" "path" "strings" "time" @@ -32,6 +33,7 @@ import ( "go.opentelemetry.io/otel/exporters/otlp/otlptrace" "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp/internal/retry" + "go.opentelemetry.io/otel/internal/global" ) const ( @@ -265,6 +267,24 @@ func WithEndpoint(endpoint string) GenericOption { }) } +func WithEndpointURL(v string) GenericOption { + return newGenericOption(func(cfg Config) Config { + u, err := url.Parse(v) + if err != nil { + global.Error(err, "otlptrace: parse endpoint url", "url", v) + return cfg + } + + cfg.Traces.Endpoint = u.Host + cfg.Traces.URLPath = u.Path + if u.Scheme != "https" { + cfg.Traces.Insecure = true + } + + return cfg + }) +} + func WithCompression(compression Compression) GenericOption { return newGenericOption(func(cfg Config) Config { cfg.Traces.Compression = compression diff --git a/exporters/otlp/otlptrace/otlptracehttp/internal/otlpconfig/options_test.go b/exporters/otlp/otlptrace/otlptracehttp/internal/otlpconfig/options_test.go index 4c7a525a996..91d59985286 100644 --- a/exporters/otlp/otlptrace/otlptracehttp/internal/otlpconfig/options_test.go +++ b/exporters/otlp/otlptrace/otlptracehttp/internal/otlpconfig/options_test.go @@ -100,6 +100,42 @@ func TestConfigs(t *testing.T) { assert.Equal(t, "someendpoint", c.Traces.Endpoint) }, }, + { + name: "Test With Endpoint URL", + opts: []GenericOption{ + WithEndpointURL("http://someendpoint/somepath"), + }, + asserts: func(t *testing.T, c *Config, grpcOption bool) { + assert.Equal(t, "someendpoint", c.Traces.Endpoint) + assert.Equal(t, "/somepath", c.Traces.URLPath) + assert.Equal(t, true, c.Traces.Insecure) + }, + }, + { + name: "Test With Secure Endpoint URL", + opts: []GenericOption{ + WithEndpointURL("https://someendpoint/somepath"), + }, + asserts: func(t *testing.T, c *Config, grpcOption bool) { + assert.Equal(t, "someendpoint", c.Traces.Endpoint) + assert.Equal(t, "/somepath", c.Traces.URLPath) + assert.Equal(t, false, c.Traces.Insecure) + }, + }, + { + name: "Test With Invalid Endpoint URL", + opts: []GenericOption{ + WithEndpointURL("%invalid"), + }, + asserts: func(t *testing.T, c *Config, grpcOption bool) { + if grpcOption { + assert.Equal(t, "localhost:4317", c.Traces.Endpoint) + } else { + assert.Equal(t, "localhost:4318", c.Traces.Endpoint) + } + assert.Equal(t, "/v1/traces", c.Traces.URLPath) + }, + }, { name: "Test Environment Endpoint", env: map[string]string{ diff --git a/exporters/otlp/otlptrace/otlptracehttp/options.go b/exporters/otlp/otlptrace/otlptracehttp/options.go index e3ed6494c5d..ea6b767a27f 100644 --- a/exporters/otlp/otlptrace/otlptracehttp/options.go +++ b/exporters/otlp/otlptrace/otlptracehttp/options.go @@ -60,15 +60,44 @@ func (w wrappedOption) applyHTTPOption(cfg otlpconfig.Config) otlpconfig.Config return w.ApplyHTTPOption(cfg) } -// WithEndpoint allows one to set the address of the collector -// endpoint that the driver will use to send spans. If -// unset, it will instead try to use -// the default endpoint (localhost:4318). Note that the endpoint -// must not contain any URL path. +// WithEndpointURL sets the target endpoint URL the Exporter will connect to. +// +// If the OTEL_EXPORTER_OTLP_ENDPOINT or OTEL_EXPORTER_OTLP_METRICS_ENDPOINT +// environment variable is set, and this option is not passed, that variable +// value will be used. If both are set, OTEL_EXPORTER_OTLP_TRACES_ENDPOINT +// will take precedence. +// +// If both this option and WithEndpointURL are used, the last used option will +// take precedence. +// +// By default, if an environment variable is not set, and this option is not +// passed, "localhost:4317" will be used. +// +// This option has no effect if WithGRPCConn is used. func WithEndpoint(endpoint string) Option { return wrappedOption{otlpconfig.WithEndpoint(endpoint)} } +// WithEndpoint sets the target endpoint URL the Exporter will connect to. +// +// If the OTEL_EXPORTER_OTLP_ENDPOINT or OTEL_EXPORTER_OTLP_METRICS_ENDPOINT +// environment variable is set, and this option is not passed, that variable +// value will be used. If both are set, OTEL_EXPORTER_OTLP_TRACES_ENDPOINT +// will take precedence. +// +// If both this option and WithEndpoint are used, the last used option will +// take precedence. +// +// If an invalid URL is provided, the default value will be kept. +// +// By default, if an environment variable is not set, and this option is not +// passed, "localhost:4317" will be used. +// +// This option has no effect if WithGRPCConn is used. +func WithEndpointURL(u string) Option { + return wrappedOption{otlpconfig.WithEndpointURL(u)} +} + // WithCompression tells the driver to compress the sent data. func WithCompression(compression Compression) Option { return wrappedOption{otlpconfig.WithCompression(otlpconfig.Compression(compression))} diff --git a/internal/shared/otlp/otlpmetric/oconf/options.go.tmpl b/internal/shared/otlp/otlpmetric/oconf/options.go.tmpl index c4f9d0830f4..8fb706f76c9 100644 --- a/internal/shared/otlp/otlpmetric/oconf/options.go.tmpl +++ b/internal/shared/otlp/otlpmetric/oconf/options.go.tmpl @@ -20,6 +20,7 @@ package oconf import ( "crypto/tls" "fmt" + "net/url" "path" "strings" "time" @@ -31,6 +32,7 @@ import ( "google.golang.org/grpc/encoding/gzip" "{{ .retryImportPath }}" + "go.opentelemetry.io/otel/internal/global" "go.opentelemetry.io/otel/sdk/metric" ) @@ -279,6 +281,24 @@ func WithEndpoint(endpoint string) GenericOption { }) } +func WithEndpointURL(v string) GenericOption { + return newGenericOption(func(cfg Config) Config { + u, err := url.Parse(v) + if err != nil { + global.Error(err, "otlpmetric: parse endpoint url", "url", v) + return cfg + } + + cfg.Metrics.Endpoint = u.Host + cfg.Metrics.URLPath = u.Path + if u.Scheme != "https" { + cfg.Metrics.Insecure = true + } + + return cfg + }) +} + func WithCompression(compression Compression) GenericOption { return newGenericOption(func(cfg Config) Config { cfg.Metrics.Compression = compression diff --git a/internal/shared/otlp/otlpmetric/oconf/options_test.go.tmpl b/internal/shared/otlp/otlpmetric/oconf/options_test.go.tmpl index 7c52efc51d9..abd12c80ca7 100644 --- a/internal/shared/otlp/otlpmetric/oconf/options_test.go.tmpl +++ b/internal/shared/otlp/otlpmetric/oconf/options_test.go.tmpl @@ -102,6 +102,42 @@ func TestConfigs(t *testing.T) { assert.Equal(t, "someendpoint", c.Metrics.Endpoint) }, }, + { + name: "Test With Endpoint URL", + opts: []GenericOption{ + WithEndpointURL("http://someendpoint/somepath"), + }, + asserts: func(t *testing.T, c *Config, grpcOption bool) { + assert.Equal(t, "someendpoint", c.Metrics.Endpoint) + assert.Equal(t, "/somepath", c.Metrics.URLPath) + assert.Equal(t, true, c.Metrics.Insecure) + }, + }, + { + name: "Test With Secure Endpoint URL", + opts: []GenericOption{ + WithEndpointURL("https://someendpoint/somepath"), + }, + asserts: func(t *testing.T, c *Config, grpcOption bool) { + assert.Equal(t, "someendpoint", c.Metrics.Endpoint) + assert.Equal(t, "/somepath", c.Metrics.URLPath) + assert.Equal(t, false, c.Metrics.Insecure) + }, + }, + { + name: "Test With Invalid Endpoint URL", + opts: []GenericOption{ + WithEndpointURL("%invalid"), + }, + asserts: func(t *testing.T, c *Config, grpcOption bool) { + if grpcOption { + assert.Equal(t, "localhost:4317", c.Metrics.Endpoint) + } else { + assert.Equal(t, "localhost:4318", c.Metrics.Endpoint) + } + assert.Equal(t, "/v1/metrics", c.Metrics.URLPath) + }, + }, { name: "Test Environment Endpoint", env: map[string]string{ diff --git a/internal/shared/otlp/otlptrace/otlpconfig/options.go.tmpl b/internal/shared/otlp/otlptrace/otlpconfig/options.go.tmpl index 9270e506a9c..1d5613c44fd 100644 --- a/internal/shared/otlp/otlptrace/otlpconfig/options.go.tmpl +++ b/internal/shared/otlp/otlptrace/otlpconfig/options.go.tmpl @@ -20,6 +20,7 @@ package otlpconfig import ( "crypto/tls" "fmt" + "net/url" "path" "strings" "time" @@ -32,6 +33,7 @@ import ( "go.opentelemetry.io/otel/exporters/otlp/otlptrace" "{{ .retryImportPath }}" + "go.opentelemetry.io/otel/internal/global" ) const ( @@ -265,6 +267,24 @@ func WithEndpoint(endpoint string) GenericOption { }) } +func WithEndpointURL(v string) GenericOption { + return newGenericOption(func(cfg Config) Config { + u, err := url.Parse(v) + if err != nil { + global.Error(err, "otlptrace: parse endpoint url", "url", v) + return cfg + } + + cfg.Traces.Endpoint = u.Host + cfg.Traces.URLPath = u.Path + if u.Scheme != "https" { + cfg.Traces.Insecure = true + } + + return cfg + }) +} + func WithCompression(compression Compression) GenericOption { return newGenericOption(func(cfg Config) Config { cfg.Traces.Compression = compression diff --git a/internal/shared/otlp/otlptrace/otlpconfig/options_test.go.tmpl b/internal/shared/otlp/otlptrace/otlpconfig/options_test.go.tmpl index b83891a89f6..8b614b0c47b 100644 --- a/internal/shared/otlp/otlptrace/otlpconfig/options_test.go.tmpl +++ b/internal/shared/otlp/otlptrace/otlpconfig/options_test.go.tmpl @@ -100,6 +100,42 @@ func TestConfigs(t *testing.T) { assert.Equal(t, "someendpoint", c.Traces.Endpoint) }, }, + { + name: "Test With Endpoint URL", + opts: []GenericOption{ + WithEndpointURL("http://someendpoint/somepath"), + }, + asserts: func(t *testing.T, c *Config, grpcOption bool) { + assert.Equal(t, "someendpoint", c.Traces.Endpoint) + assert.Equal(t, "/somepath", c.Traces.URLPath) + assert.Equal(t, true, c.Traces.Insecure) + }, + }, + { + name: "Test With Secure Endpoint URL", + opts: []GenericOption{ + WithEndpointURL("https://someendpoint/somepath"), + }, + asserts: func(t *testing.T, c *Config, grpcOption bool) { + assert.Equal(t, "someendpoint", c.Traces.Endpoint) + assert.Equal(t, "/somepath", c.Traces.URLPath) + assert.Equal(t, false, c.Traces.Insecure) + }, + }, + { + name: "Test With Invalid Endpoint URL", + opts: []GenericOption{ + WithEndpointURL("%invalid"), + }, + asserts: func(t *testing.T, c *Config, grpcOption bool) { + if grpcOption { + assert.Equal(t, "localhost:4317", c.Traces.Endpoint) + } else { + assert.Equal(t, "localhost:4318", c.Traces.Endpoint) + } + assert.Equal(t, "/v1/traces", c.Traces.URLPath) + }, + }, { name: "Test Environment Endpoint", env: map[string]string{