@@ -6,11 +6,15 @@ import (
66 "sort"
77 "strings"
88 "time"
9+
10+ "github.com/go-resty/resty/v2"
11+ "github.com/rs/zerolog/log"
12+ "golang.org/x/sync/errgroup"
913)
1014
1115// TestReport reports on the parameters and results of one to many test runs
1216type TestReport struct {
13- ReportID string `json:"report_id "`
17+ ID string `json:"id "`
1418 GoProject string `json:"go_project"`
1519 HeadSHA string `json:"head_sha"`
1620 BaseSHA string `json:"base_sha"`
@@ -25,6 +29,9 @@ type TestReport struct {
2529
2630// TestResult contains the results and outputs of a single test
2731type TestResult struct {
32+ // ReportID is the ID of the report this test result belongs to
33+ // used mostly for Splunk logging
34+ ReportID string `json:"report_id"`
2835 TestName string `json:"test_name"`
2936 TestPackage string `json:"test_package"`
3037 PackagePanic bool `json:"package_panic"`
@@ -79,7 +86,7 @@ const (
7986
8087// SplunkTestReport is the full wrapper structure sent to Splunk for the full test report (sans results)
8188type SplunkTestReport struct {
82- Event SplunkTestResultEvent `json:"event"` // https://docs.splunk.com/Splexicon:Event
89+ Event SplunkTestReportEvent `json:"event"` // https://docs.splunk.com/Splexicon:Event
8390 SourceType string `json:"sourcetype"` // https://docs.splunk.com/Splexicon:Sourcetype
8491}
8592
@@ -180,7 +187,7 @@ func aggregate(reportChan <-chan *TestReport, errChan <-chan error, opts *aggreg
180187 excludedTests := map [string ]struct {}{}
181188 selectedTests := map [string ]struct {}{}
182189
183- fullReport .ReportID = opts .reportID
190+ fullReport .ID = opts .reportID
184191 for report := range reportChan {
185192 if fullReport .GoProject == "" {
186193 fullReport .GoProject = report .GoProject
@@ -196,6 +203,7 @@ func aggregate(reportChan <-chan *TestReport, errChan <-chan error, opts *aggreg
196203 selectedTests [test ] = struct {}{}
197204 }
198205 for _ , result := range report .Results {
206+ result .ReportID = opts .reportID
199207 key := result .TestName + "|" + result .TestPackage
200208 if existing , found := testMap [key ]; found {
201209 existing = mergeTestResults (existing , result )
@@ -218,18 +226,97 @@ func aggregate(reportChan <-chan *TestReport, errChan <-chan error, opts *aggreg
218226 fullReport .SelectedTests = append (fullReport .SelectedTests , test )
219227 }
220228
229+ // Send report to Splunk before adding test results
230+ if opts .splunkURL != "" {
231+ err := sendReportToSplunk (opts .splunkURL , opts .splunkToken , opts .splunkEvent , fullReport )
232+ if err != nil {
233+ log .Error ().Err (err ).Msg ("Error sending report to Splunk" )
234+ } else {
235+ log .Debug ().Str ("event" , string (opts .splunkEvent )).Msg ("Report sent to Splunk" )
236+ }
237+ }
238+
221239 // Prepare final results
240+ eg := errgroup.Group {}
222241 var aggregatedResults []TestResult
223- for _ , result := range testMap {
242+ for _ , r := range testMap {
243+ result := r
224244 aggregatedResults = append (aggregatedResults , result )
245+ if opts .splunkURL != "" {
246+ eg .Go (func () error {
247+ return sendResultsToSplunk (opts .splunkURL , opts .splunkToken , opts .splunkEvent , result )
248+ })
249+ }
225250 }
226251
227252 sortTestResults (aggregatedResults )
228253 fullReport .Results = aggregatedResults
229254
255+ if splunkErr := eg .Wait (); splunkErr != nil {
256+ log .Error ().Err (splunkErr ).Msg ("Error sending results to Splunk" )
257+ } else {
258+ log .Debug ().Str ("event" , string (opts .splunkEvent )).Msg ("All results sent to Splunk" )
259+ }
230260 return fullReport , nil
231261}
232262
263+ // sendReportToSplunk sends meta test report data to Splunk
264+ func sendReportToSplunk (url , token string , event SplunkEvent , report * TestReport ) error {
265+ client := resty .New ()
266+ client .AddRetryAfterErrorCondition ().SetRetryCount (3 ).SetRetryWaitTime (5 * time .Second )
267+ resp , err := client .R ().
268+ SetHeader ("Authorization" , fmt .Sprintf ("Splunk %s" , token )).
269+ SetHeader ("Content-Type" , "application/json" ).
270+ SetBody (SplunkTestReport {
271+ Event : SplunkTestReportEvent {
272+ Event : event ,
273+ Type : Report ,
274+ Data : * report ,
275+ },
276+ SourceType : "flakeguard_json" ,
277+ }).
278+ Post (url )
279+ if err != nil {
280+ return err
281+ }
282+ if resp .IsError () {
283+ return fmt .Errorf ("error sending report to Splunk: %s" , resp .String ())
284+ }
285+ return nil
286+ }
287+
288+ func sendResultsToSplunk (url , token string , event SplunkEvent , results ... TestResult ) error {
289+ client := resty .New ()
290+ client .AddRetryAfterErrorCondition ().SetRetryCount (3 ).SetRetryWaitTime (5 * time .Second )
291+ eg := errgroup.Group {}
292+ for _ , r := range results {
293+ result := r
294+ eg .Go (func () error {
295+ resp , err := client .R ().
296+ SetHeader ("Authorization" , fmt .Sprintf ("Splunk %s" , token )).
297+ SetHeader ("Content-Type" , "application/json" ).
298+ SetBody (SplunkTestResult {
299+ Event : SplunkTestResultEvent {
300+ Event : event ,
301+ Type : Result ,
302+ Data : result ,
303+ },
304+ SourceType : "flakeguard_json" ,
305+ }).
306+ Post (url )
307+ if err != nil {
308+ return err
309+ }
310+ if resp .IsError () {
311+ return fmt .Errorf ("error sending result to Splunk: %s" , resp .String ())
312+ }
313+ return nil
314+ })
315+ }
316+
317+ return eg .Wait ()
318+ }
319+
233320func aggregateFromReports (opts * aggregateOptions , reports ... * TestReport ) (* TestReport , error ) {
234321 reportChan := make (chan * TestReport , len (reports ))
235322 errChan := make (chan error , 1 )
0 commit comments