From 15492ee8331ff0642bd7379da4c4f65251fe3141 Mon Sep 17 00:00:00 2001 From: phgermanov Date: Tue, 29 Jul 2025 13:38:49 +0300 Subject: [PATCH] feat(splunk): enhance error details (#5429) --- pkg/splunk/data.go | 1 + pkg/splunk/splunk.go | 14 +++++++++ pkg/splunk/splunk_test.go | 65 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 80 insertions(+) diff --git a/pkg/splunk/data.go b/pkg/splunk/data.go index 7e51eceb3..b7525f948 100644 --- a/pkg/splunk/data.go +++ b/pkg/splunk/data.go @@ -28,6 +28,7 @@ type MonitoringData struct { Duration string `json:"Duration,omitempty"` ErrorCode string `json:"ErrorCode,omitempty"` ErrorCategory string `json:"ErrorCategory,omitempty"` + ErrorMessage string `json:"ErrorMessage,omitempty"` CorrelationID string `json:"CorrelationId,omitempty"` CommitHash string `json:"CommitHash,omitempty"` Branch string `json:"Branch,omitempty"` diff --git a/pkg/splunk/splunk.go b/pkg/splunk/splunk.go index b851ee5c8..6feded684 100644 --- a/pkg/splunk/splunk.go +++ b/pkg/splunk/splunk.go @@ -3,6 +3,7 @@ package splunk import ( "bytes" "encoding/json" + "fmt" "io" "net/http" "os" @@ -110,6 +111,18 @@ func readCommonPipelineEnvironment(filePath string) string { } func (s *Splunk) prepareTelemetry(telemetryData telemetry.Data) MonitoringData { + var errorMessage string + + if telemetryData.CustomData.ErrorCode != "0" { + if fatalErrorDetail := log.GetFatalErrorDetail(); fatalErrorDetail != nil { + var errorDetail map[string]any + if err := json.Unmarshal(fatalErrorDetail, &errorDetail); err == nil { + if errorVal, exists := errorDetail["error"]; exists && errorVal != nil { + errorMessage = fmt.Sprintf("%v", errorVal) + } + } + } + } monitoringData := MonitoringData{ PipelineUrlHash: telemetryData.PipelineURLHash, @@ -123,6 +136,7 @@ func (s *Splunk) prepareTelemetry(telemetryData telemetry.Data) MonitoringData { Duration: telemetryData.CustomData.Duration, ErrorCode: telemetryData.CustomData.ErrorCode, ErrorCategory: telemetryData.CustomData.ErrorCategory, + ErrorMessage: errorMessage, CorrelationID: s.correlationID, CommitHash: readCommonPipelineEnvironment("git/headCommitId"), Branch: readCommonPipelineEnvironment("git/branch"), diff --git a/pkg/splunk/splunk_test.go b/pkg/splunk/splunk_test.go index 955003898..becca5a5d 100644 --- a/pkg/splunk/splunk_test.go +++ b/pkg/splunk/splunk_test.go @@ -15,6 +15,8 @@ import ( "github.com/SAP/jenkins-library/pkg/log" "github.com/SAP/jenkins-library/pkg/telemetry" "github.com/jarcoal/httpmock" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestInitialize(t *testing.T) { @@ -376,6 +378,7 @@ func Test_prepareTelemetry(t *testing.T) { Duration: "1234", ErrorCode: "0", ErrorCategory: "Undefined", + ErrorMessage: "", CorrelationID: "Correlation-Test", CommitHash: "N/A", Branch: "N/A", @@ -399,6 +402,66 @@ func Test_prepareTelemetry(t *testing.T) { } } +func Test_prepareTelemetry_ErrorMessageCapture(t *testing.T) { + tests := []struct { + name string + errorCode string + fatalErrorDetail map[string]any + expectedMessage string + }{ + { + name: "captures error message when step fails", + errorCode: "1", + fatalErrorDetail: map[string]any{ + "error": "failed publishing artifact: npm ERR! 401 Unauthorized - authentication required", + }, + expectedMessage: "failed publishing artifact: npm ERR! 401 Unauthorized - authentication required", + }, + { + name: "no error message when step succeeds", + errorCode: "0", + fatalErrorDetail: nil, + expectedMessage: "", + }, + { + name: "no error message when step fails but no fatal error detail available", + errorCode: "1", + fatalErrorDetail: nil, + expectedMessage: "", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.fatalErrorDetail != nil { + errorDetailBytes, err := json.Marshal(tt.fatalErrorDetail) + require.NoError(t, err) + log.SetFatalErrorDetail(errorDetailBytes) + } else { + log.SetFatalErrorDetail(nil) + } + defer log.SetFatalErrorDetail(nil) + + telemetryData := telemetry.Data{ + BaseData: telemetry.BaseData{ + Orchestrator: "Jenkins", + }, + CustomData: telemetry.CustomData{ + ErrorCode: tt.errorCode, + }, + } + + splunkClient := &Splunk{} + err := splunkClient.Initialize("test", "url", "token", "index", false) + require.NoError(t, err) + + result := splunkClient.prepareTelemetry(telemetryData) + + assert.Equal(t, tt.expectedMessage, result.ErrorMessage) + }) + } +} + func Test_tryPostMessages(t *testing.T) { type args struct { telemetryData MonitoringData @@ -421,6 +484,7 @@ func Test_tryPostMessages(t *testing.T) { Duration: "12345678", ErrorCode: "0", ErrorCategory: "undefined", + ErrorMessage: "", CorrelationID: "123", CommitHash: "a6bc", Branch: "prod", @@ -443,6 +507,7 @@ func Test_tryPostMessages(t *testing.T) { Duration: "12345678", ErrorCode: "0", ErrorCategory: "undefined", + ErrorMessage: "", CorrelationID: "123", CommitHash: "a6bc", Branch: "prod",