github.com/ouraigua/jenkins-library@v0.0.0-20231028010029-fbeaf2f3aa9b/pkg/telemetry/telemetry.go (about) 1 package telemetry 2 3 import ( 4 "crypto/sha1" 5 "encoding/json" 6 "fmt" 7 "github.com/SAP/jenkins-library/pkg/orchestrator" 8 "strconv" 9 "time" 10 11 "net/http" 12 "net/url" 13 14 piperhttp "github.com/SAP/jenkins-library/pkg/http" 15 "github.com/SAP/jenkins-library/pkg/log" 16 ) 17 18 // eventType 19 const eventType = "library-os-ng" 20 21 // actionName 22 const actionName = "Piper Library OS" 23 24 // LibraryRepository that is passed into with -ldflags 25 var LibraryRepository string 26 27 // Telemetry struct which holds necessary infos about telemetry 28 type Telemetry struct { 29 baseData BaseData 30 baseMetaData BaseMetaData 31 data Data 32 provider orchestrator.OrchestratorSpecificConfigProviding 33 disabled bool 34 client *piperhttp.Client 35 CustomReportingDsn string 36 CustomReportingToken string 37 customClient *piperhttp.Client 38 BaseURL string 39 Endpoint string 40 SiteID string 41 } 42 43 // Initialize sets up the base telemetry data and is called in generated part of the steps 44 func (t *Telemetry) Initialize(telemetryDisabled bool, stepName string) { 45 t.disabled = telemetryDisabled 46 47 provider, err := orchestrator.NewOrchestratorSpecificConfigProvider() 48 if err != nil || provider == nil { 49 log.Entry().Warningf("could not get orchestrator config provider, leads to insufficient data") 50 provider = &orchestrator.UnknownOrchestratorConfigProvider{} 51 } 52 t.provider = provider 53 54 if t.client == nil { 55 t.client = &piperhttp.Client{} 56 } 57 58 t.client.SetOptions(piperhttp.ClientOptions{MaxRequestDuration: 5 * time.Second, MaxRetries: -1}) 59 60 if t.BaseURL == "" { 61 //SWA baseURL 62 t.BaseURL = "https://webanalytics.cfapps.eu10.hana.ondemand.com" 63 } 64 if t.Endpoint == "" { 65 // SWA endpoint 66 t.Endpoint = "/tracker/log" 67 } 68 if len(LibraryRepository) == 0 { 69 LibraryRepository = "https://github.com/n/a" 70 } 71 72 if t.SiteID == "" { 73 t.SiteID = "827e8025-1e21-ae84-c3a3-3f62b70b0130" 74 } 75 76 t.baseData = BaseData{ 77 Orchestrator: provider.OrchestratorType(), 78 StageName: provider.GetStageName(), 79 URL: LibraryRepository, 80 ActionName: actionName, 81 EventType: eventType, 82 StepName: stepName, 83 SiteID: t.SiteID, 84 PipelineURLHash: t.getPipelineURLHash(), // http://server:port/jenkins/job/foo/ 85 BuildURLHash: t.getBuildURLHash(), // http://server:port/jenkins/job/foo/15/ 86 } 87 t.baseMetaData = baseMetaData 88 } 89 90 func (t *Telemetry) getPipelineURLHash() string { 91 jobURL := t.provider.GetJobURL() 92 return t.toSha1OrNA(jobURL) 93 } 94 95 func (t *Telemetry) getBuildURLHash() string { 96 buildURL := t.provider.GetBuildURL() 97 return t.toSha1OrNA(buildURL) 98 } 99 100 func (t *Telemetry) toSha1OrNA(input string) string { 101 if len(input) == 0 { 102 return "n/a" 103 } 104 return fmt.Sprintf("%x", sha1.Sum([]byte(input))) 105 } 106 107 // SetData sets the custom telemetry data and base data into the Data object 108 func (t *Telemetry) SetData(customData *CustomData) { 109 t.data = Data{ 110 BaseData: t.baseData, 111 BaseMetaData: t.baseMetaData, 112 CustomData: *customData, 113 } 114 } 115 116 // GetData returns telemetryData 117 func (t *Telemetry) GetData() Data { 118 return t.data 119 } 120 121 // Send telemetry information to SWA 122 func (t *Telemetry) Send() { 123 // always log step telemetry data to logfile used for internal use-case 124 t.logStepTelemetryData() 125 126 // skip if telemetry is disabled 127 if t.disabled { 128 return 129 } 130 131 request, _ := url.Parse(t.BaseURL) 132 request.Path = t.Endpoint 133 request.RawQuery = t.data.toPayloadString() 134 log.Entry().WithField("request", request.String()).Debug("Sending telemetry data") 135 t.client.SendRequest(http.MethodGet, request.String(), nil, nil, nil) 136 } 137 138 func (t *Telemetry) logStepTelemetryData() { 139 140 var fatalError map[string]interface{} 141 if t.data.CustomData.ErrorCode != "0" && log.GetFatalErrorDetail() != nil { 142 // retrieve the error information from the logCollector 143 err := json.Unmarshal(log.GetFatalErrorDetail(), &fatalError) 144 if err != nil { 145 log.Entry().WithError(err).Warn("could not unmarshal fatal error struct") 146 } 147 } 148 149 // Subtracts the duration from now to estimate the step start time 150 i, err := strconv.ParseInt(t.data.CustomData.Duration, 10, 64) 151 duration := time.Millisecond * time.Duration(i) 152 starTime := time.Now().UTC().Add(-duration) 153 154 stepTelemetryData := StepTelemetryData{ 155 StepStartTime: starTime.String(), 156 PipelineURLHash: t.data.PipelineURLHash, 157 BuildURLHash: t.data.BuildURLHash, 158 StageName: t.data.StageName, 159 StepName: t.data.BaseData.StepName, 160 ErrorCode: t.data.CustomData.ErrorCode, 161 StepDuration: t.data.CustomData.Duration, 162 ErrorCategory: t.data.CustomData.ErrorCategory, 163 ErrorDetail: fatalError, 164 CorrelationID: t.provider.GetBuildURL(), 165 PiperCommitHash: t.data.CustomData.PiperCommitHash, 166 } 167 stepTelemetryJSON, err := json.Marshal(stepTelemetryData) 168 if err != nil { 169 log.Entry().Error("could not marshal step telemetry data") 170 log.Entry().Infof("Step telemetry data: {n/a}") 171 } else { 172 // log step telemetry data, changes here need to change the regex in the internal piper lib 173 log.Entry().Infof("Step telemetry data:%v", string(stepTelemetryJSON)) 174 } 175 }