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