github.com/wangyougui/gf/v2@v2.6.5/net/gclient/gclient_tracing.go (about)

     1  // Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
     2  //
     3  // This Source Code Form is subject to the terms of the MIT License.
     4  // If a copy of the MIT was not distributed with this file,
     5  // You can obtain one at https://github.com/wangyougui/gf.
     6  
     7  package gclient
     8  
     9  import (
    10  	"context"
    11  	"fmt"
    12  	"io"
    13  	"net/http"
    14  	"net/http/httptrace"
    15  
    16  	"go.opentelemetry.io/otel"
    17  	"go.opentelemetry.io/otel/attribute"
    18  	"go.opentelemetry.io/otel/codes"
    19  	"go.opentelemetry.io/otel/propagation"
    20  	"go.opentelemetry.io/otel/trace"
    21  
    22  	"github.com/wangyougui/gf/v2"
    23  	"github.com/wangyougui/gf/v2/internal/httputil"
    24  	"github.com/wangyougui/gf/v2/internal/utils"
    25  	"github.com/wangyougui/gf/v2/net/gtrace"
    26  	"github.com/wangyougui/gf/v2/os/gctx"
    27  	"github.com/wangyougui/gf/v2/util/gconv"
    28  )
    29  
    30  const (
    31  	tracingInstrumentName                       = "github.com/wangyougui/gf/v2/net/gclient.Client"
    32  	tracingAttrHttpAddressRemote                = "http.address.remote"
    33  	tracingAttrHttpAddressLocal                 = "http.address.local"
    34  	tracingAttrHttpDnsStart                     = "http.dns.start"
    35  	tracingAttrHttpDnsDone                      = "http.dns.done"
    36  	tracingAttrHttpConnectStart                 = "http.connect.start"
    37  	tracingAttrHttpConnectDone                  = "http.connect.done"
    38  	tracingEventHttpRequest                     = "http.request"
    39  	tracingEventHttpRequestHeaders              = "http.request.headers"
    40  	tracingEventHttpRequestBaggage              = "http.request.baggage"
    41  	tracingEventHttpRequestBody                 = "http.request.body"
    42  	tracingEventHttpResponse                    = "http.response"
    43  	tracingEventHttpResponseHeaders             = "http.response.headers"
    44  	tracingEventHttpResponseBody                = "http.response.body"
    45  	tracingMiddlewareHandled        gctx.StrKey = `MiddlewareClientTracingHandled`
    46  )
    47  
    48  // internalMiddlewareTracing is a client middleware that enables tracing feature using standards of OpenTelemetry.
    49  func internalMiddlewareTracing(c *Client, r *http.Request) (response *Response, err error) {
    50  	var ctx = r.Context()
    51  	// Mark this request is handled by server tracing middleware,
    52  	// to avoid repeated handling by the same middleware.
    53  	if ctx.Value(tracingMiddlewareHandled) != nil {
    54  		return c.Next(r)
    55  	}
    56  
    57  	ctx = context.WithValue(ctx, tracingMiddlewareHandled, 1)
    58  	tr := otel.GetTracerProvider().Tracer(
    59  		tracingInstrumentName,
    60  		trace.WithInstrumentationVersion(gf.VERSION),
    61  	)
    62  	ctx, span := tr.Start(ctx, r.URL.String(), trace.WithSpanKind(trace.SpanKindClient))
    63  	defer span.End()
    64  
    65  	span.SetAttributes(gtrace.CommonLabels()...)
    66  
    67  	// Inject tracing content into http header.
    68  	otel.GetTextMapPropagator().Inject(ctx, propagation.HeaderCarrier(r.Header))
    69  
    70  	// If it is now using default trace provider, it then does no complex tracing jobs.
    71  	if gtrace.IsUsingDefaultProvider() {
    72  		response, err = c.Next(r)
    73  		return
    74  	}
    75  
    76  	// Continue client handler executing.
    77  	response, err = c.Next(
    78  		r.WithContext(
    79  			httptrace.WithClientTrace(
    80  				ctx, newClientTrace(ctx, span, r),
    81  			),
    82  		),
    83  	)
    84  	if err != nil {
    85  		span.SetStatus(codes.Error, fmt.Sprintf(`%+v`, err))
    86  	}
    87  	if response == nil || response.Response == nil {
    88  		return
    89  	}
    90  
    91  	reqBodyContentBytes, _ := io.ReadAll(response.Body)
    92  	response.Body = utils.NewReadCloser(reqBodyContentBytes, false)
    93  
    94  	resBodyContent, err := gtrace.SafeContentForHttp(reqBodyContentBytes, response.Header)
    95  	if err != nil {
    96  		span.SetStatus(codes.Error, fmt.Sprintf(`converting safe content failed: %s`, err.Error()))
    97  	}
    98  
    99  	span.AddEvent(tracingEventHttpResponse, trace.WithAttributes(
   100  		attribute.String(tracingEventHttpResponseHeaders, gconv.String(httputil.HeaderToMap(response.Header))),
   101  		attribute.String(tracingEventHttpResponseBody, resBodyContent),
   102  	))
   103  	return
   104  }