github.com/prebid/prebid-server/v2@v2.18.0/analytics/agma/agma_module.go (about) 1 package agma 2 3 import ( 4 "bytes" 5 "errors" 6 "net/http" 7 "os" 8 "os/signal" 9 "sync" 10 "syscall" 11 "time" 12 13 "github.com/benbjohnson/clock" 14 "github.com/docker/go-units" 15 "github.com/golang/glog" 16 "github.com/prebid/go-gdpr/vendorconsent" 17 "github.com/prebid/prebid-server/v2/analytics" 18 "github.com/prebid/prebid-server/v2/config" 19 "github.com/prebid/prebid-server/v2/openrtb_ext" 20 ) 21 22 type httpSender = func(payload []byte) error 23 24 const ( 25 agmaGVLID = 1122 26 p9 = 9 27 ) 28 29 type AgmaLogger struct { 30 sender httpSender 31 clock clock.Clock 32 accounts []config.AgmaAnalyticsAccount 33 eventCount int64 34 maxEventCount int64 35 maxBufferByteSize int64 36 maxDuration time.Duration 37 mux sync.RWMutex 38 sigTermCh chan os.Signal 39 buffer bytes.Buffer 40 bufferCh chan []byte 41 } 42 43 func newAgmaLogger(cfg config.AgmaAnalytics, sender httpSender, clock clock.Clock) (*AgmaLogger, error) { 44 pSize, err := units.FromHumanSize(cfg.Buffers.BufferSize) 45 if err != nil { 46 return nil, err 47 } 48 pDuration, err := time.ParseDuration(cfg.Buffers.Timeout) 49 if err != nil { 50 return nil, err 51 } 52 if len(cfg.Accounts) == 0 { 53 return nil, errors.New("Please configure at least one account for Agma Analytics") 54 } 55 56 buffer := bytes.Buffer{} 57 buffer.Write([]byte("[")) 58 59 return &AgmaLogger{ 60 sender: sender, 61 clock: clock, 62 accounts: cfg.Accounts, 63 maxBufferByteSize: pSize, 64 eventCount: 0, 65 maxEventCount: int64(cfg.Buffers.EventCount), 66 maxDuration: pDuration, 67 buffer: buffer, 68 bufferCh: make(chan []byte), 69 sigTermCh: make(chan os.Signal, 1), 70 }, nil 71 } 72 73 func NewModule(httpClient *http.Client, cfg config.AgmaAnalytics, clock clock.Clock) (analytics.Module, error) { 74 sender, err := createHttpSender(httpClient, cfg.Endpoint) 75 if err != nil { 76 return nil, err 77 } 78 79 m, err := newAgmaLogger(cfg, sender, clock) 80 if err != nil { 81 return nil, err 82 } 83 84 signal.Notify(m.sigTermCh, os.Interrupt, syscall.SIGTERM) 85 86 go m.start() 87 88 return m, nil 89 } 90 91 func (l *AgmaLogger) start() { 92 ticker := l.clock.Ticker(l.maxDuration) 93 for { 94 select { 95 case <-l.sigTermCh: 96 glog.Infof("[AgmaAnalytics] Received Close, trying to flush buffer") 97 l.flush() 98 return 99 case event := <-l.bufferCh: 100 l.bufferEvent(event) 101 if l.isFull() { 102 l.flush() 103 } 104 case <-ticker.C: 105 l.flush() 106 } 107 } 108 } 109 110 func (l *AgmaLogger) bufferEvent(data []byte) { 111 l.mux.Lock() 112 defer l.mux.Unlock() 113 114 l.buffer.Write(data) 115 l.buffer.WriteByte(',') 116 l.eventCount++ 117 } 118 119 func (l *AgmaLogger) isFull() bool { 120 l.mux.RLock() 121 defer l.mux.RUnlock() 122 return l.eventCount >= l.maxEventCount || int64(l.buffer.Len()) >= l.maxBufferByteSize 123 } 124 125 func (l *AgmaLogger) flush() { 126 l.mux.Lock() 127 128 if l.eventCount == 0 || l.buffer.Len() == 0 { 129 l.mux.Unlock() 130 return 131 } 132 133 // Close the json array, remove last , 134 l.buffer.Truncate(l.buffer.Len() - 1) 135 l.buffer.Write([]byte("]")) 136 137 payload := make([]byte, l.buffer.Len()) 138 _, err := l.buffer.Read(payload) 139 if err != nil { 140 l.reset() 141 l.mux.Unlock() 142 glog.Warning("[AgmaAnalytics] fail to copy the buffer") 143 return 144 } 145 146 go l.sender(payload) 147 148 l.reset() 149 l.mux.Unlock() 150 } 151 152 func (l *AgmaLogger) reset() { 153 l.buffer.Reset() 154 l.buffer.Write([]byte("[")) 155 l.eventCount = 0 156 } 157 158 func (l *AgmaLogger) extractPublisherAndSite(requestWrapper *openrtb_ext.RequestWrapper) (string, string) { 159 publisherId := "" 160 appSiteId := "" 161 if requestWrapper.Site != nil { 162 if requestWrapper.Site.Publisher != nil { 163 publisherId = requestWrapper.Site.Publisher.ID 164 } 165 appSiteId = requestWrapper.Site.ID 166 } 167 if requestWrapper.App != nil { 168 if requestWrapper.App.Publisher != nil { 169 publisherId = requestWrapper.App.Publisher.ID 170 } 171 appSiteId = requestWrapper.App.ID 172 } 173 return publisherId, appSiteId 174 } 175 176 func (l *AgmaLogger) shouldTrackEvent(requestWrapper *openrtb_ext.RequestWrapper) (bool, string) { 177 userExt, err := requestWrapper.GetUserExt() 178 if err != nil || userExt == nil { 179 return false, "" 180 } 181 consent := userExt.GetConsent() 182 if consent == nil { 183 return false, "" 184 } 185 consentStr := *consent 186 parsedConsent, err := vendorconsent.ParseString(consentStr) 187 if err != nil { 188 return false, "" 189 } 190 191 p9Allowed := parsedConsent.PurposeAllowed(p9) 192 agmaAllowed := parsedConsent.VendorConsent(agmaGVLID) 193 if !p9Allowed || !agmaAllowed { 194 return false, "" 195 } 196 197 publisherId, appSiteId := l.extractPublisherAndSite(requestWrapper) 198 if publisherId == "" && appSiteId == "" { 199 return false, "" 200 } 201 202 for _, account := range l.accounts { 203 if account.PublisherId == publisherId { 204 if account.SiteAppId == "" { 205 return true, account.Code 206 } 207 if account.SiteAppId == appSiteId { 208 return true, account.Code 209 } 210 } 211 } 212 213 return false, "" 214 } 215 216 func (l *AgmaLogger) LogAuctionObject(event *analytics.AuctionObject) { 217 if event == nil || event.Status != http.StatusOK || event.RequestWrapper == nil { 218 return 219 } 220 shouldTrack, code := l.shouldTrackEvent(event.RequestWrapper) 221 if !shouldTrack { 222 return 223 } 224 data, err := serializeAnayltics(event.RequestWrapper, EventTypeAuction, code, event.StartTime) 225 if err != nil { 226 glog.Errorf("[AgmaAnalytics] Error serializing auction object: %v", err) 227 return 228 } 229 l.bufferCh <- data 230 } 231 232 func (l *AgmaLogger) LogAmpObject(event *analytics.AmpObject) { 233 if event == nil || event.Status != http.StatusOK || event.RequestWrapper == nil { 234 return 235 } 236 shouldTrack, code := l.shouldTrackEvent(event.RequestWrapper) 237 if !shouldTrack { 238 return 239 } 240 data, err := serializeAnayltics(event.RequestWrapper, EventTypeAmp, code, event.StartTime) 241 if err != nil { 242 glog.Errorf("[AgmaAnalytics] Error serializing amp object: %v", err) 243 return 244 } 245 l.bufferCh <- data 246 } 247 248 func (l *AgmaLogger) LogVideoObject(event *analytics.VideoObject) { 249 if event == nil || event.Status != http.StatusOK || event.RequestWrapper == nil { 250 return 251 } 252 shouldTrack, code := l.shouldTrackEvent(event.RequestWrapper) 253 if !shouldTrack { 254 return 255 } 256 data, err := serializeAnayltics(event.RequestWrapper, EventTypeVideo, code, event.StartTime) 257 if err != nil { 258 glog.Errorf("[AgmaAnalytics] Error serializing video object: %v", err) 259 return 260 } 261 l.bufferCh <- data 262 } 263 264 func (l *AgmaLogger) LogCookieSyncObject(event *analytics.CookieSyncObject) {} 265 func (l *AgmaLogger) LogNotificationEventObject(event *analytics.NotificationEvent) {} 266 func (l *AgmaLogger) LogSetUIDObject(event *analytics.SetUIDObject) {}