This works for me:
package main
import (
"bytes"
"context"
"fmt"
"io/ioutil"
"log"
"net/http"
"net/http/httptest"
"time"
"github.com/kr/pretty"
"go.elastic.co/apm/module/apmhttp/v2"
"go.elastic.co/apm/v2"
"go.elastic.co/apm/v2/apmtest"
)
var tracingClient = apmhttp.WrapClient(nil)
func HttpCall(ctx context.Context, method, url string, data []byte, header map[string]string) ([]byte, error) {
if ctx != nil {
ctx, _ = context.WithTimeout(ctx, time.Minute)
} else {
ctx, _ = context.WithTimeout(context.TODO(), time.Minute)
}
req, err := http.NewRequestWithContext(ctx, method, url, bytes.NewReader(data))
if err != nil {
return nil, err
}
for k, v := range header {
req.Header.Set(k, v)
}
resp, err := tracingClient.Do(req)
if err != nil {
apm.CaptureError(req.Context(), err).Send()
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("[%s]%s", resp.Status, url)
}
return ioutil.ReadAll(resp.Body)
}
func main() {
tracer := apmtest.NewRecordingTracer()
tracer.SetExitSpanMinDuration(0)
tracer.SetSpanCompressionEnabled(false)
tx := tracer.StartTransaction("tx_name", "tx_type")
ctx := apm.ContextWithTransaction(context.Background(), tx)
ctx, cancel := context.WithCancel(ctx)
dctx := apm.DetachedContext(ctx)
cancel()
tx.End()
// detached context should not be affected by the cancel() above
if err := dctx.Err(); err != nil {
log.Fatal(err)
}
mux := http.NewServeMux()
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("hello"))
})
srv := httptest.NewServer(mux)
defer srv.Close()
data, err := HttpCall(dctx, "GET", srv.URL, nil, nil)
if err != nil {
log.Fatal(err)
}
log.Printf("%s", data)
tracer.Flush(nil)
pretty.Println(tracer.Payloads())
}
transporttest.Payloads{
Errors: nil,
Metrics: nil,
Spans: {
{
Name: "GET 127.0.0.1:46875",
Timestamp: model.Time{
wall: 0x24085d8,
ext: 63805478262,
loc: (*time.Location)(nil),
},
Duration: 0.742258,
Type: "external",
Subtype: "http",
Action: "",
ID: {0x1b, 0xe5, 0xac, 0x1b, 0x75, 0xcc, 0xcd, 0x68},
TransactionID: {0xa9, 0xb1, 0xb0, 0x2c, 0x5c, 0xcc, 0x72, 0x93},
TraceID: {0xa9, 0xb1, 0xb0, 0x2c, 0x5c, 0xcc, 0x72, 0x93, 0xdb, 0x4e, 0x7b, 0x4b, 0xdc, 0x52, 0xb7, 0xbe},
ParentID: {0xa9, 0xb1, 0xb0, 0x2c, 0x5c, 0xcc, 0x72, 0x93},
SampleRate: &float64(1),
Context: &model.SpanContext{
Destination: &model.DestinationSpanContext{
Address: "127.0.0.1",
Port: 46875,
Service: &model.DestinationServiceSpanContext{Type:"external", Name:"http://127.0.0.1:46875", Resource:"127.0.0.1:46875"},
Cloud: (*model.DestinationCloudSpanContext)(nil),
},
Service: &model.ServiceSpanContext{
Target: &model.ServiceTargetSpanContext{Type:"http", Name:"127.0.0.1:46875"},
},
Database: (*model.DatabaseSpanContext)(nil),
Message: (*model.MessageSpanContext)(nil),
HTTP: &model.HTTPSpanContext{
URL: &url.URL{
Scheme: "http",
Opaque: "",
User: (*url.Userinfo)(nil),
Host: "127.0.0.1:46875",
Path: "/",
RawPath: "",
OmitHost: false,
ForceQuery: false,
RawQuery: "",
Fragment: "",
RawFragment: "",
},
StatusCode: 200,
},
Tags: nil,
},
Stacktrace: nil,
Outcome: "success",
Composite: (*model.CompositeSpan)(nil),
Links: nil,
OTel: (*model.OTel)(nil),
},
},
Transactions: {
{
ID: {0xa9, 0xb1, 0xb0, 0x2c, 0x5c, 0xcc, 0x72, 0x93},
TraceID: {0xa9, 0xb1, 0xb0, 0x2c, 0x5c, 0xcc, 0x72, 0x93, 0xdb, 0x4e, 0x7b, 0x4b, 0xdc, 0x52, 0xb7, 0xbe},
ParentID: {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0},
Name: "tx_name",
Type: "tx_type",
Timestamp: model.Time{
wall: 0x23b9050,
ext: 63805478262,
loc: (*time.Location)(nil),
},
Duration: 0.0027949999999999997,
Result: "",
Context: (*model.Context)(nil),
Sampled: (*bool)(nil),
SampleRate: &float64(1),
SpanCount: model.SpanCount{},
DroppedSpansStats: nil,
Outcome: "success",
OTel: (*model.OTel)(nil),
FAAS: (*model.FAAS)(nil),
Links: nil,
},
},
Profiles: nil,
}