net/http.Client

HTTP client implementation.

Add field to Transport

Join Point
Definition ofnet/http.Transport
Advice
Add new field named DD__tracer_internal typed as bool.

Flag tracer internal Transport instances

Advice
Replace the expression using the template:
{{- .AST.Type -}}{
	DD__tracer_internal: true,
	{{ range .AST.Elts }}{{ . }},
	{{ end }}
}

Instrument Transport.RoundTrip

Join Point
Function body
Advice
Introduce new declarations:
// Using the following synthetic imports:
import (
	context "context"
	ddtrace "gopkg.in/DataDog/dd-trace-go.v1/ddtrace"
)
//go:linkname __dd_appsec_RASPEnabled gopkg.in/DataDog/dd-trace-go.v1/internal/appsec.RASPEnabled
func __dd_appsec_RASPEnabled() bool

//go:linkname __dd_httpsec_ProtectRoundTrip gopkg.in/DataDog/dd-trace-go.v1/internal/appsec/emitter/httpsec.ProtectRoundTrip
func __dd_httpsec_ProtectRoundTrip(context.Context, string) error

//go:linkname __dd_tracer_SpanType gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer.SpanType
func __dd_tracer_SpanType(string) ddtrace.StartSpanOption

//go:linkname __dd_tracer_ResourceName gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer.ResourceName
func __dd_tracer_ResourceName(string) ddtrace.StartSpanOption

//go:linkname __dd_tracer_Tag gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer.Tag
func __dd_tracer_Tag(string, any) ddtrace.StartSpanOption

//go:linkname __dd_tracer_StartSpanFromContext gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer.StartSpanFromContext
func __dd_tracer_StartSpanFromContext(context.Context, string, ...ddtrace.StartSpanOption) (ddtrace.Span, context.Context)

//go:linkname __dd_tracer_WithError gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer.WithError
func __dd_tracer_WithError(error) ddtrace.FinishOption

//go:linkname __dd_tracer_Inject gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer.Inject
func __dd_tracer_Inject(ddtrace.SpanContext, any) error

type __dd_tracer_HTTPHeadersCarrier Header
func (c __dd_tracer_HTTPHeadersCarrier) Set(key, val string) {
	Header(c).Set(key, val)
}
Advice
Prepend statements produced by the following template:
// Using the following synthetic imports:
import (
	ddtrace      "gopkg.in/DataDog/dd-trace-go.v1/ddtrace"
	events       "gopkg.in/DataDog/dd-trace-go.v1/appsec/events"
	ext          "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/ext"
	fmt          "fmt"
	globalconfig "gopkg.in/DataDog/dd-trace-go.v1/internal/globalconfig"
	math         "math"
	namingschema "gopkg.in/DataDog/dd-trace-go.v1/internal/namingschema"
	os           "os"
	strconv      "strconv"
)
{{- /* Largely copied from https://github.com/DataDog/dd-trace-go/blob/v1.65.0-rc.2/contrib/net/http/roundtripper.go#L28-L104 */ -}}
{{- $t := .Function.Receiver -}}
{{- $req := .Function.Argument 0 -}}
{{- $res := .Function.Result 0 -}}
{{- $err := .Function.Result 1 -}}
if !{{ $t }}.DD__tracer_internal {
	resourceName := fmt.Sprintf("%s %s", {{ $req }}.Method, {{ $req }}.URL.Path)
	spanName := namingschema.OpName(namingschema.HTTPClient)
	// Copy the URL so we don't modify the outgoing request
	url := *{{ $req }}.URL
	url.User = nil // Don't include userinfo in the http.url tag
	opts := []ddtrace.StartSpanOption{
		__dd_tracer_SpanType(ext.SpanTypeHTTP),
		__dd_tracer_ResourceName(resourceName),
		__dd_tracer_Tag(ext.HTTPMethod, {{ $req }}.Method),
		__dd_tracer_Tag(ext.HTTPURL, url.String()),
		__dd_tracer_Tag(ext.Component, "net/http"),
		__dd_tracer_Tag(ext.SpanKind, ext.SpanKindClient),
		__dd_tracer_Tag(ext.NetworkDestinationName, url.Hostname()),
	}
	if analyticsRate := globalconfig.AnalyticsRate(); !math.IsNaN(analyticsRate) {
		opts = append(opts, __dd_tracer_Tag(ext.EventSampleRate, analyticsRate))
	}
	if port, err := strconv.Atoi(url.Port()); err == nil {
		opts = append(opts, __dd_tracer_Tag(ext.NetworkDestinationPort, port))
	}
	span, ctx := __dd_tracer_StartSpanFromContext({{ $req }}.Context(), spanName, opts...)
	{{ $req }} = {{ $req }}.Clone(ctx)
	defer func() {
		if !events.IsSecurityError({{ $err }}) {
			span.Finish(__dd_tracer_WithError({{ $err }}))
		} else {
			span.Finish()
		}
	}()

	if {{ $err }} = __dd_tracer_Inject(span.Context(), __dd_tracer_HTTPHeadersCarrier({{ $req }}.Header)); {{ $err }} != nil {
		fmt.Fprintf(os.Stderr, "contrib/net/http.Roundtrip: failed to inject http headers: %v\n", {{ $err }})
	}

	if __dd_appsec_RASPEnabled() {
		if err := __dd_httpsec_ProtectRoundTrip(ctx, {{ $req }}.URL.String()); err != nil {
			return nil, err
		}
	}

	defer func() {
		if {{ $err }} != nil {
			span.SetTag("http.errors", {{ $err }}.Error())
			span.SetTag(ext.Error, {{ $err }})
		} else {
			span.SetTag(ext.HTTPCode, strconv.Itoa({{ $res }}.StatusCode))
			if {{ $res }}.StatusCode >= 500 && {{ $res}}.StatusCode < 600 {
				// Treat HTTP 5XX as errors
				span.SetTag("http.errors", {{ $res }}.Status)
				span.SetTag(ext.Error, fmt.Errorf("%d: %s", {{ $res }}.StatusCode, StatusText({{ $res }}.StatusCode)))
			}
		}
	}()
}

Wire context through http.Get/Head/Post/PostForm

Join Point
All of
Advice
Replace the expression using the template:
// Using the following synthetic imports:
import (
	instrument "github.com/datadog/orchestrion/instrument/net/http"
)
{{- $ctx := .Function.ArgumentOfType "context.Context" -}}
{{- $req := .Function.ArgumentOfType "*net/http.Request" }}
{{- if $ctx -}}
	instrument.{{ .AST.Fun.Name }}(
		{{ $ctx }},
		{{ range .AST.Args }}{{ . }},
		{{ end }}
	)
{{- else if $req -}}
	instrument.{{ .AST.Fun.Name }}(
		{{ $req }}.Context(),
		{{ range .AST.Args }}{{ . }},
		{{ end }}
	)
{{- else -}}
	{{ . }}
{{- end -}}