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 }