github.com/Axway/agent-sdk@v1.1.101/pkg/transaction/eventgenerator.go (about)

     1  package transaction
     2  
     3  import (
     4  	"encoding/json"
     5  	"strings"
     6  	"time"
     7  
     8  	"github.com/Axway/agent-sdk/pkg/agent"
     9  	"github.com/Axway/agent-sdk/pkg/agent/cache"
    10  	"github.com/Axway/agent-sdk/pkg/apic"
    11  	v1 "github.com/Axway/agent-sdk/pkg/apic/apiserver/models/api/v1"
    12  	catalog "github.com/Axway/agent-sdk/pkg/apic/apiserver/models/catalog/v1alpha1"
    13  	management "github.com/Axway/agent-sdk/pkg/apic/apiserver/models/management/v1alpha1"
    14  	"github.com/Axway/agent-sdk/pkg/traceability"
    15  	"github.com/Axway/agent-sdk/pkg/traceability/sampling"
    16  	"github.com/Axway/agent-sdk/pkg/transaction/metric"
    17  	"github.com/Axway/agent-sdk/pkg/transaction/models"
    18  	transutil "github.com/Axway/agent-sdk/pkg/transaction/util"
    19  	"github.com/Axway/agent-sdk/pkg/util/errors"
    20  	hc "github.com/Axway/agent-sdk/pkg/util/healthcheck"
    21  	"github.com/Axway/agent-sdk/pkg/util/log"
    22  	"github.com/elastic/beats/v7/libbeat/beat"
    23  	"github.com/elastic/beats/v7/libbeat/common"
    24  )
    25  
    26  // EventGenerator - Create the events to be published to Condor
    27  type EventGenerator interface {
    28  	CreateEvent(logEvent LogEvent, eventTime time.Time, metaData common.MapStr, fields common.MapStr, privateData interface{}) (event beat.Event, err error) // DEPRECATED
    29  	CreateEvents(summaryEvent LogEvent, detailEvents []LogEvent, eventTime time.Time, metaData common.MapStr, fields common.MapStr, privateData interface{}) (events []beat.Event, err error)
    30  	SetUseTrafficForAggregation(useTrafficForAggregation bool)
    31  }
    32  
    33  // Generator - Create the events to be published to Condor
    34  type Generator struct {
    35  	shouldAddFields                bool
    36  	shouldUseTrafficForAggregation bool
    37  	logger                         log.FieldLogger
    38  }
    39  
    40  // NewEventGenerator - Create a new event generator
    41  func NewEventGenerator() EventGenerator {
    42  	logger := log.NewFieldLogger().
    43  		WithPackage("sdk.transaction.eventgenerator").
    44  		WithComponent("eventgenerator")
    45  	eventGen := &Generator{
    46  		shouldAddFields:                !traceability.IsHTTPTransport(),
    47  		shouldUseTrafficForAggregation: true,
    48  		logger:                         logger,
    49  	}
    50  	hc.RegisterHealthcheck("Event Generator", "eventgen", eventGen.healthcheck)
    51  
    52  	return eventGen
    53  }
    54  
    55  // SetUseTrafficForAggregation - set the flag to use traffic events for aggregation.
    56  func (e *Generator) SetUseTrafficForAggregation(useTrafficForAggregation bool) {
    57  	e.shouldUseTrafficForAggregation = useTrafficForAggregation
    58  }
    59  
    60  // CreateEvent - Creates a new event to be sent to Amplify Observability, expects sampling is handled by agent
    61  func (e *Generator) CreateEvent(logEvent LogEvent, eventTime time.Time, metaData common.MapStr, eventFields common.MapStr, privateData interface{}) (beat.Event, error) {
    62  	// if CreateEvent is being used, sampling will not work, so all events need to be sent
    63  	if metaData == nil {
    64  		metaData = common.MapStr{}
    65  	}
    66  	metaData.Put(sampling.SampleKey, true)
    67  
    68  	if logEvent.TransactionSummary != nil {
    69  
    70  		e.processTxnSummary(logEvent)
    71  		e.trackMetrics(logEvent, 0)
    72  	}
    73  
    74  	return e.createEvent(logEvent, eventTime, metaData, eventFields, privateData)
    75  }
    76  
    77  func (e *Generator) trackMetrics(summaryEvent LogEvent, bytes int64) {
    78  	if e.shouldUseTrafficForAggregation {
    79  		apiDetails := models.APIDetails{
    80  			ID:       summaryEvent.TransactionSummary.Proxy.ID,
    81  			Name:     summaryEvent.TransactionSummary.Proxy.Name,
    82  			Revision: summaryEvent.TransactionSummary.Proxy.Revision,
    83  			Stage:    summaryEvent.TransactionSummary.Proxy.Stage,
    84  			Version:  summaryEvent.TransactionSummary.Proxy.Version,
    85  		}
    86  
    87  		if summaryEvent.TransactionSummary.Team != nil {
    88  			apiDetails.TeamID = summaryEvent.TransactionSummary.Team.ID
    89  		}
    90  
    91  		statusCode := summaryEvent.TransactionSummary.StatusDetail
    92  		duration := summaryEvent.TransactionSummary.Duration
    93  		appDetails := models.AppDetails{}
    94  		if summaryEvent.TransactionSummary.Application != nil {
    95  			appDetails.Name = summaryEvent.TransactionSummary.Application.Name
    96  			appDetails.ID = strings.TrimLeft(summaryEvent.TransactionSummary.Application.ID, SummaryEventApplicationIDPrefix)
    97  		}
    98  
    99  		collector := metric.GetMetricCollector()
   100  		if collector != nil {
   101  			metricDetail := metric.Detail{
   102  				APIDetails: apiDetails,
   103  				StatusCode: statusCode,
   104  				Duration:   int64(duration),
   105  				Bytes:      bytes,
   106  				AppDetails: appDetails,
   107  			}
   108  			collector.AddMetricDetail(metricDetail)
   109  		}
   110  	}
   111  }
   112  
   113  // CreateEvent - Creates a new event to be sent to Amplify Observability
   114  func (e *Generator) createEvent(logEvent LogEvent, eventTime time.Time, metaData common.MapStr, eventFields common.MapStr, privateData interface{}) (beat.Event, error) {
   115  	event := beat.Event{}
   116  	serializedLogEvent, err := json.Marshal(logEvent)
   117  	if err != nil {
   118  		return event, err
   119  	}
   120  
   121  	eventData := eventFields
   122  	// No need to get the other field data if not being sampled
   123  	if sampled, found := metaData[sampling.SampleKey]; found && sampled.(bool) {
   124  		eventData, err = e.createEventData(serializedLogEvent, eventFields)
   125  	}
   126  	if err != nil {
   127  		return event, err
   128  	}
   129  
   130  	return beat.Event{
   131  		Timestamp: eventTime,
   132  		Meta:      metaData,
   133  		Private:   privateData,
   134  		Fields:    eventData,
   135  	}, nil
   136  }
   137  
   138  // CreateEvents - Creates new events to be sent to Amplify Observability
   139  func (e *Generator) CreateEvents(summaryEvent LogEvent, detailEvents []LogEvent, eventTime time.Time, metaData common.MapStr, eventFields common.MapStr, privateData interface{}) ([]beat.Event, error) {
   140  	events := make([]beat.Event, 0)
   141  
   142  	// See if the uri is in the api exceptions list
   143  	if e.isInAPIExceptionsList(detailEvents) {
   144  		e.logger.Debug("Found api path in traceability api exceptions list.  Ignore transaction event")
   145  		return events, nil
   146  	}
   147  
   148  	// Check to see if marketplace provisioning/subs is enabled
   149  	err := e.processTxnSummary(summaryEvent)
   150  	if err != nil {
   151  		return nil, err
   152  	}
   153  
   154  	//if no summary is sent then prepare the array of TransactionEvents for publishing
   155  	if summaryEvent == (LogEvent{}) {
   156  		return e.handleTransactionEvents(detailEvents, eventTime, metaData, eventFields, privateData)
   157  	}
   158  
   159  	shouldSample, err := sampling.ShouldSampleTransaction(e.createSamplingTransactionDetails(summaryEvent))
   160  	if err != nil {
   161  		return events, err
   162  	}
   163  	if shouldSample {
   164  		if metaData == nil {
   165  			metaData = common.MapStr{}
   166  		}
   167  		metaData.Put(sampling.SampleKey, true)
   168  	}
   169  
   170  	newEvent, err := e.createEvent(summaryEvent, eventTime, metaData, eventFields, privateData)
   171  
   172  	if err != nil {
   173  		return events, err
   174  	}
   175  
   176  	events = append(events, newEvent)
   177  	for _, event := range detailEvents {
   178  		newEvent, err := e.createEvent(event, eventTime, metaData, eventFields, privateData)
   179  		if err == nil {
   180  			events = append(events, newEvent)
   181  		}
   182  	}
   183  
   184  	bytes := 0
   185  	if len(detailEvents) > 0 {
   186  		if httpEvent, ok := detailEvents[0].TransactionEvent.Protocol.(*Protocol); ok {
   187  			bytes = httpEvent.BytesSent
   188  		}
   189  	}
   190  	e.trackMetrics(summaryEvent, int64(bytes))
   191  
   192  	return events, nil
   193  }
   194  
   195  func (e *Generator) handleTransactionEvents(detailEvents []LogEvent, eventTime time.Time, metaData common.MapStr, eventFields common.MapStr, privateData interface{}) ([]beat.Event, error) {
   196  	events := make([]beat.Event, 0)
   197  	for _, event := range detailEvents {
   198  		if metaData == nil {
   199  			metaData = common.MapStr{}
   200  		}
   201  		metaData.Put(sampling.SampleKey, true)
   202  		newEvent, err := e.createEvent(event, eventTime, metaData, eventFields, privateData)
   203  		if err == nil {
   204  			events = append(events, newEvent)
   205  		}
   206  	}
   207  
   208  	return events, nil
   209  
   210  }
   211  
   212  func (e *Generator) processTxnSummary(summaryEvent LogEvent) error {
   213  	// only process if there is a central client and marketplace subs are enabled
   214  	if agent.GetCentralClient() == nil {
   215  		return nil
   216  	}
   217  	if summaryEvent.TransactionSummary != nil {
   218  		txnSummary := e.updateTxnSummaryByAccessRequest(summaryEvent)
   219  		if txnSummary != nil {
   220  			jsonData, err := json.Marshal(&txnSummary)
   221  			if err != nil {
   222  				return err
   223  			}
   224  			e.logger.Trace(string(jsonData))
   225  			summaryEvent.TransactionSummary = txnSummary
   226  		}
   227  	}
   228  	return nil
   229  }
   230  
   231  // updateTxnSummaryByAccessRequest - get the consumer information to add to transaction event.  If we don't have any
   232  //
   233  //	information we need to get the consumer information, then we just return nil
   234  func (e *Generator) updateTxnSummaryByAccessRequest(summaryEvent LogEvent) *Summary {
   235  	cacheManager := agent.GetCacheManager()
   236  
   237  	// get proxy information
   238  	if summaryEvent.TransactionSummary.Proxy == nil {
   239  		e.logger.Debug("proxy information is not available, no consumer information attached")
   240  		return nil
   241  	}
   242  
   243  	// Go get the access request and managed app
   244  	accessRequest, managedApp := e.getAccessRequest(cacheManager, summaryEvent)
   245  
   246  	// Update the consumer details
   247  	summaryEvent.TransactionSummary.ConsumerDetails = transutil.UpdateWithConsumerDetails(accessRequest, managedApp, e.logger)
   248  
   249  	// Update provider details
   250  	updatedSummaryEvent := updateWithProviderDetails(accessRequest, managedApp, summaryEvent.TransactionSummary, e.logger)
   251  
   252  	return updatedSummaryEvent
   253  }
   254  
   255  // getAccessRequest -
   256  func (e *Generator) getAccessRequest(cacheManager cache.Manager, summaryEvent LogEvent) (*management.AccessRequest, *v1.ResourceInstance) {
   257  	appName := unknown
   258  	apiID := summaryEvent.TransactionSummary.Proxy.ID
   259  	stage := summaryEvent.TransactionSummary.Proxy.Stage
   260  	version := summaryEvent.TransactionSummary.Proxy.Version
   261  	e.logger.
   262  		WithField("api-id", apiID).
   263  		WithField("stage", stage).
   264  		Trace("transaction summary proxy information")
   265  
   266  	if summaryEvent.TransactionSummary.Application != nil {
   267  		appName = summaryEvent.TransactionSummary.Application.Name
   268  		e.logger.
   269  			WithField("app-name", appName).
   270  			Trace("transaction summary dataplane details application name")
   271  	}
   272  
   273  	// get the managed application
   274  	managedApp := cacheManager.GetManagedApplicationByName(appName)
   275  	if managedApp == nil {
   276  		e.logger.
   277  			WithField("app-name", appName).
   278  			Trace("could not get managed application by name, no consumer information attached")
   279  		return nil, nil
   280  	}
   281  	e.logger.
   282  		WithField("app-name", appName).
   283  		WithField("managed-app-name", managedApp.Name).
   284  		Trace("managed application info")
   285  
   286  	// get the access request
   287  	accessRequest := transutil.GetAccessRequest(cacheManager, managedApp, apiID, stage, version)
   288  	if accessRequest == nil {
   289  		e.logger.
   290  			Warn("could not get access request, no consumer information attached")
   291  		return nil, nil
   292  	}
   293  	e.logger.
   294  		WithField("managed-app-name", managedApp.Name).
   295  		WithField("api-id", apiID).
   296  		WithField("stage", stage).
   297  		WithField("access-request-name", accessRequest.Name).
   298  		Trace("managed application info")
   299  
   300  	return accessRequest, managedApp
   301  }
   302  
   303  // createSamplingTransactionDetails -
   304  func (e *Generator) createSamplingTransactionDetails(summaryEvent LogEvent) sampling.TransactionDetails {
   305  	var status string
   306  	var apiID string
   307  	var subID string
   308  
   309  	if summaryEvent.TransactionSummary != nil {
   310  		status = summaryEvent.TransactionSummary.Status
   311  		if summaryEvent.TransactionSummary.Proxy != nil {
   312  			apiID = summaryEvent.TransactionSummary.Proxy.ID
   313  		}
   314  
   315  		consumerDetails := summaryEvent.TransactionSummary.ConsumerDetails
   316  		if consumerDetails != nil && consumerDetails.Subscription != nil {
   317  			subID = consumerDetails.Subscription.ID
   318  		}
   319  	}
   320  
   321  	return sampling.TransactionDetails{
   322  		Status: status,
   323  		APIID:  apiID,
   324  		SubID:  subID,
   325  	}
   326  }
   327  
   328  // Validate APIs in the traceability exceptions list
   329  func (e *Generator) isInAPIExceptionsList(logEvents []LogEvent) bool {
   330  
   331  	// Sanity check.
   332  	if len(logEvents) == 0 {
   333  		return false
   334  	}
   335  
   336  	// Check first leg for URI.  Use the raw value before redaction happens
   337  	uriRaw := ""
   338  
   339  	if httpEvent, ok := logEvents[0].TransactionEvent.Protocol.(*Protocol); ok {
   340  		uriRaw = httpEvent.uriRaw
   341  	}
   342  
   343  	// Get the api exceptions list
   344  	return traceability.ShouldIgnoreEvent(uriRaw)
   345  
   346  }
   347  
   348  // healthcheck -
   349  func (e *Generator) healthcheck(name string) *hc.Status {
   350  	// Create the default return
   351  	status := &hc.Status{
   352  		Result:  hc.OK,
   353  		Details: "",
   354  	}
   355  
   356  	if percentage, _ := sampling.GetGlobalSamplingPercentage(); percentage == 0 {
   357  		// Do not execute the healthcheck when sampling is 0
   358  		return status
   359  	}
   360  
   361  	_, err := agent.GetCentralAuthToken()
   362  	if err != nil {
   363  		status = &hc.Status{
   364  			Result:  hc.FAIL,
   365  			Details: errors.Wrap(apic.ErrAuthenticationCall, err.Error()).Error(),
   366  		}
   367  	}
   368  
   369  	return status
   370  }
   371  
   372  func (e *Generator) createEventData(message []byte, eventFields common.MapStr) (eventData map[string]interface{}, err error) {
   373  	eventData = make(map[string]interface{})
   374  	// Copy event fields if specified
   375  	if len(eventFields) > 0 {
   376  		for key, value := range eventFields {
   377  			// Ignore message field as it gets added with this method
   378  			if key != "message" {
   379  				eventData[key] = value
   380  			}
   381  		}
   382  	}
   383  
   384  	eventData["message"] = string(message)
   385  	if e.shouldAddFields {
   386  		fields, err := e.createEventFields()
   387  		if err != nil {
   388  			return nil, err
   389  		}
   390  		eventData["fields"] = fields
   391  	}
   392  	return eventData, err
   393  }
   394  
   395  func (e *Generator) createEventFields() (fields map[string]string, err error) {
   396  	fields = make(map[string]string)
   397  	var token string
   398  	if token, err = agent.GetCentralAuthToken(); err != nil {
   399  		return
   400  	}
   401  	fields["token"] = token
   402  	fields[traceability.FlowHeader] = traceability.TransactionFlow
   403  	return
   404  }
   405  
   406  // updateWithProviderDetails -
   407  func updateWithProviderDetails(accessRequest *management.AccessRequest, managedApp *v1.ResourceInstance, summaryEvent *Summary, log log.FieldLogger) *Summary {
   408  
   409  	// Set default to provider details in case access request or managed apps comes back nil
   410  	summaryEvent.AssetResource = &models.AssetResource{
   411  		ID:   unknown,
   412  		Name: unknown,
   413  	}
   414  
   415  	summaryEvent.Product = &models.Product{
   416  		ID:          unknown,
   417  		Name:        unknown,
   418  		VersionID:   unknown,
   419  		VersionName: unknown,
   420  	}
   421  
   422  	summaryEvent.ProductPlan = &models.ProductPlan{
   423  		ID: unknown,
   424  	}
   425  
   426  	summaryEvent.Quota = &models.Quota{
   427  		ID: unknown,
   428  	}
   429  
   430  	if accessRequest == nil || managedApp == nil {
   431  		log.Trace("access request or managed app is nil. Setting default values to unknown")
   432  		return summaryEvent
   433  	}
   434  
   435  	productRef := accessRequest.GetReferenceByGVK(catalog.ProductGVK())
   436  	if productRef.ID == "" || productRef.Name == "" {
   437  		log.Trace("could not get product information, setting product to unknown")
   438  	} else {
   439  		summaryEvent.Product.ID = productRef.ID
   440  		summaryEvent.Product.Name = productRef.Name
   441  	}
   442  
   443  	productReleaseRef := accessRequest.GetReferenceByGVK(catalog.ProductReleaseGVK())
   444  	if productReleaseRef.ID == "" || productReleaseRef.Name == "" {
   445  		log.Trace("could not get product release information, setting product release to unknown")
   446  	} else {
   447  		summaryEvent.Product.VersionID = productReleaseRef.ID
   448  		summaryEvent.Product.VersionName = productReleaseRef.Name
   449  	}
   450  	log.
   451  		WithField("product-id", summaryEvent.Product.ID).
   452  		WithField("product-name", summaryEvent.Product.Name).
   453  		WithField("product-version-id", summaryEvent.Product.VersionID).
   454  		WithField("product-version-name", summaryEvent.Product.VersionName).
   455  		Trace("product information")
   456  
   457  	assetResourceRef := accessRequest.GetReferenceByGVK(catalog.AssetResourceGVK())
   458  	if assetResourceRef.ID == "" || assetResourceRef.Name == "" {
   459  		log.Trace("could not get asset resource, setting asset resource to unknown")
   460  	} else {
   461  		summaryEvent.AssetResource.ID = assetResourceRef.ID
   462  		summaryEvent.AssetResource.Name = assetResourceRef.Name
   463  	}
   464  	log.
   465  		WithField("asset-resource-id", summaryEvent.AssetResource.ID).
   466  		WithField("asset-resource-name", summaryEvent.AssetResource.Name).
   467  		Trace("asset resource information")
   468  
   469  	api := &models.APIDetails{
   470  		ID:                 summaryEvent.Proxy.ID,
   471  		Name:               summaryEvent.Proxy.Name,
   472  		Revision:           summaryEvent.Proxy.Revision,
   473  		TeamID:             summaryEvent.Team.ID,
   474  		APIServiceInstance: accessRequest.Spec.ApiServiceInstance,
   475  	}
   476  	summaryEvent.API = api
   477  	log.
   478  		WithField("proxy-id", summaryEvent.Proxy.ID).
   479  		WithField("proxy-name", summaryEvent.Proxy.Name).
   480  		WithField("proxy-revision", summaryEvent.Proxy.Revision).
   481  		WithField("proxy-team-id", summaryEvent.Team.ID).
   482  		WithField("apiservice", accessRequest.Spec.ApiServiceInstance).
   483  		Trace("api details information")
   484  
   485  	productPlanRef := accessRequest.GetReferenceByGVK(catalog.ProductPlanGVK())
   486  	if productPlanRef.ID == "" {
   487  		log.Debug("could not get product plan ID, setting product plan to unknown")
   488  	} else {
   489  		summaryEvent.ProductPlan.ID = productPlanRef.ID
   490  	}
   491  	log.
   492  		WithField("product-plan-id", summaryEvent.ProductPlan.ID).
   493  		Trace("product plan ID information")
   494  
   495  	quotaRef := accessRequest.GetReferenceByGVK(catalog.QuotaGVK())
   496  	if quotaRef.ID == "" {
   497  		log.Debug("could not get quota ID, setting quota to unknown")
   498  	} else {
   499  		summaryEvent.Quota.ID = quotaRef.ID
   500  	}
   501  	log.
   502  		WithField("quota-id", summaryEvent.Quota.ID).
   503  		Trace("quota ID information")
   504  
   505  	return summaryEvent
   506  }