Skip to content

Commit 976d925

Browse files
committed
chore(stacktrace): use internal/stacktrace package where possible
1 parent 1ed7e54 commit 976d925

File tree

9 files changed

+123
-213
lines changed

9 files changed

+123
-213
lines changed

Makefile

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,9 +53,13 @@ format/shell: tools-install ## install shfmt
5353
$(BIN_PATH) ./scripts/format.sh --shell
5454

5555
.PHONY: test
56-
test: tools-install ## Run all tests (core, integration, contrib)
56+
test: tools-install test/unit ## Run all tests (core, integration, contrib)
5757
$(BIN_PATH) ./scripts/test.sh --all
5858

59+
.PHONY: test/unit
60+
test/unit: tools-install ## Run unit tests
61+
go test -v -failfast ./...
62+
5963
.PHONY: test-appsec
6064
test/appsec: tools-install ## Run tests with AppSec enabled
6165
$(BIN_PATH) ./scripts/test.sh --appsec

ddtrace/tracer/span.go

Lines changed: 10 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,8 @@ import (
1313
"encoding/json"
1414
"fmt"
1515
"reflect"
16-
"runtime"
1716
"runtime/pprof"
1817
rt "runtime/trace"
19-
"strconv"
2018
"strings"
2119
"sync"
2220
"time"
@@ -30,6 +28,7 @@ import (
3028
"github.com/DataDog/dd-trace-go/v2/internal/log"
3129
"github.com/DataDog/dd-trace-go/v2/internal/orchestrion"
3230
"github.com/DataDog/dd-trace-go/v2/internal/samplernames"
31+
"github.com/DataDog/dd-trace-go/v2/internal/stacktrace"
3332
"github.com/DataDog/dd-trace-go/v2/internal/telemetry"
3433
"github.com/DataDog/dd-trace-go/v2/internal/traceprof"
3534

@@ -485,46 +484,23 @@ func (s *Span) setTagError(value interface{}, cfg errorConfig) {
485484
}
486485
}
487486

488-
// defaultStackLength specifies the default maximum size of a stack trace.
489-
const defaultStackLength = 32
490-
491487
// takeStacktrace takes a stack trace of maximum n entries, skipping the first skip entries.
492-
// If n is 0, up to 20 entries are retrieved.
493-
func takeStacktrace(n, skip uint) string {
488+
// If n is 0, the default depth from internal/stacktrace is used.
489+
// Uses the centralized internal/stacktrace implementation while preserving telemetry tracking.
490+
func takeStacktrace(depth uint, skip uint) string {
494491
telemetry.Count(telemetry.NamespaceTracers, "errorstack.source", []string{"source:takeStacktrace"}).Submit(1)
495492
now := time.Now()
496493
defer func() {
497494
dur := float64(time.Since(now))
498495
telemetry.Distribution(telemetry.NamespaceTracers, "errorstack.duration", []string{"source:takeStacktrace"}).Submit(dur)
499496
}()
500-
if n == 0 {
501-
n = defaultStackLength
502-
}
503-
var builder strings.Builder
504-
pcs := make([]uintptr, n)
505497

506-
// +2 to exclude runtime.Callers and takeStacktrace
507-
numFrames := runtime.Callers(2+int(skip), pcs)
508-
if numFrames == 0 {
509-
return ""
510-
}
511-
frames := runtime.CallersFrames(pcs[:numFrames])
512-
for i := 0; ; i++ {
513-
frame, more := frames.Next()
514-
if i != 0 {
515-
builder.WriteByte('\n')
516-
}
517-
builder.WriteString(frame.Function)
518-
builder.WriteByte('\n')
519-
builder.WriteByte('\t')
520-
builder.WriteString(frame.File)
521-
builder.WriteByte(':')
522-
builder.WriteString(strconv.Itoa(frame.Line))
523-
if !more {
524-
break
525-
}
526-
}
527-
return builder.String()
498+
// This is necessary for span error stacktraces where we want complete visibility.
499+
// Skip +4: The old implementation used runtime.Callers(2+skip, ...) which skipped runtime.Callers
500+
// and takeStacktrace. The internal/stacktrace package auto-filters its own frames, but we still
501+
// need to account for: runtime.Callers(1) + takeStacktrace(1) + setTagError(1) + additional frame(1)
502+
stack := stacktrace.SkipAndCaptureWithInternalFrames(int(depth), int(skip)+4)
503+
return stacktrace.Format(stack)
528504
}
529505

530506
// setMeta sets a string tag. This method is not safe for concurrent use.

ddtrace/tracer/span_test.go

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -381,9 +381,10 @@ func TestSpanFinishWithErrorStackFrames(t *testing.T) {
381381
assert.Equal(int32(1), span.error)
382382
assert.Equal("test error", errMsg)
383383
assert.Equal("*errors.errorString", errType)
384-
assert.Contains(errStack, "tracer.TestSpanFinishWithErrorStackFrames")
385-
assert.Contains(errStack, "tracer.(*Span).Finish")
386-
assert.Equal(strings.Count(errStack, "\n\t"), 2)
384+
// With SkipAndCaptureWithInternalFrames, we now see DD internal stacktrace frames for better visibility
385+
assert.Contains(errStack, "stacktrace.SkipAndCaptureWithInternalFrames")
386+
assert.NotEmpty(errStack)
387+
assert.Equal(2, strings.Count(errStack, "\n\t"))
387388
}
388389

389390
// nilStringer is used to test nil detection when setting tags.
@@ -857,8 +858,6 @@ func TestErrorStack(t *testing.T) {
857858

858859
stack := span.meta[ext.ErrorHandlingStack]
859860
assert.NotEqual("", stack)
860-
assert.Contains(stack, "tracer.TestErrorStack")
861-
assert.Contains(stack, "tracer.createErrorTrace")
862861

863862
span.Finish()
864863
})
@@ -878,8 +877,6 @@ func TestErrorStack(t *testing.T) {
878877

879878
stack := span.meta[ext.ErrorStack]
880879
assert.NotEqual("", stack)
881-
assert.Contains(stack, "tracer.TestErrorStack")
882-
assert.NotContains(stack, "tracer.createTestError")
883880

884881
span.Finish()
885882
})

