diff --git a/CHANGELOG.md b/CHANGELOG.md index 53aae42bb6c1..68004acdbc5b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -43,6 +43,7 @@ - `pkg/translator/prometheusremotewrite`: Fix data race when used with other exporters (#9736) - `examples/demo`: fix baggage not work in trace demo app. (#9418) - `prometheusreceiver`: Handle the condition where `up` metric value is NaN (#9253) +- `tanzuobservabilityexporter`: Make metrics stanza in config be optional (#9098) ## v0.50.0 diff --git a/exporter/tanzuobservabilityexporter/README.md b/exporter/tanzuobservabilityexporter/README.md index 13711c76418a..b7a5af1f4425 100644 --- a/exporter/tanzuobservabilityexporter/README.md +++ b/exporter/tanzuobservabilityexporter/README.md @@ -1,39 +1,17 @@ # Tanzu Observability (Wavefront) Exporter -This exporter supports sending traces to [Tanzu Observability](https://tanzu.vmware.com/observability). +This exporter supports sending metrics and traces to [Tanzu Observability](https://tanzu.vmware.com/observability). ## Prerequisites - [Obtain the Tanzu Observability by Wavefront API token.](https://docs.wavefront.com/wavefront_api.html#generating-an-api-token) - [Set up and start a Tanzu Observability by Wavefront proxy](https://docs.wavefront.com/proxies_installing.html) and configure it with the API token you obtained. -- To have the proxy generate [span RED metrics](https://docs.wavefront.com/trace_data_details.html#red-metrics) from trace data, [configure](https://docs.wavefront.com/proxies_configuring.html) the proxy's `customTracingListenerPorts` and use it for the exporter's endpoint. - -## Data Conversion - -- Trace IDs and Span IDs are converted to UUIDs. For example, span IDs are left-padded with zeros to fit the correct size. -- Events are converted to [Span Logs](https://docs.wavefront.com/trace_data_details.html#span-logs). -- Kind is converted to the `span.kind` tag. -- Status is converted to `error`, `status.code` and `status.message` tags. -- TraceState is converted to the `w3c.tracestate` tag. - -## Tanzu Observability Specific Attributes - -- Application identity tags, which are [required by Tanzu Observability](https://docs.wavefront.com/trace_data_details.html#how-wavefront-uses-application-tags), are added if they are missing. - - `application` is set to "defaultApp". - - `service` is set to "defaultService". -- A `source` field is required in a [Tanzu Observability Span](https://docs.wavefront.com/trace_data_details.html#span-fields) and [Tanzu Observability Metrics](https://docs.wavefront.com/wavefront_data_format.html#wavefront-data-format-fields). The `source` is set to the first matching OpenTelemetry Resource Attribute from the list below. The matched Attribute is excluded from the resulting Tanzu Observability Span/Metrics tags to reduce duplicate data. If none of the Attributes exist on the Resource, the hostname of the OpenTelemetry Collector is used as a default for `source`. - 1. `source` - 2. `host.name` - 3. `hostname` - 4. `host.id` +- To have the proxy generate [span RED metrics](https://docs.wavefront.com/trace_data_details.html#red-metrics) from trace data, [configure](https://docs.wavefront.com/proxies_configuring.html) the proxy to receive traces by setting `customTracingListenerPorts=30001`. For metrics, the proxy listens on port 2878 by default. ## Configuration -The only required configuration is a Wavefront proxy API endpoint to receive traces from the Tanzu Observability Exporter. - -Given a Wavefront proxy at `10.10.10.10`, configured with `customTracingListenerPorts` set to `30001`, the traces endpoint would be `http://10.10.10.10:30001`. - -### Example Configuration +Given a Wavefront proxy at 10.10.10.10 configured with `customTracingListenerPorts=30001`, a basic configuration of +the Tanzu Observability exporter follows: ```yaml receivers: @@ -47,6 +25,8 @@ exporters: tanzuobservability: traces: endpoint: "http://10.10.10.10:30001" + metrics: + endpoint: "http://10.10.10.10:2878" service: pipelines: @@ -54,54 +34,63 @@ service: receivers: [examplereceiver] processors: [batch] exporters: [tanzuobservability] + metrics: + receivers: [examplereceiver] + processors: [batch] + exporters: [tanzuobservability] ``` -### Advanced Configuration +## Advanced Configuration -#### Processors +### Resource Attributes on Metrics -The memory limiter processor is used to prevent out of memory situations on the collector. It allows performing periodic -checks of memory usage – if it exceeds defined limits it will begin dropping data and forcing garbage collection to -reduce memory -consumption. [Details and defaults here](https://github.com/open-telemetry/opentelemetry-collector/blob/main/processor/memorylimiterprocessor/README.md) . +Client programs using an OpenTelemetry SDK can be configured to wrap all emitted telemetry (metrics, spans, logs) with +a set of global key-value pairs, called [resource attributes](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/resource/sdk.md). +By default, the Tanzu Observability Exporter includes resource attributes on spans but _excludes_ them on metrics. To +include resource attributes as tags on metrics, configure the `resource_attributes` stanza per the example below. -**NOTE:** The order matters when enabling multiple processors in a pipeline (e.g. the memory limiter and batch processors in the example config below). Please refer to the processors' [documentation](https://github.com/open-telemetry/opentelemetry-collector/tree/main/processor) for more information. +**Note:** Tanzu Observability has a 254-character limit on tag key-value pairs. If a resource attribute exceeds this +limit, the metric will not show up in Tanzu Observability. -#### Exporter +### Queuing and Retries -This exporter -uses [queuing and retry helpers](https://github.com/open-telemetry/opentelemetry-collector/blob/main/exporter/exporterhelper/README.md) -provided by the core OpenTelemetry Collector. The `retry_on_failure` and `sending_queue` features are enabled by -default, but can be disabled using the options below. +This exporter uses OpenTelemetry Collector helpers to queue data and retry on failures. -* `retry_on_failure` [Details and defaults here](https://github.com/open-telemetry/opentelemetry-collector/blob/main/exporter/exporterhelper/README.md#configuration) - . Enabled by default. - * `enabled` - * `initial_interval` - * `max_interval` - * `max_elapsed_time` +* `retry_on_failure` [Details and defaults here](https://github.com/open-telemetry/opentelemetry-collector/blob/main/exporter/exporterhelper/README.md#configuration). * `sending_queue` [Details and defaults here](https://github.com/open-telemetry/opentelemetry-collector/blob/main/exporter/exporterhelper/README.md#configuration) - . Enabled by default. - * `enabled` - * `num_consumers` - * `queue_size` + +### Recommended Pipeline Processors + +The memory_limiter processor is recommended to prevent out of memory situations on the collector. It allows performing +periodic checks of memory usage – if it exceeds defined limits it will begin dropping data and forcing garbage +collection to reduce memory consumption. [Details and defaults here](https://github.com/open-telemetry/opentelemetry-collector/blob/main/processor/memorylimiterprocessor/README.md). + +**Note:** The order matters when enabling multiple processors in a pipeline (e.g. the memory limiter and batch +processors in the example config below). Please refer to the processors' [documentation](https://github.com/open-telemetry/opentelemetry-collector/tree/main/processor) +for more information. + +### Example Advanced Configuration ```yaml receivers: examplereceiver: processors: - batch: - timeout: 10s memory_limiter: check_interval: 1s limit_percentage: 50 spike_limit_percentage: 30 + batch: + timeout: 10s exporters: tanzuobservability: traces: endpoint: "http://10.10.10.10:30001" + metrics: + endpoint: "http://10.10.10.10:2878" + resource_attributes: + enabled: true retry_on_failure: max_elapsed_time: 3m sending_queue: @@ -110,7 +99,110 @@ exporters: service: pipelines: traces: - receivers: [ examplereceiver ] - processors: [ memory_limiter, batch ] - exporters: [ tanzuobservability ] + receivers: [examplereceiver] + processors: [memory_limiter, batch] + exporters: [tanzuobservability] + metrics: + receivers: [examplereceiver] + processors: [memory_limiter, batch] + exporters: [tanzuobservability] +``` + +## Attributes Required by Tanzu Observability + +### Source + +A `source` field is required in Tanzu Observability [spans](https://docs.wavefront.com/trace_data_details.html#span-fields) +and [metrics](https://docs.wavefront.com/wavefront_data_format.html#wavefront-data-format-fields). The source is set to the +first matching OpenTelemetry Resource Attribute: + +1. `source` +2. `host.name` +3. `hostname` +4. `host.id` + +To reduce duplicate data, the matched attribute is excluded from the tags on the exported Tanzu Observability span or metric. +If none of the above resource attributes exist, the OpenTelemetry Collector's hostname is used as a fallback for source. + +### Application Identity Tags on Spans + +[Application identity tags](https://docs.wavefront.com/trace_data_details.html#how-wavefront-uses-application-tags) of +`application` and `service` are required for all spans in Tanzu Observability. + +- `application` is set to the value of the attribute `application` on the OpenTelemetry Span or Resource. Default is "defaultApp". +- `service` is set the value of the attribute `service` or `service.name` on the OpenTelemetry Span or Resource. Default is "defaultService". + +## Data Conversion for Traces + +- Trace IDs and Span IDs are converted to UUIDs. For example, span IDs are left-padded with zeros to fit the correct size. +- Events are converted to [Span Logs](https://docs.wavefront.com/trace_data_details.html#span-logs). +- Kind is converted to the `span.kind` tag. +- If a Span's status code is error, a tag of `error=true` is added. If the status also has a description, it's set to `otel.status_description`. +- TraceState is converted to the `w3c.tracestate` tag. + +## Data Conversion for Metrics + +This section describes the process used by the Exporter when converting from +[OpenTelemetry Metrics](https://opentelemetry.io/docs/reference/specification/metrics/datamodel) to +[Tanzu Observability by Wavefront Metrics](https://docs.wavefront.com/metric_types.html). + +| OpenTelemetry Metric Type | Wavefront Metric Type | Notes | +| ------ | ------ | ------ | +| Gauge | Gauge | +| Cumulative Sum | Cumulative Counter | +| Delta Sum | Delta Counter | +| Cumulative Histogram (incl. Exponential) | Cumulative Counters | [Details below](#cumulative-histogram-conversion-incl-exponential). | +| Delta Histogram (incl. Exponential) | Histogram | +| Summary | Gauges | [Details below](#summary-conversion). + +### Cumulative Histogram Conversion (incl. Exponential) + +A cumulative histogram is converted to multiple counter metrics: one counter per bucket in the histogram. Each counter +has a special "le" tag that matches the upper bound of the corresponding bucket. The value of the counter metric is the +sum of the histogram's corresponding bucket and all the buckets before it. + +When working with OpenTelemetry Cumulative Histograms that have been converted to Wavefront Counters, these functions +will be of use: +- [cumulativeHisto()](https://docs.wavefront.com/ts_cumulativeHisto.html) +- [cumulativePercentile()](https://docs.wavefront.com/ts_cumulativePercentile.html) + +#### Example + +Suppose a cumulative histogram named "http.response_times" has +the following buckets and values: + +| Bucket | Value | +| ------ | ----- | +| ≤ 100ms | 5 | +| > 100ms to ≤ 200ms | 20 | +| > 200ms | 100 | + +The exporter sends the following metrics to tanzuobservability: + +| Name | Tags | Value | +| ---- | ---- | ----- | +| http.response_times | le="100" | 5 | +| http.response_times | le="200" | 25 | +| http.response_times | le="+Inf" | 125 | + +#### Example WQL Query on a Cumulative Histogram + +Using the cumulative histogram from the section above, this WQL query will produce a graph showing +the 95th percentile of http response times in the last 15 minutes. + +``` +cumulativePercentile(95, mavg(15m, deriv(sum(ts(http.reponse_times), le)))) ``` +The sum function aggregates the http response times and groups them by the le tag. Since +http.response_times has three buckets, the sum() function will graph three lines, one for each bucket. +deriv() shows the per second rate of change in the three lines from sum. The mavg function averages +the rates of change of the three lines over the last 15 minutes. Since the rates of change are per +second, if you multiply the average rate of change for a bucket by 900, you get the number of new +http requests falling into that bucket in the last 15 minutes. Finally, cumulativePercentile +uses the values of the `le` tags, which are http response times, and linear interpolation of the +bucket counts to estimate the 95th percentile of http.response_times over the last 15 minutes. + +### Summary Conversion + +A summary is converted to multiple gauge metrics: one gauge for every quantile in the summary. A special "quantile" tag +contains avalue between 0 and 1 indicating the quantile for which the value belongs. diff --git a/exporter/tanzuobservabilityexporter/config.go b/exporter/tanzuobservabilityexporter/config.go index 2069ce029f21..f16c83a566d1 100644 --- a/exporter/tanzuobservabilityexporter/config.go +++ b/exporter/tanzuobservabilityexporter/config.go @@ -18,6 +18,7 @@ import ( "errors" "fmt" "net/url" + "strconv" "go.opentelemetry.io/collector/config" "go.opentelemetry.io/collector/config/confighttp" @@ -46,28 +47,55 @@ type Config struct { Metrics MetricsConfig `mapstructure:"metrics"` } +func (c *Config) hasMetricsEndpoint() bool { + return c.Metrics.Endpoint != "" +} + +func (c *Config) hasTracesEndpoint() bool { + return c.Traces.Endpoint != "" +} + +func (c *Config) parseMetricsEndpoint() (hostName string, port int, err error) { + return parseEndpoint(c.Metrics.Endpoint) +} + +func (c *Config) parseTracesEndpoint() (hostName string, port int, err error) { + return parseEndpoint(c.Traces.Endpoint) +} + func (c *Config) Validate() error { - tracesURL, err := parseEndpoint("traces", c.Traces.Endpoint) - if err != nil { - return err + var tracesHostName, metricsHostName string + var err error + if c.hasTracesEndpoint() { + tracesHostName, _, err = c.parseTracesEndpoint() + if err != nil { + return fmt.Errorf("Failed to parse traces.endpoint: %v", err) + } } - metricsURL, err := parseEndpoint("metrics", c.Metrics.Endpoint) - if err != nil { - return err + if c.hasMetricsEndpoint() { + metricsHostName, _, err = c.parseMetricsEndpoint() + if err != nil { + return fmt.Errorf("Failed to parse metrics.endpoint: %v", err) + } } - if tracesURL.Hostname() != metricsURL.Hostname() { + if c.hasTracesEndpoint() && c.hasMetricsEndpoint() && tracesHostName != metricsHostName { return errors.New("host for metrics and traces must be the same") } return nil } -func parseEndpoint(name string, endpoint string) (*url.URL, error) { +func parseEndpoint(endpoint string) (hostName string, port int, err error) { if endpoint == "" { - return nil, fmt.Errorf("A non-empty %s.endpoint is required", name) + return "", 0, errors.New("a non-empty endpoint is required") } u, err := url.Parse(endpoint) if err != nil { - return nil, fmt.Errorf("invalid %s.endpoint %s", name, err) + return "", 0, err + } + port, err = strconv.Atoi(u.Port()) + if err != nil { + return "", 0, errors.New("valid port required") } - return u, nil + hostName = u.Hostname() + return hostName, port, nil } diff --git a/exporter/tanzuobservabilityexporter/config_test.go b/exporter/tanzuobservabilityexporter/config_test.go index 55d95807e2c8..9a13ab130aa2 100644 --- a/exporter/tanzuobservabilityexporter/config_test.go +++ b/exporter/tanzuobservabilityexporter/config_test.go @@ -18,58 +18,20 @@ import ( "testing" "github.com/stretchr/testify/assert" - "go.opentelemetry.io/collector/config" "go.opentelemetry.io/collector/config/confighttp" ) -func TestConfigRequiresNonEmptyEndpoint(t *testing.T) { - c := &Config{ - ExporterSettings: config.ExporterSettings{}, - Traces: TracesConfig{ - HTTPClientSettings: confighttp.HTTPClientSettings{Endpoint: ""}, - }, - Metrics: MetricsConfig{ - HTTPClientSettings: confighttp.HTTPClientSettings{Endpoint: "http://localhost:2878"}, - }, - } - - assert.Error(t, c.Validate()) -} - func TestConfigRequiresValidEndpointUrl(t *testing.T) { c := &Config{ - ExporterSettings: config.ExporterSettings{}, Traces: TracesConfig{ HTTPClientSettings: confighttp.HTTPClientSettings{Endpoint: "http#$%^&#$%&#"}, }, - Metrics: MetricsConfig{ - HTTPClientSettings: confighttp.HTTPClientSettings{Endpoint: "http://localhost:2878"}, - }, } - - assert.Error(t, c.Validate()) -} - -func TestMetricsConfigRequiresNonEmptyEndpoint(t *testing.T) { - c := &Config{ - ExporterSettings: config.ExporterSettings{}, - Traces: TracesConfig{ - HTTPClientSettings: confighttp.HTTPClientSettings{Endpoint: "http://localhost:30001"}, - }, - Metrics: MetricsConfig{ - HTTPClientSettings: confighttp.HTTPClientSettings{Endpoint: ""}, - }, - } - assert.Error(t, c.Validate()) } func TestMetricsConfigRequiresValidEndpointUrl(t *testing.T) { c := &Config{ - ExporterSettings: config.ExporterSettings{}, - Traces: TracesConfig{ - HTTPClientSettings: confighttp.HTTPClientSettings{Endpoint: "http://localhost:30001"}, - }, Metrics: MetricsConfig{ HTTPClientSettings: confighttp.HTTPClientSettings{Endpoint: "http#$%^&#$%&#"}, }, @@ -80,7 +42,6 @@ func TestMetricsConfigRequiresValidEndpointUrl(t *testing.T) { func TestDifferentHostNames(t *testing.T) { c := &Config{ - ExporterSettings: config.ExporterSettings{}, Traces: TracesConfig{ HTTPClientSettings: confighttp.HTTPClientSettings{Endpoint: "http://localhost:30001"}, }, @@ -93,7 +54,6 @@ func TestDifferentHostNames(t *testing.T) { func TestConfigNormal(t *testing.T) { c := &Config{ - ExporterSettings: config.ExporterSettings{}, Traces: TracesConfig{ HTTPClientSettings: confighttp.HTTPClientSettings{Endpoint: "http://localhost:40001"}, }, diff --git a/exporter/tanzuobservabilityexporter/exporter.go b/exporter/tanzuobservabilityexporter/exporter.go index e02723bf1e6a..4feca974f938 100644 --- a/exporter/tanzuobservabilityexporter/exporter.go +++ b/exporter/tanzuobservabilityexporter/exporter.go @@ -18,8 +18,6 @@ import ( "context" "errors" "fmt" - "net/url" - "strconv" "github.com/google/uuid" "github.com/wavefronthq/wavefront-sdk-go/senders" @@ -33,6 +31,7 @@ import ( const ( defaultApplicationName = "defaultApp" defaultServiceName = "defaultService" + defaultMetricsPort = 2878 labelApplication = "application" labelError = "error" labelEventName = "name" @@ -68,22 +67,26 @@ func newTracesExporter(settings component.ExporterCreateSettings, c config.Expor if !ok { return nil, fmt.Errorf("invalid config: %#v", c) } - - endpoint, err := url.Parse(cfg.Traces.Endpoint) + if !cfg.hasTracesEndpoint() { + return nil, fmt.Errorf("traces.endpoint required") + } + tracingHost, tracingPort, err := cfg.parseTracesEndpoint() if err != nil { return nil, fmt.Errorf("failed to parse traces.endpoint: %v", err) } - tracingPort, err := strconv.Atoi(endpoint.Port()) - if err != nil { - // the port is empty, otherwise url.Parse would have failed above - return nil, fmt.Errorf("traces.endpoint requires a port") + metricsPort := defaultMetricsPort + if cfg.hasMetricsEndpoint() { + _, metricsPort, err = cfg.parseMetricsEndpoint() + if err != nil { + return nil, fmt.Errorf("failed to parse metrics.endpoint: %v", err) + } } // we specify a MetricsPort so the SDK can report its internal metrics // but don't currently export any metrics from the pipeline s, err := senders.NewProxySender(&senders.ProxyConfiguration{ - Host: endpoint.Hostname(), - MetricsPort: 2878, + Host: tracingHost, + MetricsPort: metricsPort, TracingPort: tracingPort, FlushIntervalSeconds: 1, SDKMetricsTags: map[string]string{"otel.traces.collector_version": settings.BuildInfo.Version}, diff --git a/exporter/tanzuobservabilityexporter/factory.go b/exporter/tanzuobservabilityexporter/factory.go index 9eefc700236e..c0037de6434a 100644 --- a/exporter/tanzuobservabilityexporter/factory.go +++ b/exporter/tanzuobservabilityexporter/factory.go @@ -20,7 +20,6 @@ import ( "go.opentelemetry.io/collector/component" "go.opentelemetry.io/collector/config" - "go.opentelemetry.io/collector/config/confighttp" "go.opentelemetry.io/collector/exporter/exporterhelper" "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/resourcetotelemetry" @@ -39,18 +38,10 @@ func NewFactory() component.ExporterFactory { } func createDefaultConfig() config.Exporter { - tracesCfg := TracesConfig{ - HTTPClientSettings: confighttp.HTTPClientSettings{Endpoint: "http://localhost:30001"}, - } - metricsCfg := MetricsConfig{ - HTTPClientSettings: confighttp.HTTPClientSettings{Endpoint: "http://localhost:2878"}, - } return &Config{ ExporterSettings: config.NewExporterSettings(config.NewComponentID(exporterType)), QueueSettings: exporterhelper.NewDefaultQueueSettings(), RetrySettings: exporterhelper.NewDefaultRetrySettings(), - Traces: tracesCfg, - Metrics: metricsCfg, } } diff --git a/exporter/tanzuobservabilityexporter/factory_test.go b/exporter/tanzuobservabilityexporter/factory_test.go index f8df9b0ff4b6..3994605c691a 100644 --- a/exporter/tanzuobservabilityexporter/factory_test.go +++ b/exporter/tanzuobservabilityexporter/factory_test.go @@ -39,8 +39,8 @@ func TestCreateDefaultConfig(t *testing.T) { actual, ok := cfg.(*Config) require.True(t, ok, "invalid Config: %#v", cfg) - assert.Equal(t, "http://localhost:30001", actual.Traces.Endpoint) - assert.Equal(t, "http://localhost:2878", actual.Metrics.Endpoint) + assert.False(t, actual.hasMetricsEndpoint()) + assert.False(t, actual.hasTracesEndpoint()) } func TestLoadConfig(t *testing.T) { @@ -84,7 +84,7 @@ func TestCreateExporter(t *testing.T) { defaultConfig := createDefaultConfig() cfg := defaultConfig.(*Config) params := componenttest.NewNopExporterCreateSettings() - + cfg.Traces.Endpoint = "http://localhost:30001" te, err := createTracesExporter(context.Background(), params, cfg) assert.Nil(t, err) assert.NotNil(t, te, "failed to create trace exporter") @@ -94,7 +94,7 @@ func TestCreateMetricsExporter(t *testing.T) { defaultConfig := createDefaultConfig() cfg := defaultConfig.(*Config) params := componenttest.NewNopExporterCreateSettings() - + cfg.Metrics.Endpoint = "http://localhost:2878" te, err := createMetricsExporter(context.Background(), params, cfg) assert.NoError(t, err) assert.NotNil(t, te, "failed to create metrics exporter") diff --git a/exporter/tanzuobservabilityexporter/metrics_exporter.go b/exporter/tanzuobservabilityexporter/metrics_exporter.go index 1f09a624ddab..1289e98c3333 100644 --- a/exporter/tanzuobservabilityexporter/metrics_exporter.go +++ b/exporter/tanzuobservabilityexporter/metrics_exporter.go @@ -17,8 +17,6 @@ package tanzuobservabilityexporter // import "github.com/open-telemetry/opentele import ( "context" "fmt" - "net/url" - "strconv" "github.com/wavefronthq/wavefront-sdk-go/senders" "go.opentelemetry.io/collector/component" @@ -34,6 +32,7 @@ func createMetricsConsumer(hostName string, port int, settings component.Telemet s, err := senders.NewProxySender(&senders.ProxyConfiguration{ Host: hostName, MetricsPort: port, + DistributionPort: port, FlushIntervalSeconds: 1, SDKMetricsTags: map[string]string{"otel.metrics.collector_version": otelVersion}, }) @@ -62,16 +61,14 @@ func newMetricsExporter(settings component.ExporterCreateSettings, c config.Expo if !ok { return nil, fmt.Errorf("invalid config: %#v", c) } - endpoint, err := url.Parse(cfg.Metrics.Endpoint) - if err != nil { - return nil, fmt.Errorf("failed to parse metrics.endpoint: %v", err) + if !cfg.hasMetricsEndpoint() { + return nil, fmt.Errorf("metrics.endpoint required") } - metricsPort, err := strconv.Atoi(endpoint.Port()) + hostName, port, err := cfg.parseMetricsEndpoint() if err != nil { - // The port is empty, otherwise url.Parse would have failed above - return nil, fmt.Errorf("metrics.endpoint requires a port") + return nil, fmt.Errorf("failed to parse metrics.endpoint: %v", err) } - consumer, err := creator(endpoint.Hostname(), metricsPort, settings.TelemetrySettings, settings.BuildInfo.Version) + consumer, err := creator(hostName, port, settings.TelemetrySettings, settings.BuildInfo.Version) if err != nil { return nil, err } diff --git a/exporter/tanzuobservabilityexporter/metrics_exporter_test.go b/exporter/tanzuobservabilityexporter/metrics_exporter_test.go index 67af9f1346dc..37ed68865cef 100644 --- a/exporter/tanzuobservabilityexporter/metrics_exporter_test.go +++ b/exporter/tanzuobservabilityexporter/metrics_exporter_test.go @@ -60,6 +60,8 @@ func verifyPushMetricsData(t *testing.T, errorOnSend bool) error { func createMockMetricsExporter( sender *mockMetricSender) (component.MetricsExporter, error) { cfg := createDefaultConfig() + ourConfig := cfg.(*Config) + ourConfig.Metrics.Endpoint = "http://localhost:2878" creator := func( hostName string, port int, settings component.TelemetrySettings, otelVersion string) (*metricsConsumer, error) { return newMetricsConsumer(