Skip to content

Commit 4b7a126

Browse files
authored
added support to enable opt-in for forwarding exit spans without an entry span (#684)
added support for opt-in exit spans without an entry span.
1 parent 98c5b1d commit 4b7a126

10 files changed

+560
-30
lines changed

README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,14 @@ col.StartSpan("log.go", []ot.StartSpanOption{
145145

146146
This log can then be visualized in the dashboard under Analytics/Logs. You can add a filter by service name. In our example, the service name is "My Go App".
147147

148+
### Opt-in Exit Spans
149+
150+
Go tracer support the opt-in feature for the exit spans. When enabled, the collector can start capturing exit spans, even without an entry span. This capability is particularly useful for scenarios like cronjobs and other background tasks, enabling the users to tailor the tracing according to their specific requirements. By setting the `INSTANA_ALLOW_ROOT_EXIT_SPAN` variable, users can choose whether the tracer should start a trace with an exit span or not. The environment variable can have 2 values. (1: Tracer should record exit spans for the outgoing calls, when it has no active entry span. 0 or any other values: Tracer should not start a trace with an exit span).
151+
152+
```bash
153+
export INSTANA_ALLOW_ROOT_EXIT_SPAN=1
154+
```
155+
148156
### Complete Example
149157

150158
[Basic Usage](./example/basic_usage/main.go)

instrumentation_http.go

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -182,23 +182,27 @@ func RoundTripper(sensor TracerLogger, original http.RoundTripper) http.RoundTri
182182
}
183183
return tracingRoundTripper(func(req *http.Request) (*http.Response, error) {
184184
ctx := req.Context()
185-
parentSpan, ok := SpanFromContext(ctx)
186-
if !ok {
187-
// don't trace the exit call if there was no entry span provided
188-
return original.RoundTrip(req)
189-
}
190185

191186
sanitizedURL := cloneURL(req.URL)
192187
sanitizedURL.RawQuery = ""
193188
sanitizedURL.User = nil
194189

195-
span := sensor.Tracer().StartSpan("http",
190+
opts := []ot.StartSpanOption{
196191
ext.SpanKindRPCClient,
197-
ot.ChildOf(parentSpan.Context()),
198192
ot.Tags{
199193
"http.url": sanitizedURL.String(),
200194
"http.method": req.Method,
201-
})
195+
},
196+
}
197+
198+
tracer := sensor.Tracer()
199+
parentSpan, ok := SpanFromContext(ctx)
200+
if ok {
201+
tracer = parentSpan.Tracer()
202+
opts = append(opts, ot.ChildOf(parentSpan.Context()))
203+
}
204+
205+
span := tracer.StartSpan("http", opts...)
202206
defer span.Finish()
203207

204208
// clone the request since the RoundTrip should not modify the original one

instrumentation_http_test.go

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"fmt"
1010
"net/http"
1111
"net/http/httptest"
12+
"os"
1213
"strings"
1314
"testing"
1415

@@ -580,8 +581,10 @@ func TestRoundTripper_WithoutParentSpan(t *testing.T) {
580581
defer instana.ShutdownSensor()
581582

582583
rt := instana.RoundTripper(s, testRoundTripper(func(req *http.Request) (*http.Response, error) {
583-
assert.Empty(t, req.Header.Get(instana.FieldT))
584-
assert.Empty(t, req.Header.Get(instana.FieldS))
584+
// These fields will be present, as an exit span would be created
585+
// However the exit spans will not be recorded, as they are discarded before sending to the agent.
586+
assert.NotEmpty(t, req.Header.Get(instana.FieldT))
587+
assert.NotEmpty(t, req.Header.Get(instana.FieldS))
585588

586589
return &http.Response{
587590
Status: http.StatusText(http.StatusNotImplemented),
@@ -596,6 +599,40 @@ func TestRoundTripper_WithoutParentSpan(t *testing.T) {
596599
assert.Empty(t, recorder.GetQueuedSpans())
597600
}
598601

602+
func TestRoundTripper_AllowRootExitSpan(t *testing.T) {
603+
604+
os.Setenv("INSTANA_ALLOW_ROOT_EXIT_SPAN", "1")
605+
defer func() {
606+
os.Unsetenv("INSTANA_ALLOW_ROOT_EXIT_SPAN")
607+
}()
608+
609+
recorder := instana.NewTestRecorder()
610+
s := instana.NewSensorWithTracer(instana.NewTracerWithEverything(&instana.Options{AgentClient: alwaysReadyClient{}}, recorder))
611+
defer instana.ShutdownSensor()
612+
613+
rt := instana.RoundTripper(s, testRoundTripper(func(req *http.Request) (*http.Response, error) {
614+
// These fields will be present as an exit span would be created
615+
assert.NotEmpty(t, req.Header.Get(instana.FieldT))
616+
assert.NotEmpty(t, req.Header.Get(instana.FieldS))
617+
618+
return &http.Response{
619+
Status: http.StatusText(http.StatusNotImplemented),
620+
StatusCode: http.StatusNotImplemented,
621+
}, nil
622+
}))
623+
624+
resp, err := rt.RoundTrip(httptest.NewRequest("GET", "http://example.com/hello", nil))
625+
require.NoError(t, err)
626+
assert.Equal(t, http.StatusNotImplemented, resp.StatusCode)
627+
628+
// the spans are present in the recorder as INSTANA_ALLOW_ROOT_EXIT_SPAN is configured
629+
spans := recorder.GetQueuedSpans()
630+
require.Len(t, spans, 1)
631+
span := spans[0]
632+
assert.Equal(t, 0, span.Ec)
633+
assert.EqualValues(t, instana.ExitSpanKind, span.Kind)
634+
}
635+
599636
func TestRoundTripper_Error(t *testing.T) {
600637
serverErr := errors.New("something went wrong")
601638

instrumentation_sql_go1.10_test.go

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import (
1818
)
1919

2020
func TestWrapSQLConnector_Exec(t *testing.T) {
21+
2122
recorder := instana.NewTestRecorder()
2223
s := instana.NewSensorWithTracer(instana.NewTracerWithEverything(&instana.Options{
2324
Service: "go-sensor-test",
@@ -27,7 +28,13 @@ func TestWrapSQLConnector_Exec(t *testing.T) {
2728

2829
db := sql.OpenDB(instana.WrapSQLConnector(s, "connection string", sqlConnector{}))
2930

30-
res, err := db.Exec("TEST QUERY")
31+
pSpan := s.Tracer().StartSpan("parent-span")
32+
ctx := context.Background()
33+
if pSpan != nil {
34+
ctx = instana.ContextWithSpan(ctx, pSpan)
35+
}
36+
37+
res, err := db.ExecContext(ctx, "TEST QUERY")
3138
require.NoError(t, err)
3239

3340
lastID, err := res.LastInsertId()
@@ -60,18 +67,25 @@ func TestWrapSQLConnector_Exec(t *testing.T) {
6067
}
6168

6269
func TestWrapSQLConnector_Exec_Error(t *testing.T) {
70+
6371
recorder := instana.NewTestRecorder()
6472
s := instana.NewSensorWithTracer(instana.NewTracerWithEverything(&instana.Options{
6573
Service: "go-sensor-test",
6674
AgentClient: alwaysReadyClient{},
6775
}, recorder))
6876
defer instana.ShutdownSensor()
6977

78+
pSpan := s.Tracer().StartSpan("parent-span")
79+
ctx := context.Background()
80+
if pSpan != nil {
81+
ctx = instana.ContextWithSpan(ctx, pSpan)
82+
}
83+
7084
db := sql.OpenDB(instana.WrapSQLConnector(s, "connection string", sqlConnector{
7185
Error: errors.New("something went wrong"),
7286
}))
7387

74-
_, err := db.Exec("TEST QUERY")
88+
_, err := db.ExecContext(ctx, "TEST QUERY")
7589
assert.Error(t, err)
7690

7791
spans := recorder.GetQueuedSpans()
@@ -101,16 +115,23 @@ func TestWrapSQLConnector_Exec_Error(t *testing.T) {
101115
}
102116

103117
func TestWrapSQLConnector_Query(t *testing.T) {
118+
104119
recorder := instana.NewTestRecorder()
105120
s := instana.NewSensorWithTracer(instana.NewTracerWithEverything(&instana.Options{
106121
Service: "go-sensor-test",
107122
AgentClient: alwaysReadyClient{},
108123
}, recorder))
109124
defer instana.ShutdownSensor()
110125

126+
pSpan := s.Tracer().StartSpan("parent-span")
127+
ctx := context.Background()
128+
if pSpan != nil {
129+
ctx = instana.ContextWithSpan(ctx, pSpan)
130+
}
131+
111132
db := sql.OpenDB(instana.WrapSQLConnector(s, "connection string", sqlConnector{}))
112133

113-
res, err := db.Query("TEST QUERY")
134+
res, err := db.QueryContext(ctx, "TEST QUERY")
114135
require.NoError(t, err)
115136

116137
cols, err := res.Columns()
@@ -143,6 +164,7 @@ func TestWrapSQLConnector_Query(t *testing.T) {
143164
}
144165

145166
func TestWrapSQLConnector_Query_Error(t *testing.T) {
167+
146168
recorder := instana.NewTestRecorder()
147169
s := instana.NewSensorWithTracer(instana.NewTracerWithEverything(&instana.Options{
148170
Service: "go-sensor-test",
@@ -155,7 +177,13 @@ func TestWrapSQLConnector_Query_Error(t *testing.T) {
155177
Error: dbErr,
156178
}))
157179

158-
_, err := db.Query("TEST QUERY")
180+
pSpan := s.Tracer().StartSpan("parent-span")
181+
ctx := context.Background()
182+
if pSpan != nil {
183+
ctx = instana.ContextWithSpan(ctx, pSpan)
184+
}
185+
186+
_, err := db.QueryContext(ctx, "TEST QUERY")
159187
assert.Error(t, err)
160188

161189
spans := recorder.GetQueuedSpans()

0 commit comments

Comments
 (0)