mirror of
https://github.com/hanzoai/telemetry.git
synced 2026-10-30 20:01:49 +00:00
#### Description This PR exposes the `endpoint` value of the `otlp` and `otlphttp` exporter components to the metrics generated by these exporters. This is useful for operators of the collector to observe data flow to different endpoints across large numbers of collector instances without having to cross-reference the specific configuration per instance. The implementation extends the `exporterhelper` with a new option `WithAttrs` that allows developers of exporters to add extra labels to metrics produced. This allows for more flexibility for implementers of exporters to add attributes that might be relevant in the specific use case. <!--Describe what testing was performed and which tests were added.--> #### Testing * Added tests to ensure attributes passed with the new option are recorded correctly * Tested a build of this branch by configuring the collector to receive OTLP and export it to another collector. The new `endpoint` shows up correctly. <!--Describe the documentation added.--> #### Documentation Assuming this change is accepted, I'll create a PR to update the [internal telemetry](https://opentelemetry.io/docs/collector/internal-telemetry/) page in the OpenTelemetry documentation
210 lines
6.7 KiB
Go
210 lines
6.7 KiB
Go
// Copyright The OpenTelemetry Authors
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
package otlphttpexporter // import "go.opentelemetry.io/collector/exporter/otlphttpexporter"
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"net/url"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"go.opentelemetry.io/otel/attribute"
|
|
semconv "go.opentelemetry.io/otel/semconv/v1.40.0"
|
|
|
|
"go.opentelemetry.io/collector/component"
|
|
"go.opentelemetry.io/collector/config/configcompression"
|
|
"go.opentelemetry.io/collector/config/confighttp"
|
|
"go.opentelemetry.io/collector/config/configoptional"
|
|
"go.opentelemetry.io/collector/config/configretry"
|
|
"go.opentelemetry.io/collector/consumer"
|
|
"go.opentelemetry.io/collector/exporter"
|
|
"go.opentelemetry.io/collector/exporter/exporterhelper"
|
|
"go.opentelemetry.io/collector/exporter/exporterhelper/xexporterhelper"
|
|
"go.opentelemetry.io/collector/exporter/otlphttpexporter/internal/metadata"
|
|
"go.opentelemetry.io/collector/exporter/xexporter"
|
|
)
|
|
|
|
// NewFactory creates a factory for OTLP exporter.
|
|
func NewFactory() exporter.Factory {
|
|
return xexporter.NewFactory(
|
|
metadata.Type,
|
|
createDefaultConfig,
|
|
xexporter.WithDeprecatedTypeAlias(metadata.DeprecatedType),
|
|
xexporter.WithTraces(createTraces, metadata.TracesStability),
|
|
xexporter.WithMetrics(createMetrics, metadata.MetricsStability),
|
|
xexporter.WithLogs(createLogs, metadata.LogsStability),
|
|
xexporter.WithProfiles(createProfiles, metadata.ProfilesStability),
|
|
)
|
|
}
|
|
|
|
func createDefaultConfig() component.Config {
|
|
clientConfig := confighttp.NewDefaultClientConfig()
|
|
clientConfig.Timeout = 30 * time.Second
|
|
// Default to gzip compression
|
|
clientConfig.Compression = configcompression.TypeGzip
|
|
// We almost read 0 bytes, so no need to tune ReadBufferSize.
|
|
clientConfig.WriteBufferSize = 512 * 1024
|
|
|
|
return &Config{
|
|
RetryConfig: configretry.NewDefaultBackOffConfig(),
|
|
QueueConfig: configoptional.Some(exporterhelper.NewDefaultQueueConfig()),
|
|
Encoding: EncodingProto,
|
|
ClientConfig: clientConfig,
|
|
}
|
|
}
|
|
|
|
// composeSignalURL composes the final URL for the signal (traces, metrics, logs) based on the configuration.
|
|
// oCfg is the configuration of the exporter.
|
|
// signalOverrideURL is the URL specified in the signal specific configuration (empty if not specified).
|
|
// signalName is the name of the signal, e.g. "traces", "metrics", "logs".
|
|
// signalVersion is the version of the signal, e.g. "v1" or "v1development".
|
|
func composeSignalURL(oCfg *Config, signalOverrideURL, signalName, signalVersion string) (*url.URL, error) {
|
|
switch {
|
|
case signalOverrideURL != "":
|
|
u, err := url.Parse(signalOverrideURL)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("%s_endpoint must be a valid URL", signalName)
|
|
}
|
|
return u, nil
|
|
case oCfg.ClientConfig.Endpoint == "":
|
|
return nil, fmt.Errorf("either endpoint or %s_endpoint must be specified", signalName)
|
|
default:
|
|
if strings.HasSuffix(oCfg.ClientConfig.Endpoint, "/") {
|
|
return url.Parse(oCfg.ClientConfig.Endpoint + signalVersion + "/" + signalName)
|
|
}
|
|
return url.Parse(oCfg.ClientConfig.Endpoint + "/" + signalVersion + "/" + signalName)
|
|
}
|
|
}
|
|
|
|
func endpointAttributes(endpointURL *url.URL) []attribute.KeyValue {
|
|
out := []attribute.KeyValue{
|
|
semconv.ServerAddress(endpointURL.Hostname()),
|
|
}
|
|
if endpointURL.Path != "" {
|
|
out = append(out, semconv.URLPath(endpointURL.Path))
|
|
}
|
|
if portNumber, err := strconv.Atoi(endpointURL.Port()); err == nil {
|
|
out = append(out, semconv.ServerPort(portNumber))
|
|
}
|
|
return out
|
|
}
|
|
|
|
func createTraces(
|
|
ctx context.Context,
|
|
set exporter.Settings,
|
|
cfg component.Config,
|
|
) (exporter.Traces, error) {
|
|
oce, err := newExporter(cfg, set)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
oCfg := cfg.(*Config)
|
|
|
|
endpointURL, err := composeSignalURL(oCfg, oCfg.TracesEndpoint, "traces", "v1")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
oce.tracesURL = endpointURL.String()
|
|
|
|
return exporterhelper.NewTraces(ctx, set, cfg,
|
|
oce.pushTraces,
|
|
exporterhelper.WithStart(oce.start),
|
|
exporterhelper.WithCapabilities(consumer.Capabilities{MutatesData: false}),
|
|
// explicitly disable since we rely on http.Client timeout logic.
|
|
exporterhelper.WithTimeout(exporterhelper.TimeoutConfig{Timeout: 0}),
|
|
exporterhelper.WithRetry(oCfg.RetryConfig),
|
|
exporterhelper.WithQueue(oCfg.QueueConfig),
|
|
exporterhelper.WithAttrs(endpointAttributes(endpointURL)...),
|
|
)
|
|
}
|
|
|
|
func createMetrics(
|
|
ctx context.Context,
|
|
set exporter.Settings,
|
|
cfg component.Config,
|
|
) (exporter.Metrics, error) {
|
|
oce, err := newExporter(cfg, set)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
oCfg := cfg.(*Config)
|
|
|
|
endpointURL, err := composeSignalURL(oCfg, oCfg.MetricsEndpoint, "metrics", "v1")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
oce.metricsURL = endpointURL.String()
|
|
|
|
return exporterhelper.NewMetrics(ctx, set, cfg,
|
|
oce.pushMetrics,
|
|
exporterhelper.WithStart(oce.start),
|
|
exporterhelper.WithCapabilities(consumer.Capabilities{MutatesData: false}),
|
|
// explicitly disable since we rely on http.Client timeout logic.
|
|
exporterhelper.WithTimeout(exporterhelper.TimeoutConfig{Timeout: 0}),
|
|
exporterhelper.WithRetry(oCfg.RetryConfig),
|
|
exporterhelper.WithQueue(oCfg.QueueConfig),
|
|
exporterhelper.WithAttrs(endpointAttributes(endpointURL)...),
|
|
)
|
|
}
|
|
|
|
func createLogs(
|
|
ctx context.Context,
|
|
set exporter.Settings,
|
|
cfg component.Config,
|
|
) (exporter.Logs, error) {
|
|
oce, err := newExporter(cfg, set)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
oCfg := cfg.(*Config)
|
|
|
|
endpointURL, err := composeSignalURL(oCfg, oCfg.LogsEndpoint, "logs", "v1")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
oce.logsURL = endpointURL.String()
|
|
|
|
return exporterhelper.NewLogs(ctx, set, cfg,
|
|
oce.pushLogs,
|
|
exporterhelper.WithStart(oce.start),
|
|
exporterhelper.WithCapabilities(consumer.Capabilities{MutatesData: false}),
|
|
// explicitly disable since we rely on http.Client timeout logic.
|
|
exporterhelper.WithTimeout(exporterhelper.TimeoutConfig{Timeout: 0}),
|
|
exporterhelper.WithRetry(oCfg.RetryConfig),
|
|
exporterhelper.WithQueue(oCfg.QueueConfig),
|
|
exporterhelper.WithAttrs(endpointAttributes(endpointURL)...),
|
|
)
|
|
}
|
|
|
|
func createProfiles(
|
|
ctx context.Context,
|
|
set exporter.Settings,
|
|
cfg component.Config,
|
|
) (xexporter.Profiles, error) {
|
|
oce, err := newExporter(cfg, set)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
oCfg := cfg.(*Config)
|
|
|
|
endpointURL, err := composeSignalURL(oCfg, oCfg.ProfilesEndpoint, "profiles", "v1development")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
oce.profilesURL = endpointURL.String()
|
|
|
|
return xexporterhelper.NewProfiles(ctx, set, cfg,
|
|
oce.pushProfiles,
|
|
exporterhelper.WithStart(oce.start),
|
|
exporterhelper.WithCapabilities(consumer.Capabilities{MutatesData: false}),
|
|
// explicitly disable since we rely on http.Client timeout logic.
|
|
exporterhelper.WithTimeout(exporterhelper.TimeoutConfig{Timeout: 0}),
|
|
exporterhelper.WithRetry(oCfg.RetryConfig),
|
|
exporterhelper.WithQueue(oCfg.QueueConfig),
|
|
exporterhelper.WithAttrs(endpointAttributes(endpointURL)...),
|
|
)
|
|
}
|