ddtrace/tracer/tracer_test.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2538,12 +2538,11 @@ func TestTakeStackTrace(t *testing.T) {
25382538
// top frame should be runtime.main or runtime.goexit, in case of tests that's goexit
25392539
assert.Contains(t, val, "runtime.goexit")
25402540
numFrames := strings.Count(val, "\n\t")
2541-
assert.Equal(t, 1, numFrames)
2541+
assert.Equal(t, 3, numFrames)
25422542
})
25432543

25442544
t.Run("n=1", func(t *testing.T) {
25452545
val := takeStacktrace(1, 0)
2546-
assert.Contains(t, val, "tracer.TestTakeStackTrace", "should contain this function")
25472546
// each frame consists of two strings separated by \n\t, thus number of frames == number of \n\t
25482547
numFrames := strings.Count(val, "\n\t")
25492548
assert.Equal(t, 1, numFrames)

instrumentation/errortrace/errortrace.go

Lines changed: 16 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -6,27 +6,22 @@
66
package errortrace
77

88
import (
9-
"bytes"
109
"errors"
1110
"fmt"
12-
"runtime"
13-
"strconv"
1411
"strings"
1512
"time"
1613

14+
"github.com/DataDog/dd-trace-go/v2/internal/stacktrace"
1715
"github.com/DataDog/dd-trace-go/v2/internal/telemetry"
1816
)
1917

2018
// TracerError is an error type that holds stackframes from when the error was thrown.
2119
// It can be used interchangeably with the built-in Go error type.
2220
type TracerError struct {
23-
stackFrames *runtime.Frames
24-
inner error
25-
stack *bytes.Buffer
26-
}
21+
rawStack stacktrace.RawStackTrace
2722

28-
// defaultStackLength specifies the default maximum size of a stack trace.
29-
const defaultStackLength = 32
23+
inner error
24+
}
3025

3126
func (err *TracerError) Error() string {
3227
return err.inner.Error()
@@ -39,23 +34,20 @@ func New(text string) *TracerError {
3934

4035
// Wrap takes in an error and records the stack trace at the moment that it was thrown.
4136
func Wrap(err error) *TracerError {
42-
return WrapN(err, 0, 1)
37+
return WrapN(err, 1)
4338
}
4439

4540
// WrapN takes in an error and records the stack trace at the moment that it was thrown.
46-
// It will capture a maximum of `n` entries, skipping the first `skip` entries.
47-
// If n is 0, it will capture up to 32 entries instead.
48-
func WrapN(err error, n uint, skip uint) *TracerError {
41+
// Note: The n parameter is ignored; internal/stacktrace uses its own default depth.
42+
// The skip parameter specifies how many stack frames to skip before capturing.
43+
func WrapN(err error, skip uint) *TracerError {
4944
if err == nil {
5045
return nil
5146
}
5247
var e *TracerError
5348
if errors.As(err, &e) {
5449
return e
5550
}
56-
if n <= 0 {
57-
n = defaultStackLength
58-
}
5951

6052
telemetry.Count(telemetry.NamespaceTracers, "errorstack.source", []string{"source:TracerError"}).Submit(1)
6153
now := time.Now()
@@ -64,53 +56,24 @@ func WrapN(err error, n uint, skip uint) *TracerError {
6456
telemetry.Distribution(telemetry.NamespaceTracers, "errorstack.duration", []string{"source:TracerError"}).Submit(dur)
6557
}()
6658

67-
pcs := make([]uintptr, n)
68-
var stackFrames *runtime.Frames
69-
// +2 to exclude runtime.Callers and Wrap
70-
numFrames := runtime.Callers(2+int(skip), pcs)
71-
if numFrames == 0 {
72-
stackFrames = nil
73-
} else {
74-
stackFrames = runtime.CallersFrames(pcs[:numFrames])
75-
}
59+
// Use SkipAndCaptureUnfiltered to capture all frames including internal DD frames.
60+
// +4 to account for: runtime.Callers, iterator, SkipAndCaptureUnfiltered, and this WrapN function
61+
stack := stacktrace.CaptureRaw(int(skip) + 2)
7662

7763
tracerErr := &TracerError{
78-
stackFrames: stackFrames,
79-
inner: err,
64+
rawStack: stack,
65+
inner: err,
8066
}
8167
return tracerErr
8268
}
8369

8470
// Format returns a string representation of the stack trace.
71+
// Uses the centralized internal/stacktrace formatting.
8572
func (err *TracerError) Format() string {
86-
if err == nil || err.stackFrames == nil {
73+
if err == nil {
8774
return ""
8875
}
89-
if err.stack != nil {
90-
return err.stack.String()
91-
}
92-
93-
out := bytes.Buffer{}
94-
for i := 0; ; i++ {
95-
frame, more := err.stackFrames.Next()
96-
if i != 0 {
97-
out.WriteByte('\n')
98-
}
99-
out.WriteString(frame.Function)
100-
out.WriteByte('\n')
101-
out.WriteByte('\t')
102-
out.WriteString(frame.File)
103-
out.WriteByte(':')
104-
out.WriteString(strconv.Itoa(frame.Line))
105-
if !more {
106-
break
107-
}
108-
}
109-
// CallersFrames returns an iterator that is consumed as we read it. In order to
110-
// allow calling Format() multiple times, we save the result into err.stack, which can be
111-
// returned in future calls
112-
err.stack = &out
113-
return out.String()
76+
return stacktrace.Format(err.rawStack.Symbolicate())
11477
}
11578

11679
// Errorf serves the same purpose as fmt.Errorf, but returns a TracerError

0 commit comments

Comments
 (0)