Skip to content

Commit bc2781f

Browse files
authored
awsxrayexporter - OTel Go SDK stacktrace translation support (#4670)
* go stack trace translation support - awsxrayexporter * added tests * minor changes * ran gofmt * remove unused var * added condition to check array length * using regex for string matching * fix lint error * fix linter issue * address CR comments
1 parent 970edee commit bc2781f

File tree

2 files changed

+157
-0
lines changed

2 files changed

+157
-0
lines changed

exporter/awsxrayexporter/internal/translator/cause.go

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ package translator
1717
import (
1818
"bufio"
1919
"net/textproto"
20+
"regexp"
2021
"strconv"
2122
"strings"
2223

@@ -167,6 +168,8 @@ func parseException(exceptionType string, message string, stacktrace string, lan
167168
case "php":
168169
// The PHP SDK formats stack traces exactly like Java would
169170
exceptions = fillJavaStacktrace(stacktrace, exceptions)
171+
case "go":
172+
exceptions = fillGoStacktrace(stacktrace, exceptions)
170173
}
171174

172175
return exceptions
@@ -486,6 +489,58 @@ func fillDotnetStacktrace(stacktrace string, exceptions []awsxray.Exception) []a
486489
return exceptions
487490
}
488491

492+
func fillGoStacktrace(stacktrace string, exceptions []awsxray.Exception) []awsxray.Exception {
493+
var line string
494+
var label string
495+
var path string
496+
var lineNumber int
497+
498+
plnre := regexp.MustCompile(`([^:\s]+)\:(\d+)`)
499+
re := regexp.MustCompile(`^goroutine.*\brunning\b.*:$`)
500+
501+
r := textproto.NewReader(bufio.NewReader(strings.NewReader(stacktrace)))
502+
503+
// Skip first line containing top level exception / message
504+
_, _ = r.ReadLine()
505+
exception := &exceptions[0]
506+
line, err := r.ReadLine()
507+
if err != nil {
508+
return exceptions
509+
}
510+
511+
exception.Stack = make([]awsxray.StackFrame, 0)
512+
for {
513+
match := re.Match([]byte(line))
514+
if match {
515+
line, _ = r.ReadLine()
516+
}
517+
518+
label = line
519+
line, _ = r.ReadLine()
520+
521+
matches := plnre.FindStringSubmatch(line)
522+
if len(matches) == 3 {
523+
path = matches[1]
524+
lineNumber, _ = strconv.Atoi(matches[2])
525+
}
526+
527+
stack := awsxray.StackFrame{
528+
Path: aws.String(path),
529+
Label: aws.String(label),
530+
Line: aws.Int(lineNumber),
531+
}
532+
533+
exception.Stack = append(exception.Stack, stack)
534+
535+
line, err = r.ReadLine()
536+
if err != nil {
537+
break
538+
}
539+
}
540+
541+
return exceptions
542+
}
543+
489544
// indexOf returns position of the first occurrence of a Byte in str starting at pos index.
490545
func indexOf(str string, c byte, pos int) int {
491546
if pos < 0 {

exporter/awsxrayexporter/internal/translator/cause_test.go

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1014,3 +1014,105 @@ func TestParseExceptionPhpStacktraceMalformedLines(t *testing.T) {
10141014
assert.Equal(t, "test.php", *exceptions[0].Stack[1].Path)
10151015
assert.Equal(t, 89, *exceptions[0].Stack[1].Line)
10161016
}
1017+
1018+
func TestParseExceptionGoWithoutStacktrace(t *testing.T) {
1019+
exceptionType := "Exception"
1020+
message := "Thrown from grandparent"
1021+
1022+
stacktrace := ""
1023+
1024+
exceptions := parseException(exceptionType, message, stacktrace, "go")
1025+
1026+
assert.Len(t, exceptions, 1)
1027+
assert.NotEmpty(t, exceptions[0].ID)
1028+
assert.Equal(t, "Exception", *exceptions[0].Type)
1029+
assert.Equal(t, "Thrown from grandparent", *exceptions[0].Message)
1030+
assert.Nil(t, exceptions[0].Stack)
1031+
}
1032+
1033+
func TestParseExceptionGoWithStacktrace(t *testing.T) {
1034+
exceptionType := "Exception"
1035+
message := "error message"
1036+
1037+
stacktrace := `goroutine 19 [running]:
1038+
go.opentelemetry.io/otel/sdk/trace.recordStackTrace(0x0, 0x0)
1039+
otel-go-core/opentelemetry-go/sdk/trace/span.go:323 +0x9b
1040+
go.opentelemetry.io/otel/sdk/trace.(*span).RecordError(0xc0003a6000, 0x14a5f00, 0xc00038c000, 0xc000390140, 0x3, 0x4)
1041+
otel-go-core/opentelemetry-go/sdk/trace/span.go:302 +0x3fc
1042+
go.opentelemetry.io/otel/sdk/trace.TestRecordErrorWithStackTrace(0xc000102900)
1043+
otel-go-core/opentelemetry-go/sdk/trace/trace_test.go:1167 +0x3ef
1044+
testing.tRunner(0xc000102900, 0x1484410)
1045+
/usr/local/Cellar/go/1.16.3/libexec/src/testing/testing.go:1193 +0x1a3
1046+
created by testing.(*T).Run
1047+
/usr/local/Cellar/go/1.16.3/libexec/src/testing/testing.go:1238 +0x63c`
1048+
1049+
exceptions := parseException(exceptionType, message, stacktrace, "go")
1050+
assert.Len(t, exceptions, 1)
1051+
assert.NotEmpty(t, exceptions[0].ID)
1052+
assert.Equal(t, "Exception", *exceptions[0].Type)
1053+
assert.Equal(t, "error message", *exceptions[0].Message)
1054+
assert.Len(t, exceptions[0].Stack, 5)
1055+
1056+
assert.True(t, strings.HasPrefix(*exceptions[0].Stack[0].Label, "go.opentelemetry.io/otel/sdk/trace.recordStackTrace"))
1057+
assert.Equal(t, "otel-go-core/opentelemetry-go/sdk/trace/span.go", *exceptions[0].Stack[0].Path)
1058+
assert.Equal(t, 323, *exceptions[0].Stack[0].Line)
1059+
assert.True(t, strings.HasPrefix(*exceptions[0].Stack[1].Label, "go.opentelemetry.io/otel/sdk/trace.(*span).RecordError"))
1060+
assert.Equal(t, "otel-go-core/opentelemetry-go/sdk/trace/span.go", *exceptions[0].Stack[1].Path)
1061+
assert.Equal(t, 302, *exceptions[0].Stack[1].Line)
1062+
assert.True(t, strings.HasPrefix(*exceptions[0].Stack[3].Label, "testing.tRunner"))
1063+
assert.Equal(t, "/usr/local/Cellar/go/1.16.3/libexec/src/testing/testing.go", *exceptions[0].Stack[3].Path)
1064+
assert.Equal(t, 1193, *exceptions[0].Stack[3].Line)
1065+
assert.True(t, strings.HasPrefix(*exceptions[0].Stack[4].Label, "created by testing.(*T).Run"))
1066+
assert.Equal(t, "/usr/local/Cellar/go/1.16.3/libexec/src/testing/testing.go", *exceptions[0].Stack[4].Path)
1067+
assert.Equal(t, 1238, *exceptions[0].Stack[4].Line)
1068+
}
1069+
1070+
func TestParseMultipleExceptionGoWithStacktrace(t *testing.T) {
1071+
exceptionType := "Exception"
1072+
message := "panic"
1073+
1074+
stacktrace := `goroutine 19 [running]:
1075+
go.opentelemetry.io/otel/sdk/trace.recordStackTrace(0x0, 0x0)
1076+
Documents/otel-go-core/opentelemetry-go/sdk/trace/span.go:318 +0x9b
1077+
go.opentelemetry.io/otel/sdk/trace.(*span).End(0xc000082300, 0xc0000a0040, 0x1, 0x1)
1078+
Documents/otel-go-core/opentelemetry-go/sdk/trace/span.go:252 +0x4ee
1079+
panic(0x1414f00, 0xc0000a0050)
1080+
/usr/local/Cellar/go/1.16.3/libexec/src/runtime/panic.go:971 +0x4c7
1081+
go.opentelemetry.io/otel/sdk/trace.TestSpanCapturesPanicWithStackTrace.func1()
1082+
Documents/otel-go-core/opentelemetry-go/sdk/trace/trace_test.go:1425 +0x225
1083+
github.com/stretchr/testify/assert.didPanic.func1(0xc0001ad0e8, 0xc0001ad0d7, 0xc0001ad0d8, 0xc00009e048)
1084+
go/pkg/mod/github.com/stretchr/[email protected]/assert/assertions.go:1018 +0xb8
1085+
github.com/stretchr/testify/assert.didPanic(0xc00009e048, 0x14a5b00, 0x0, 0x0, 0x0, 0x0)
1086+
go/pkg/mod/github.com/stretchr/[email protected]/assert/assertions.go:1020 +0x85
1087+
github.com/stretchr/testify/assert.PanicsWithError(0x14a5b60, 0xc000186600, 0x146e31c, 0xd, 0xc00009e048, 0x0, 0x0, 0x0, 0xc000038900)
1088+
go/pkg/mod/github.com/stretchr/[email protected]/assert/assertions.go:1071 +0x10c
1089+
goroutine 26 [running]:
1090+
github.com/stretchr/testify/require.PanicsWithError(0x14a7328, 0xc000186600, 0x146e31c, 0xd, 0xc00009e048, 0x0, 0x0, 0x0)
1091+
go/pkg/mod/github.com/stretchr/[email protected]/require/require.go:1607 +0x15e
1092+
go.opentelemetry.io/otel/sdk/trace.TestSpanCapturesPanicWithStackTrace(0xc000186600)
1093+
Documents/otel-go-core/opentelemetry-go/sdk/trace/trace_test.go:1427 +0x33a
1094+
testing.tRunner(0xc000186600, 0x1484440)
1095+
/usr/local/Cellar/go/1.16.3/libexec/src/testing/testing.go:1193 +0x1a3
1096+
created by testing.(*T).Run
1097+
/usr/local/Cellar/go/1.16.3/libexec/src/testing/testing.go:1238 +0x63c`
1098+
1099+
exceptions := parseException(exceptionType, message, stacktrace, "go")
1100+
assert.Len(t, exceptions, 1)
1101+
assert.NotEmpty(t, exceptions[0].ID)
1102+
assert.Equal(t, "Exception", *exceptions[0].Type)
1103+
assert.Equal(t, "panic", *exceptions[0].Message)
1104+
assert.Len(t, exceptions[0].Stack, 11)
1105+
1106+
assert.True(t, strings.HasPrefix(*exceptions[0].Stack[0].Label, "go.opentelemetry.io/otel/sdk/trace.recordStackTrace"))
1107+
assert.Equal(t, "Documents/otel-go-core/opentelemetry-go/sdk/trace/span.go", *exceptions[0].Stack[0].Path)
1108+
assert.Equal(t, 318, *exceptions[0].Stack[0].Line)
1109+
assert.True(t, strings.HasPrefix(*exceptions[0].Stack[7].Label, "github.com/stretchr/testify/require.PanicsWithError"))
1110+
assert.Equal(t, "go/pkg/mod/github.com/stretchr/[email protected]/require/require.go", *exceptions[0].Stack[7].Path)
1111+
assert.Equal(t, 1607, *exceptions[0].Stack[7].Line)
1112+
assert.True(t, strings.HasPrefix(*exceptions[0].Stack[8].Label, "go.opentelemetry.io/otel/sdk/trace.TestSpanCapturesPanicWithStackTrace"))
1113+
assert.Equal(t, "Documents/otel-go-core/opentelemetry-go/sdk/trace/trace_test.go", *exceptions[0].Stack[8].Path)
1114+
assert.Equal(t, 1427, *exceptions[0].Stack[8].Line)
1115+
assert.True(t, strings.HasPrefix(*exceptions[0].Stack[10].Label, "created by testing.(*T).Run"))
1116+
assert.Equal(t, "/usr/local/Cellar/go/1.16.3/libexec/src/testing/testing.go", *exceptions[0].Stack[10].Path)
1117+
assert.Equal(t, 1238, *exceptions[0].Stack[10].Line)
1118+
}

0 commit comments

Comments
 (0)