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

feat: Add variable state capture to internal server error logging #2251

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
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: 5 additions & 3 deletions routes/index.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ import (
"github.com/stakwork/sphinx-tribes/utils"
)


// NewRouter creates a chi router
func NewRouter() *http.Server {
r := initChi()
Expand Down Expand Up @@ -203,14 +202,17 @@ func internalServerErrorHandler(next http.Handler) http.Handler {
n := runtime.Stack(buf, true)
stackTrace := string(buf[:n])

state := utils.CaptureVariableState(err)

// Format stack trace to edge list
edgeList := utils.FormatStacktraceToEdgeList(stackTrace, err)
edgeList := utils.FormatStacktraceToEdgeList(stackTrace, err, state)

utils.Log.Error("Internal Server Error: %s %s\nError: %v\nStack Trace:\n%s\nEdge List:\n%+v\n",
utils.Log.Error("Internal Server Error: %s %s\nError: %v\nStack Trace:\n%s\nVariable State:\n%s\nEdge List:\n%+v\n",
r.Method,
r.URL.Path,
err,
stackTrace,
state,
utils.PrettyPrintEdgeList(edgeList),
)

Expand Down
23 changes: 20 additions & 3 deletions utils/stacktrace.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,24 @@ type TraceNodeData struct {
TraceUUID string `json:"trace_uuid"`
}

func FormatStacktraceToEdgeList(stackTrace string, err interface{}) EdgeList {
func CaptureVariableState(err interface{}) string {

errStr := fmt.Sprintf("%+v", err)

var state strings.Builder
state.WriteString("Error Variables:\n")

if m, ok := err.(map[string]interface{}); ok {
b, _ := json.MarshalIndent(m, "", " ")
state.Write(b)
} else {
state.WriteString(errStr)
}

return state.String()
}

func FormatStacktraceToEdgeList(stackTrace string, err interface{}, state string) EdgeList {

now := time.Now().Unix()

Expand All @@ -81,7 +98,7 @@ func FormatStacktraceToEdgeList(stackTrace string, err interface{}) EdgeList {

reportNodeData := ReportNodeData{
AppType: nil,
Errors: fmt.Sprintf("%v", err),
Errors: fmt.Sprintf("%v\nVariable State:\n%s", err, state),
ReleaseStage: "development",
ReportID: reportID,
SeverityReason: "unhandledException",
Expand Down Expand Up @@ -266,7 +283,7 @@ func parseStackTrace(stackTrace string) []string {
}
}
if len(frames) > 1 {
return frames[2:]
return frames[2:]
}
return frames
}
Expand Down
59 changes: 53 additions & 6 deletions utils/stacktrace_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,14 @@ func TestFormatStacktraceToEdgeList(t *testing.T) {
name string
stackTrace string
err interface{}
state string
expectedChecks func(t *testing.T, edgeList EdgeList)
}{
{
name: "Basic Error Scenario",
stackTrace: generateTestStackTrace(),
err: fmt.Errorf("test error"),
state: "Error Variables:\ntest error",
expectedChecks: func(t *testing.T, edgeList EdgeList) {
assert.NotNil(t, edgeList)
assert.Greater(t, len(edgeList.EdgeList), 0)
Expand All @@ -41,6 +43,12 @@ func TestFormatStacktraceToEdgeList(t *testing.T) {
assert.Equal(t, "Report", generatedByEdge.Source.NodeType)
assert.Equal(t, "Application", generatedByEdge.Targets[0].NodeType)

// Verify error message contains state information
reportNode := generatedByEdge.Source
reportData, ok := reportNode.NodeData.(ReportNodeData)
assert.True(t, ok)
assert.Contains(t, reportData.Errors, "Variable State:")

// Check HAS edges
hasEdges := findEdgesByType(edgeList, "HAS")
assert.Greater(t, len(hasEdges), 0)
Expand All @@ -62,6 +70,7 @@ func TestFormatStacktraceToEdgeList(t *testing.T) {
name: "Nil Error Scenario",
stackTrace: generateTestStackTrace(),
err: nil,
state: "Error Variables:\n<nil>",
expectedChecks: func(t *testing.T, edgeList EdgeList) {
assert.NotNil(t, edgeList)
assert.Greater(t, len(edgeList.EdgeList), 0)
Expand All @@ -71,6 +80,7 @@ func TestFormatStacktraceToEdgeList(t *testing.T) {
name: "Complex Error Scenario",
stackTrace: generateTestStackTrace(),
err: struct{ message string }{"complex error"},
state: "Error Variables:\n{message:complex error}",
expectedChecks: func(t *testing.T, edgeList EdgeList) {
assert.NotNil(t, edgeList)
assert.Greater(t, len(edgeList.EdgeList), 0)
Expand All @@ -80,7 +90,7 @@ func TestFormatStacktraceToEdgeList(t *testing.T) {

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
edgeList := FormatStacktraceToEdgeList(tc.stackTrace, tc.err)
edgeList := FormatStacktraceToEdgeList(tc.stackTrace, tc.err, tc.state)
tc.expectedChecks(t, edgeList)
})
}
Expand Down Expand Up @@ -119,6 +129,42 @@ another random line`,
}
}

func TestCaptureVariableState(t *testing.T) {
testCases := []struct {
name string
err interface{}
expectedState string
}{
{
name: "Simple Error",
err: fmt.Errorf("test error"),
expectedState: "Error Variables:\ntest error",
},
{
name: "Map Error",
err: map[string]interface{}{"key": "value"},
expectedState: "Error Variables:\n{\n \"key\": \"value\"\n}",
},
{
name: "Nil Error",
err: nil,
expectedState: "Error Variables:\n<nil>",
},
{
name: "Struct Error",
err: struct{ msg string }{"test"},
expectedState: "Error Variables:\n{msg:test}",
},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
state := CaptureVariableState(tc.err)
assert.Equal(t, tc.expectedState, state)
})
}
}

func TestGenerateReportID(t *testing.T) {

reportID1 := generateReportID()
Expand Down Expand Up @@ -151,38 +197,39 @@ func findEdgesByType(edgeList EdgeList, edgeType string) []Edge {
func BenchmarkFormatStacktraceToEdgeList(b *testing.B) {
stackTrace := generateTestStackTrace()
err := fmt.Errorf("benchmark error")
state := CaptureVariableState(err)

b.ResetTimer()
for i := 0; i < b.N; i++ {
FormatStacktraceToEdgeList(stackTrace, err)
FormatStacktraceToEdgeList(stackTrace, err, state)
}
}

func TestLongStackTraceHandling(t *testing.T) {

var longStackTraceBuilder strings.Builder
for i := 0; i < 1000; i++ {
longStackTraceBuilder.WriteString(fmt.Sprintf("goroutine %d [running]:\n", i))
longStackTraceBuilder.WriteString(fmt.Sprintf("/path/to/long/stack/trace%d.go:%d +0x26\n", i, i))
}
longStackTrace := longStackTraceBuilder.String()
state := "Error Variables:\nStress Test Error"

edgeList := FormatStacktraceToEdgeList(longStackTrace, "Stress Test Error")
edgeList := FormatStacktraceToEdgeList(longStackTrace, "Stress Test Error", state)
assert.NotNil(t, edgeList)
assert.Greater(t, len(edgeList.EdgeList), 0)
}

func TestConcurrentStackTraceFormatting(t *testing.T) {
stackTrace := generateTestStackTrace()
err := fmt.Errorf("concurrent error")
state := CaptureVariableState(err)

concurrentRuns := 100

results := make(chan EdgeList, concurrentRuns)

for i := 0; i < concurrentRuns; i++ {
go func() {
results <- FormatStacktraceToEdgeList(stackTrace, err)
results <- FormatStacktraceToEdgeList(stackTrace, err, state)
}()
}

Expand Down
Loading