github.com/prebid/prebid-server/v2@v2.18.0/analytics/pubstack/pubstack_module.go (about) 1 package pubstack 2 3 import ( 4 "fmt" 5 "net/http" 6 "os" 7 "os/signal" 8 "sync" 9 "syscall" 10 "time" 11 12 "github.com/benbjohnson/clock" 13 "github.com/golang/glog" 14 15 "github.com/prebid/prebid-server/v2/analytics" 16 "github.com/prebid/prebid-server/v2/analytics/pubstack/eventchannel" 17 "github.com/prebid/prebid-server/v2/analytics/pubstack/helpers" 18 ) 19 20 type Configuration struct { 21 ScopeID string `json:"scopeId"` 22 Endpoint string `json:"endpoint"` 23 Features map[string]bool `json:"features"` 24 } 25 26 // routes for events 27 const ( 28 auction = "auction" 29 cookieSync = "cookiesync" 30 amp = "amp" 31 setUID = "setuid" 32 video = "video" 33 ) 34 35 type bufferConfig struct { 36 timeout time.Duration 37 count int64 38 size int64 39 } 40 41 type PubstackModule struct { 42 eventChannels map[string]*eventchannel.EventChannel 43 httpClient *http.Client 44 sigTermCh chan os.Signal 45 stopCh chan struct{} 46 scope string 47 cfg *Configuration 48 buffsCfg *bufferConfig 49 muxConfig sync.RWMutex 50 clock clock.Clock 51 } 52 53 func NewModule(client *http.Client, scope, endpoint, configRefreshDelay string, maxEventCount int, maxByteSize, maxTime string, clock clock.Clock) (analytics.Module, error) { 54 configUpdateTask, err := NewConfigUpdateHttpTask( 55 client, 56 scope, 57 endpoint, 58 configRefreshDelay) 59 if err != nil { 60 return nil, err 61 } 62 63 return NewModuleWithConfigTask(client, scope, endpoint, maxEventCount, maxByteSize, maxTime, configUpdateTask, clock) 64 } 65 66 func NewModuleWithConfigTask(client *http.Client, scope, endpoint string, maxEventCount int, maxByteSize, maxTime string, configTask ConfigUpdateTask, clock clock.Clock) (analytics.Module, error) { 67 glog.Infof("[pubstack] Initializing module scope=%s endpoint=%s\n", scope, endpoint) 68 69 // parse args 70 bufferCfg, err := newBufferConfig(maxEventCount, maxByteSize, maxTime) 71 if err != nil { 72 return nil, fmt.Errorf("fail to parse the module args, arg=analytics.pubstack.buffers, :%v", err) 73 } 74 75 defaultFeatures := map[string]bool{ 76 auction: false, 77 video: false, 78 amp: false, 79 cookieSync: false, 80 setUID: false, 81 } 82 83 defaultConfig := &Configuration{ 84 ScopeID: scope, 85 Endpoint: endpoint, 86 Features: defaultFeatures, 87 } 88 89 pb := PubstackModule{ 90 scope: scope, 91 httpClient: client, 92 cfg: defaultConfig, 93 buffsCfg: bufferCfg, 94 sigTermCh: make(chan os.Signal), 95 stopCh: make(chan struct{}), 96 eventChannels: make(map[string]*eventchannel.EventChannel), 97 muxConfig: sync.RWMutex{}, 98 clock: clock, 99 } 100 101 signal.Notify(pb.sigTermCh, os.Interrupt, syscall.SIGTERM) 102 103 configChannel := configTask.Start(pb.stopCh) 104 go pb.start(configChannel) 105 106 glog.Info("[pubstack] Pubstack analytics configured and ready") 107 return &pb, nil 108 } 109 110 func (p *PubstackModule) LogAuctionObject(ao *analytics.AuctionObject) { 111 p.muxConfig.RLock() 112 defer p.muxConfig.RUnlock() 113 114 if !p.isFeatureEnable(auction) { 115 return 116 } 117 118 // serialize event 119 payload, err := helpers.JsonifyAuctionObject(ao, p.scope) 120 if err != nil { 121 glog.Warning("[pubstack] Cannot serialize auction") 122 return 123 } 124 125 p.eventChannels[auction].Push(payload) 126 } 127 128 func (p *PubstackModule) LogNotificationEventObject(ne *analytics.NotificationEvent) { 129 } 130 131 func (p *PubstackModule) LogVideoObject(vo *analytics.VideoObject) { 132 p.muxConfig.RLock() 133 defer p.muxConfig.RUnlock() 134 135 if !p.isFeatureEnable(video) { 136 return 137 } 138 139 // serialize event 140 payload, err := helpers.JsonifyVideoObject(vo, p.scope) 141 if err != nil { 142 glog.Warning("[pubstack] Cannot serialize video") 143 return 144 } 145 146 p.eventChannels[video].Push(payload) 147 } 148 149 func (p *PubstackModule) LogSetUIDObject(so *analytics.SetUIDObject) { 150 p.muxConfig.RLock() 151 defer p.muxConfig.RUnlock() 152 153 if !p.isFeatureEnable(setUID) { 154 return 155 } 156 157 // serialize event 158 payload, err := helpers.JsonifySetUIDObject(so, p.scope) 159 if err != nil { 160 glog.Warning("[pubstack] Cannot serialize video") 161 return 162 } 163 164 p.eventChannels[setUID].Push(payload) 165 } 166 167 func (p *PubstackModule) LogCookieSyncObject(cso *analytics.CookieSyncObject) { 168 p.muxConfig.RLock() 169 defer p.muxConfig.RUnlock() 170 171 if !p.isFeatureEnable(cookieSync) { 172 return 173 } 174 175 // serialize event 176 payload, err := helpers.JsonifyCookieSync(cso, p.scope) 177 if err != nil { 178 glog.Warning("[pubstack] Cannot serialize video") 179 return 180 } 181 182 p.eventChannels[cookieSync].Push(payload) 183 } 184 185 func (p *PubstackModule) LogAmpObject(ao *analytics.AmpObject) { 186 p.muxConfig.RLock() 187 defer p.muxConfig.RUnlock() 188 189 if !p.isFeatureEnable(amp) { 190 return 191 } 192 193 // serialize event 194 payload, err := helpers.JsonifyAmpObject(ao, p.scope) 195 if err != nil { 196 glog.Warning("[pubstack] Cannot serialize video") 197 return 198 } 199 200 p.eventChannels[amp].Push(payload) 201 } 202 203 func (p *PubstackModule) start(c <-chan *Configuration) { 204 for { 205 select { 206 case <-p.sigTermCh: 207 close(p.stopCh) 208 cfg := p.cfg.clone().disableAllFeatures() 209 p.updateConfig(cfg) 210 return 211 case config := <-c: 212 p.updateConfig(config) 213 glog.Infof("[pubstack] Updating config: %v", p.cfg) 214 } 215 } 216 } 217 218 func (p *PubstackModule) updateConfig(config *Configuration) { 219 p.muxConfig.Lock() 220 defer p.muxConfig.Unlock() 221 222 if p.cfg.isSameAs(config) { 223 return 224 } 225 226 p.cfg = config 227 p.closeAllEventChannels() 228 229 p.registerChannel(amp) 230 p.registerChannel(auction) 231 p.registerChannel(cookieSync) 232 p.registerChannel(video) 233 p.registerChannel(setUID) 234 } 235 236 func (p *PubstackModule) isFeatureEnable(feature string) bool { 237 val, ok := p.cfg.Features[feature] 238 return ok && val 239 } 240 241 func (p *PubstackModule) registerChannel(feature string) { 242 if p.isFeatureEnable(feature) { 243 sender := eventchannel.BuildEndpointSender(p.httpClient, p.cfg.Endpoint, feature) 244 p.eventChannels[feature] = eventchannel.NewEventChannel(sender, p.clock, p.buffsCfg.size, p.buffsCfg.count, p.buffsCfg.timeout) 245 } 246 } 247 248 func (p *PubstackModule) closeAllEventChannels() { 249 for key, ch := range p.eventChannels { 250 ch.Close() 251 delete(p.eventChannels, key) 252 } 253 }