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 }