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  }