github.com/gravitational/teleport/api@v0.0.0-20240507183017-3110591cbafc/observability/tracing/http/http.go (about)

     1  // Copyright 2023 Gravitational, Inc
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package http
    16  
    17  import (
    18  	"net/http"
    19  
    20  	"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
    21  	"go.opentelemetry.io/otel"
    22  	metricnoop "go.opentelemetry.io/otel/metric/noop"
    23  )
    24  
    25  func init() {
    26  	// There's a memory leak in otelhttp caused by the default global
    27  	// delegating meter provider. Here we set the no op meter provider to
    28  	// avoid this.
    29  	//
    30  	// See the upstream issue for more:
    31  	// https://github.com/open-telemetry/opentelemetry-go-contrib/issues/5190
    32  	otel.SetMeterProvider(metricnoop.MeterProvider{})
    33  }
    34  
    35  // TransportFormatter is a span formatter that may be provided to
    36  // [otelhttp.WithSpanNameFormatter] to include the url path in the span
    37  // names generated by an [otelhttp.Transport].
    38  func TransportFormatter(_ string, r *http.Request) string {
    39  	return "HTTP " + r.Method + " " + r.URL.Path
    40  }
    41  
    42  // HandlerFormatter is a span formatter that may be provided to
    43  // [otelhttp.WithSpanNameFormatter] to include the component and url path in the span
    44  // names generated by [otelhttp.NewHandler].
    45  func HandlerFormatter(operation string, r *http.Request) string {
    46  	return operation + " " + r.Method + " " + r.URL.Path
    47  }
    48  
    49  // NewTransport wraps the provided [http.RoundTripper] with one
    50  // that automatically adds spans for each http request.
    51  //
    52  // Note: special care has been taken to ensure that the returned
    53  // [http.RoundTripper] has a CloseIdleConnections method because
    54  // the [otelhttp.Transport] does not implement it:
    55  // https://github.com/open-telemetry/opentelemetry-go-contrib/issues/3543.
    56  // Once the issue is resolved the wrapper may be discarded.
    57  func NewTransport(rt http.RoundTripper) http.RoundTripper {
    58  	return NewTransportWithInner(rt, rt)
    59  }
    60  
    61  // NewTransportWithInner wraps the provided [http.RoundTripper] with one
    62  // that automatically adds spans for each http request.
    63  // The inner round tripper is used to close idle connections when
    64  // rt.CloseIdleConnections isn't implemented for the rt provided.
    65  //
    66  // Note: special care has been taken to ensure that the returned
    67  // [http.RoundTripper] has a CloseIdleConnections method because
    68  // the [otelhttp.Transport] does not implement it:
    69  // https://github.com/open-telemetry/opentelemetry-go-contrib/issues/3543.
    70  // Once the issue is resolved the wrapper may be discarded.
    71  func NewTransportWithInner(rt http.RoundTripper, inner http.RoundTripper) http.RoundTripper {
    72  	return &roundTripWrapper{
    73  		RoundTripper: otelhttp.NewTransport(rt, otelhttp.WithSpanNameFormatter(TransportFormatter)),
    74  		inner:        inner,
    75  	}
    76  }
    77  
    78  type closeIdler interface {
    79  	CloseIdleConnections()
    80  }
    81  
    82  type roundTripWrapper struct {
    83  	http.RoundTripper
    84  	inner http.RoundTripper
    85  }
    86  
    87  // Unwrap returns the inner round tripper.
    88  func (w *roundTripWrapper) Unwrap() http.RoundTripper {
    89  	return w.inner
    90  }
    91  
    92  // CloseIdleConnections ensures that the returned [http.RoundTripper]
    93  // has a CloseIdleConnections method. Since [otelhttp.Transport] does not implement
    94  // this any [http.Client.CloseIdleConnections] calls result in a noop instead
    95  // of forwarding the request onto its wrapped [http.RoundTripper].
    96  //
    97  // DELETE WHEN https://github.com/open-telemetry/opentelemetry-go-contrib/issues/3543
    98  // has been resolved.
    99  func (w *roundTripWrapper) CloseIdleConnections() {
   100  	if c, ok := w.RoundTripper.(closeIdler); ok {
   101  		c.CloseIdleConnections()
   102  	} else if c, ok := w.inner.(closeIdler); ok {
   103  		c.CloseIdleConnections()
   104  	}
   105  }