Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

added support to enable opt-in for forwarding exit spans without an entry span #684

Merged
merged 16 commits into from
Mar 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,14 @@ col.StartSpan("log.go", []ot.StartSpanOption{

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".

### Opt-in Exit Spans

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).

```bash
export INSTANA_ALLOW_ROOT_EXIT_SPAN=1
```

### Complete Example

[Basic Usage](./example/basic_usage/main.go)
Expand Down
20 changes: 12 additions & 8 deletions instrumentation_http.go
Original file line number Diff line number Diff line change
Expand Up @@ -182,23 +182,27 @@ func RoundTripper(sensor TracerLogger, original http.RoundTripper) http.RoundTri
}
return tracingRoundTripper(func(req *http.Request) (*http.Response, error) {
ctx := req.Context()
parentSpan, ok := SpanFromContext(ctx)
if !ok {
// don't trace the exit call if there was no entry span provided
return original.RoundTrip(req)
}

sanitizedURL := cloneURL(req.URL)
sanitizedURL.RawQuery = ""
sanitizedURL.User = nil

span := sensor.Tracer().StartSpan("http",
opts := []ot.StartSpanOption{
ext.SpanKindRPCClient,
ot.ChildOf(parentSpan.Context()),
ot.Tags{
"http.url": sanitizedURL.String(),
"http.method": req.Method,
})
},
}

tracer := sensor.Tracer()
parentSpan, ok := SpanFromContext(ctx)
if ok {
tracer = parentSpan.Tracer()
opts = append(opts, ot.ChildOf(parentSpan.Context()))
}

span := tracer.StartSpan("http", opts...)
defer span.Finish()

// clone the request since the RoundTrip should not modify the original one
Expand Down
41 changes: 39 additions & 2 deletions instrumentation_http_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"fmt"
"net/http"
"net/http/httptest"
"os"
"strings"
"testing"

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

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

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

func TestRoundTripper_AllowRootExitSpan(t *testing.T) {

os.Setenv("INSTANA_ALLOW_ROOT_EXIT_SPAN", "1")
defer func() {
os.Unsetenv("INSTANA_ALLOW_ROOT_EXIT_SPAN")
}()

recorder := instana.NewTestRecorder()
s := instana.NewSensorWithTracer(instana.NewTracerWithEverything(&instana.Options{AgentClient: alwaysReadyClient{}}, recorder))
defer instana.ShutdownSensor()

rt := instana.RoundTripper(s, testRoundTripper(func(req *http.Request) (*http.Response, error) {
// These fields will be present as an exit span would be created
assert.NotEmpty(t, req.Header.Get(instana.FieldT))
assert.NotEmpty(t, req.Header.Get(instana.FieldS))

return &http.Response{
Status: http.StatusText(http.StatusNotImplemented),
StatusCode: http.StatusNotImplemented,
}, nil
}))

resp, err := rt.RoundTrip(httptest.NewRequest("GET", "http://example.com/hello", nil))
require.NoError(t, err)
assert.Equal(t, http.StatusNotImplemented, resp.StatusCode)

// the spans are present in the recorder as INSTANA_ALLOW_ROOT_EXIT_SPAN is configured
spans := recorder.GetQueuedSpans()
require.Len(t, spans, 1)
span := spans[0]
assert.Equal(t, 0, span.Ec)
assert.EqualValues(t, instana.ExitSpanKind, span.Kind)
}

func TestRoundTripper_Error(t *testing.T) {
serverErr := errors.New("something went wrong")

Expand Down
36 changes: 32 additions & 4 deletions instrumentation_sql_go1.10_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
)

func TestWrapSQLConnector_Exec(t *testing.T) {

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

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

res, err := db.Exec("TEST QUERY")
pSpan := s.Tracer().StartSpan("parent-span")
ctx := context.Background()
if pSpan != nil {
ctx = instana.ContextWithSpan(ctx, pSpan)
}

res, err := db.ExecContext(ctx, "TEST QUERY")
require.NoError(t, err)

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

func TestWrapSQLConnector_Exec_Error(t *testing.T) {

recorder := instana.NewTestRecorder()
s := instana.NewSensorWithTracer(instana.NewTracerWithEverything(&instana.Options{
Service: "go-sensor-test",
AgentClient: alwaysReadyClient{},
}, recorder))
defer instana.ShutdownSensor()

pSpan := s.Tracer().StartSpan("parent-span")
ctx := context.Background()
if pSpan != nil {
ctx = instana.ContextWithSpan(ctx, pSpan)
}

db := sql.OpenDB(instana.WrapSQLConnector(s, "connection string", sqlConnector{
Error: errors.New("something went wrong"),
}))

_, err := db.Exec("TEST QUERY")
_, err := db.ExecContext(ctx, "TEST QUERY")
assert.Error(t, err)

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

func TestWrapSQLConnector_Query(t *testing.T) {

recorder := instana.NewTestRecorder()
s := instana.NewSensorWithTracer(instana.NewTracerWithEverything(&instana.Options{
Service: "go-sensor-test",
AgentClient: alwaysReadyClient{},
}, recorder))
defer instana.ShutdownSensor()

pSpan := s.Tracer().StartSpan("parent-span")
ctx := context.Background()
if pSpan != nil {
ctx = instana.ContextWithSpan(ctx, pSpan)
}

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

res, err := db.Query("TEST QUERY")
res, err := db.QueryContext(ctx, "TEST QUERY")
require.NoError(t, err)

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

func TestWrapSQLConnector_Query_Error(t *testing.T) {

recorder := instana.NewTestRecorder()
s := instana.NewSensorWithTracer(instana.NewTracerWithEverything(&instana.Options{
Service: "go-sensor-test",
Expand All @@ -155,7 +177,13 @@ func TestWrapSQLConnector_Query_Error(t *testing.T) {
Error: dbErr,
}))

_, err := db.Query("TEST QUERY")
pSpan := s.Tracer().StartSpan("parent-span")
ctx := context.Background()
if pSpan != nil {
ctx = instana.ContextWithSpan(ctx, pSpan)
}

_, err := db.QueryContext(ctx, "TEST QUERY")
assert.Error(t, err)

spans := recorder.GetQueuedSpans()
Expand Down
Loading
Loading