From cf8179a3730ec0d384e1e04296f0349ef1396b38 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Paj=C4=85k?= Date: Wed, 7 May 2025 06:31:53 +0200 Subject: [PATCH] otlploghttp: Add WithHTTPClient option (#6688) Towards (for OTLP logs exporter): - https://github.com/open-telemetry/opentelemetry-go/issues/4536 - https://github.com/open-telemetry/opentelemetry-go/issues/5129 - https://github.com/open-telemetry/opentelemetry-go/issues/2632 Per https://github.com/open-telemetry/opentelemetry-go/pull/6362/files#r1978191352 Providing `WithHTTPClient` option allows easy interoperability e.g. with https://pkg.go.dev/golang.org/x/oauth2/clientcredentials#Config.Client and also see https://github.com/open-telemetry/opentelemetry-go/issues/2632. The options is also similar to: https://pkg.go.dev/go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc#WithGRPCConn I favor this over https://github.com/open-telemetry/opentelemetry-go/pull/6686 The option for OTLP span and metrics exporters will be added in separate PRs. --- CHANGELOG.md | 1 + exporters/otlp/otlplog/otlploghttp/client.go | 29 ++++++++++--------- .../otlp/otlplog/otlploghttp/client_test.go | 22 ++++++++++++++ exporters/otlp/otlplog/otlploghttp/config.go | 20 +++++++++++++ 4 files changed, 59 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 687e493e2..8d9befb28 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm The package contains semantic conventions from the `v1.31.0` version of the OpenTelemetry Semantic Conventions. See the [migration documentation](./semconv/v1.31.0/MIGRATION.md) for information on how to upgrade from `go.opentelemetry.io/otel/semconv/v1.30.0`(#6479) - Add `Recording`, `Scope`, and `Record` types in `go.opentelemetry.io/otel/log/logtest`. (#6507) +- Add `WithHTTPClient` option to configure the `http.Client` used by `go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp`. (#6688) ### Removed diff --git a/exporters/otlp/otlplog/otlploghttp/client.go b/exporters/otlp/otlplog/otlploghttp/client.go index 279b4be4f..3af60258a 100644 --- a/exporters/otlp/otlplog/otlploghttp/client.go +++ b/exporters/otlp/otlplog/otlploghttp/client.go @@ -44,20 +44,23 @@ func newNoopClient() *client { // newHTTPClient creates a new HTTP log client. func newHTTPClient(cfg config) (*client, error) { - hc := &http.Client{ - Transport: ourTransport, - Timeout: cfg.timeout.Value, - } - - if cfg.tlsCfg.Value != nil || cfg.proxy.Value != nil { - clonedTransport := ourTransport.Clone() - hc.Transport = clonedTransport - - if cfg.tlsCfg.Value != nil { - clonedTransport.TLSClientConfig = cfg.tlsCfg.Value + hc := cfg.httpClient + if hc == nil { + hc = &http.Client{ + Transport: ourTransport, + Timeout: cfg.timeout.Value, } - if cfg.proxy.Value != nil { - clonedTransport.Proxy = cfg.proxy.Value + + if cfg.tlsCfg.Value != nil || cfg.proxy.Value != nil { + clonedTransport := ourTransport.Clone() + hc.Transport = clonedTransport + + if cfg.tlsCfg.Value != nil { + clonedTransport.TLSClientConfig = cfg.tlsCfg.Value + } + if cfg.proxy.Value != nil { + clonedTransport.Proxy = cfg.proxy.Value + } } } diff --git a/exporters/otlp/otlplog/otlploghttp/client_test.go b/exporters/otlp/otlplog/otlploghttp/client_test.go index 97ae311ca..87f756cd6 100644 --- a/exporters/otlp/otlplog/otlploghttp/client_test.go +++ b/exporters/otlp/otlplog/otlploghttp/client_test.go @@ -783,6 +783,28 @@ func TestConfig(t *testing.T) { assert.Equal(t, []string{headerValueSetInProxy}, got[headerKeySetInProxy]) }) + t.Run("WithHTTPClient", func(t *testing.T) { + headerKeySetInProxy := http.CanonicalHeaderKey("X-Using-Proxy") + headerValueSetInProxy := "true" + exp, coll := factoryFunc("", nil, WithHTTPClient(&http.Client{ + Transport: &http.Transport{ + Proxy: func(r *http.Request) (*url.URL, error) { + r.Header.Set(headerKeySetInProxy, headerValueSetInProxy) + return r.URL, nil + }, + }, + })) + ctx := context.Background() + t.Cleanup(func() { require.NoError(t, coll.Shutdown(ctx)) }) + require.NoError(t, exp.Export(ctx, make([]log.Record, 1))) + // Ensure everything is flushed. + require.NoError(t, exp.Shutdown(ctx)) + + got := coll.Headers() + require.Contains(t, got, headerKeySetInProxy) + assert.Equal(t, []string{headerValueSetInProxy}, got[headerKeySetInProxy]) + }) + t.Run("non-retryable errors are propagated", func(t *testing.T) { exporterErr := errors.New("missing required attribute aaaa") rCh := make(chan exportResult, 1) diff --git a/exporters/otlp/otlplog/otlploghttp/config.go b/exporters/otlp/otlplog/otlploghttp/config.go index b8952272c..66140f3fe 100644 --- a/exporters/otlp/otlplog/otlploghttp/config.go +++ b/exporters/otlp/otlplog/otlploghttp/config.go @@ -95,6 +95,7 @@ type config struct { timeout setting[time.Duration] proxy setting[HTTPTransportProxyFunc] retryCfg setting[retry.Config] + httpClient *http.Client } func newConfig(options []Option) config { @@ -344,6 +345,25 @@ func WithProxy(pf HTTPTransportProxyFunc) Option { }) } +// WithHTTPClient sets the HTTP client to used by the exporter. +// +// This option will take precedence over [WithProxy], [WithTimeout], +// [WithTLSClientConfig] options as well as OTEL_EXPORTER_OTLP_CERTIFICATE, +// OTEL_EXPORTER_OTLP_LOGS_CERTIFICATE, OTEL_EXPORTER_OTLP_TIMEOUT, +// OTEL_EXPORTER_OTLP_LOGS_TIMEOUT environment variables. +// +// Timeout and all other fields of the passed [http.Client] are left intact. +// +// Be aware that passing an HTTP client with transport like +// [go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp.NewTransport] can +// cause the client to be instrumented twice and cause infinite recursion. +func WithHTTPClient(c *http.Client) Option { + return fnOpt(func(cfg config) config { + cfg.httpClient = c + return cfg + }) +} + // setting is a configuration setting value. type setting[T any] struct { Value T