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)                 {}