github.com/prebid/prebid-server@v0.275.0/hooks/hookexecution/enricher.go (about)

     1  package hookexecution
     2  
     3  import (
     4  	"encoding/json"
     5  
     6  	"github.com/buger/jsonparser"
     7  	"github.com/prebid/openrtb/v19/openrtb2"
     8  	"github.com/prebid/prebid-server/config"
     9  	jsonpatch "gopkg.in/evanphx/json-patch.v4"
    10  )
    11  
    12  const (
    13  	// traceLevelBasic excludes debug_messages and analytic_tags from output
    14  	traceLevelBasic trace = "basic"
    15  	// traceLevelVerbose sets maximum level of output information
    16  	traceLevelVerbose trace = "verbose"
    17  )
    18  
    19  // Trace controls the level of detail in the output information returned from executing hooks.
    20  type trace string
    21  
    22  func (t trace) isBasicOrHigher() bool {
    23  	return t == traceLevelBasic || t.isVerbose()
    24  }
    25  
    26  func (t trace) isVerbose() bool {
    27  	return t == traceLevelVerbose
    28  }
    29  
    30  type extPrebid struct {
    31  	Prebid extModules `json:"prebid"`
    32  }
    33  
    34  type extModules struct {
    35  	Modules json.RawMessage `json:"modules"`
    36  }
    37  
    38  // EnrichExtBidResponse adds debug and trace information returned from executing hooks to the ext argument.
    39  // In response the outcome is visible under the key response.ext.prebid.modules.
    40  //
    41  // Debug information is added only if the debug mode is enabled by request and allowed by account (if provided).
    42  // The details of the trace output depends on the value in the bidRequest.ext.prebid.trace field.
    43  // Warnings returned if bidRequest contains unexpected types for debug fields controlling debug output.
    44  func EnrichExtBidResponse(
    45  	ext json.RawMessage,
    46  	stageOutcomes []StageOutcome,
    47  	bidRequest *openrtb2.BidRequest,
    48  	account *config.Account,
    49  ) (json.RawMessage, []error, error) {
    50  	modules, warnings, err := GetModulesJSON(stageOutcomes, bidRequest, account)
    51  	if err != nil || modules == nil {
    52  		return ext, warnings, err
    53  	}
    54  
    55  	response, err := json.Marshal(extPrebid{Prebid: extModules{Modules: modules}})
    56  	if err != nil {
    57  		return ext, warnings, err
    58  	}
    59  
    60  	if ext != nil {
    61  		response, err = jsonpatch.MergePatch(ext, response)
    62  	}
    63  
    64  	return response, warnings, err
    65  }
    66  
    67  // GetModulesJSON returns debug and trace information produced from executing hooks.
    68  // Debug information is returned only if the debug mode is enabled by request and allowed by account (if provided).
    69  // The details of the trace output depends on the value in the bidRequest.ext.prebid.trace field.
    70  // Warnings returned if bidRequest contains unexpected types for debug fields controlling debug output.
    71  func GetModulesJSON(
    72  	stageOutcomes []StageOutcome,
    73  	bidRequest *openrtb2.BidRequest,
    74  	account *config.Account,
    75  ) (json.RawMessage, []error, error) {
    76  	if len(stageOutcomes) == 0 {
    77  		return nil, nil, nil
    78  	}
    79  
    80  	trace, isDebugEnabled, warnings := getDebugContext(bidRequest, account)
    81  	modulesOutcome := getModulesOutcome(stageOutcomes, trace, isDebugEnabled)
    82  	if modulesOutcome == nil {
    83  		return nil, warnings, nil
    84  	}
    85  
    86  	data, err := json.Marshal(modulesOutcome)
    87  
    88  	return data, warnings, err
    89  }
    90  
    91  func getDebugContext(bidRequest *openrtb2.BidRequest, account *config.Account) (trace, bool, []error) {
    92  	var traceLevel string
    93  	var isDebugEnabled bool
    94  	var warnings []error
    95  	var err error
    96  
    97  	if bidRequest != nil {
    98  		traceLevel, err = jsonparser.GetString(bidRequest.Ext, "prebid", "trace")
    99  		if err != nil && err != jsonparser.KeyPathNotFoundError {
   100  			warnings = append(warnings, err)
   101  		}
   102  
   103  		isDebug, err := jsonparser.GetBoolean(bidRequest.Ext, "prebid", "debug")
   104  		if err != nil && err != jsonparser.KeyPathNotFoundError {
   105  			warnings = append(warnings, err)
   106  		}
   107  
   108  		isDebugEnabled = bidRequest.Test == 1 || isDebug
   109  		if account != nil {
   110  			isDebugEnabled = isDebugEnabled && account.DebugAllow
   111  		}
   112  	}
   113  
   114  	return trace(traceLevel), isDebugEnabled, warnings
   115  }
   116  
   117  func getModulesOutcome(stageOutcomes []StageOutcome, trace trace, isDebugEnabled bool) *ModulesOutcome {
   118  	var modulesOutcome ModulesOutcome
   119  	stages := make(map[string]Stage)
   120  	stageNames := make([]string, 0)
   121  
   122  	for _, stageOutcome := range stageOutcomes {
   123  		if len(stageOutcome.Groups) == 0 {
   124  			continue
   125  		}
   126  
   127  		prepareModulesOutcome(&modulesOutcome, stageOutcome.Groups, trace, isDebugEnabled)
   128  		if !trace.isBasicOrHigher() {
   129  			continue
   130  		}
   131  
   132  		stage, ok := stages[stageOutcome.Stage]
   133  		if !ok {
   134  			stageNames = append(stageNames, stageOutcome.Stage)
   135  			stage = Stage{
   136  				Stage:    stageOutcome.Stage,
   137  				Outcomes: []StageOutcome{},
   138  			}
   139  		}
   140  
   141  		stage.Outcomes = append(stage.Outcomes, stageOutcome)
   142  		if stageOutcome.ExecutionTimeMillis > stage.ExecutionTimeMillis {
   143  			stage.ExecutionTimeMillis = stageOutcome.ExecutionTimeMillis
   144  		}
   145  
   146  		stages[stageOutcome.Stage] = stage
   147  	}
   148  
   149  	if modulesOutcome.Errors == nil && modulesOutcome.Warnings == nil && len(stages) == 0 {
   150  		return nil
   151  	}
   152  
   153  	if len(stages) > 0 {
   154  		modulesOutcome.Trace = &TraceOutcome{}
   155  		modulesOutcome.Trace.Stages = make([]Stage, 0, len(stages))
   156  
   157  		for _, stage := range stageNames {
   158  			modulesOutcome.Trace.ExecutionTimeMillis += stages[stage].ExecutionTimeMillis
   159  			modulesOutcome.Trace.Stages = append(modulesOutcome.Trace.Stages, stages[stage])
   160  		}
   161  	}
   162  
   163  	return &modulesOutcome
   164  }
   165  
   166  func prepareModulesOutcome(modulesOutcome *ModulesOutcome, groups []GroupOutcome, trace trace, isDebugEnabled bool) {
   167  	for _, group := range groups {
   168  		for i, hookOutcome := range group.InvocationResults {
   169  			if !trace.isVerbose() {
   170  				group.InvocationResults[i].DebugMessages = nil
   171  			}
   172  
   173  			if isDebugEnabled {
   174  				modulesOutcome.Errors = fillMessages(modulesOutcome.Errors, hookOutcome.Errors, hookOutcome.HookID)
   175  				modulesOutcome.Warnings = fillMessages(modulesOutcome.Warnings, hookOutcome.Warnings, hookOutcome.HookID)
   176  			}
   177  		}
   178  	}
   179  }
   180  
   181  func fillMessages(messages Messages, values []string, hookID HookID) Messages {
   182  	if len(values) == 0 {
   183  		return messages
   184  	}
   185  
   186  	if messages == nil {
   187  		return Messages{hookID.ModuleCode: {hookID.HookImplCode: values}}
   188  	}
   189  
   190  	if _, ok := messages[hookID.ModuleCode]; !ok {
   191  		messages[hookID.ModuleCode] = map[string][]string{hookID.HookImplCode: values}
   192  		return messages
   193  	}
   194  
   195  	if prevValues, ok := messages[hookID.ModuleCode][hookID.HookImplCode]; ok {
   196  		values = append(prevValues, values...)
   197  	}
   198  
   199  	messages[hookID.ModuleCode][hookID.HookImplCode] = values
   200  
   201  	return messages
   202  }