Skip to content

Commit ed3e4a1

Browse files
authored
Gracefully shutdown analytics module/runner (#3335)
1 parent 466ff83 commit ed3e4a1

File tree

14 files changed

+140
-10
lines changed

14 files changed

+140
-10
lines changed

analytics/agma/agma_module.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,11 @@ func (l *AgmaLogger) LogVideoObject(event *analytics.VideoObject) {
261261
l.bufferCh <- data
262262
}
263263

264+
func (l *AgmaLogger) Shutdown() {
265+
glog.Info("[AgmaAnalytics] Shutdown, trying to flush buffer")
266+
l.flush() // mutex safe
267+
}
268+
264269
func (l *AgmaLogger) LogCookieSyncObject(event *analytics.CookieSyncObject) {}
265270
func (l *AgmaLogger) LogNotificationEventObject(event *analytics.NotificationEvent) {}
266271
func (l *AgmaLogger) LogSetUIDObject(event *analytics.SetUIDObject) {}

analytics/agma/agma_module_test.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -664,3 +664,38 @@ func TestRaceEnd2End(t *testing.T) {
664664

665665
assert.Equal(t, expected, actual)
666666
}
667+
668+
func TestShutdownFlush(t *testing.T) {
669+
cfg := config.AgmaAnalytics{
670+
Enabled: true,
671+
Endpoint: config.AgmaAnalyticsHttpEndpoint{
672+
Url: "http://localhost:8000/event",
673+
Timeout: "5s",
674+
},
675+
Buffers: config.AgmaAnalyticsBuffer{
676+
EventCount: 1000,
677+
BufferSize: "100mb",
678+
Timeout: "5m",
679+
},
680+
Accounts: []config.AgmaAnalyticsAccount{
681+
{
682+
PublisherId: "track-me",
683+
Code: "abc",
684+
},
685+
},
686+
}
687+
mockedSender := new(MockedSender)
688+
mockedSender.On("Send", mock.Anything).Return(nil)
689+
clockMock := clock.NewMock()
690+
logger, err := newAgmaLogger(cfg, mockedSender.Send, clockMock)
691+
assert.NoError(t, err)
692+
693+
go logger.start()
694+
logger.LogAuctionObject(&mockValidAuctionObject)
695+
logger.Shutdown()
696+
697+
time.Sleep(10 * time.Millisecond)
698+
699+
mockedSender.AssertCalled(t, "Send", mock.Anything)
700+
mockedSender.AssertNumberOfCalls(t, "Send", 1)
701+
}

analytics/build/build.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,13 @@ func (ea enabledAnalytics) LogNotificationEventObject(ne *analytics.Notification
129129
}
130130
}
131131

132+
// Shutdown - correctly shutdown all analytics modules and wait for them to finish
133+
func (ea enabledAnalytics) Shutdown() {
134+
for _, module := range ea {
135+
module.Shutdown()
136+
}
137+
}
138+
132139
func evaluateActivities(rw *openrtb_ext.RequestWrapper, ac privacy.ActivityControl, componentName string) (bool, *openrtb_ext.RequestWrapper) {
133140
// returned nil request wrapper means that request wrapper was not modified by activities and doesn't have to be changed in analytics object
134141
// it is needed in order to use one function for all analytics objects with RequestWrapper

analytics/build/build_test.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,8 @@ func (m *sampleModule) LogAmpObject(ao *analytics.AmpObject) { *m.count++ }
7979

8080
func (m *sampleModule) LogNotificationEventObject(ne *analytics.NotificationEvent) { *m.count++ }
8181

82+
func (m *sampleModule) Shutdown() { *m.count++ }
83+
8284
func initAnalytics(count *int) analytics.Runner {
8385
modules := make(enabledAnalytics, 0)
8486
modules["sampleModule"] = &sampleModule{count}
@@ -92,6 +94,19 @@ func TestNewPBSAnalytics(t *testing.T) {
9294
assert.Equal(t, len(instance), 0)
9395
}
9496

97+
func TestPBSAnalyticsShutdown(t *testing.T) {
98+
countA := 0
99+
countB := 0
100+
modules := make(enabledAnalytics, 0)
101+
modules["sampleModuleA"] = &sampleModule{count: &countA}
102+
modules["sampleModuleB"] = &sampleModule{count: &countB}
103+
104+
modules.Shutdown()
105+
106+
assert.Equal(t, 1, countA, "sampleModuleA should have been shutdown")
107+
assert.Equal(t, 1, countB, "sampleModuleB should have been shutdown")
108+
}
109+
95110
func TestNewPBSAnalytics_FileLogger(t *testing.T) {
96111
if _, err := os.Stat(TEST_DIR); os.IsNotExist(err) {
97112
if err = os.MkdirAll(TEST_DIR, 0755); err != nil {
@@ -415,6 +430,8 @@ func (m *mockAnalytics) LogSetUIDObject(ao *analytics.SetUIDObject) {}
415430

416431
func (m *mockAnalytics) LogNotificationEventObject(ao *analytics.NotificationEvent) {}
417432

433+
func (m *mockAnalytics) Shutdown() {}
434+
418435
func TestLogObject(t *testing.T) {
419436
tests := []struct {
420437
description string

analytics/core.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ type Module interface {
1919
LogSetUIDObject(*SetUIDObject)
2020
LogAmpObject(*AmpObject)
2121
LogNotificationEventObject(*NotificationEvent)
22+
Shutdown()
2223
}
2324

2425
// Loggable object of a transaction at /openrtb2/auction endpoint

analytics/filesystem/file_module.go

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ import (
44
"bytes"
55
"fmt"
66

7-
"github.com/chasex/glog"
7+
cglog "github.com/chasex/glog"
8+
"github.com/golang/glog"
89
"github.com/prebid/openrtb/v20/openrtb2"
910
"github.com/prebid/prebid-server/v2/analytics"
1011
"github.com/prebid/prebid-server/v2/util/jsonutil"
@@ -21,9 +22,14 @@ const (
2122
NOTIFICATION_EVENT RequestType = "/event"
2223
)
2324

25+
type Logger interface {
26+
Debug(v ...interface{})
27+
Flush()
28+
}
29+
2430
// Module that can perform transactional logging
2531
type FileLogger struct {
26-
Logger *glog.Logger
32+
Logger Logger
2733
}
2834

2935
// Writes AuctionObject to file
@@ -85,15 +91,22 @@ func (f *FileLogger) LogNotificationEventObject(ne *analytics.NotificationEvent)
8591
f.Logger.Flush()
8692
}
8793

94+
// Shutdown the logger
95+
func (f *FileLogger) Shutdown() {
96+
// clear all pending buffered data in case there is any
97+
glog.Info("[FileLogger] Shutdown, trying to flush buffer")
98+
f.Logger.Flush()
99+
}
100+
88101
// Method to initialize the analytic module
89102
func NewFileLogger(filename string) (analytics.Module, error) {
90-
options := glog.LogOptions{
103+
options := cglog.LogOptions{
91104
File: filename,
92-
Flag: glog.LstdFlags,
93-
Level: glog.Ldebug,
94-
Mode: glog.R_Day,
105+
Flag: cglog.LstdFlags,
106+
Level: cglog.Ldebug,
107+
Mode: cglog.R_Day,
95108
}
96-
if logger, err := glog.New(options); err == nil {
109+
if logger, err := cglog.New(options); err == nil {
97110
return &FileLogger{
98111
logger,
99112
}, nil

analytics/filesystem/file_module_test.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,25 @@ import (
88

99
"github.com/prebid/prebid-server/v2/analytics"
1010
"github.com/prebid/prebid-server/v2/config"
11+
"github.com/stretchr/testify/mock"
1112

1213
"github.com/prebid/openrtb/v20/openrtb2"
1314
)
1415

1516
const TEST_DIR string = "testFiles"
1617

18+
type MockLogger struct {
19+
mock.Mock
20+
}
21+
22+
func (ml *MockLogger) Debug(v ...interface{}) {
23+
ml.Called(v)
24+
}
25+
26+
func (ml *MockLogger) Flush() {
27+
ml.Called()
28+
}
29+
1730
func TestAmpObject_ToJson(t *testing.T) {
1831
ao := &analytics.AmpObject{
1932
Status: http.StatusOK,
@@ -97,3 +110,15 @@ func TestFileLogger_LogObjects(t *testing.T) {
97110
t.Fatalf("Couldn't initialize file logger: %v", err)
98111
}
99112
}
113+
114+
func TestFileLoggerShutdown(t *testing.T) {
115+
mockLogger := &MockLogger{}
116+
fl := &FileLogger{
117+
Logger: mockLogger,
118+
}
119+
mockLogger.On("Flush").Return(nil)
120+
121+
fl.Shutdown()
122+
123+
mockLogger.AssertNumberOfCalls(t, "Flush", 1)
124+
}

analytics/pubstack/pubstack_module.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,12 @@ func (p *PubstackModule) LogAmpObject(ao *analytics.AmpObject) {
200200
p.eventChannels[amp].Push(payload)
201201
}
202202

203+
// Shutdown - no op since the analytic module already implements system signal handling
204+
// and trying to close a closed channel will cause panic
205+
func (p *PubstackModule) Shutdown() {
206+
glog.Info("[PubstackModule] Shutdown")
207+
}
208+
203209
func (p *PubstackModule) start(c <-chan *Configuration) {
204210
for {
205211
select {

analytics/runner.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,5 @@ type Runner interface {
1111
LogSetUIDObject(*SetUIDObject)
1212
LogAmpObject(*AmpObject, privacy.ActivityControl)
1313
LogNotificationEventObject(*NotificationEvent, privacy.ActivityControl)
14+
Shutdown()
1415
}

endpoints/cookie_sync_test.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2198,6 +2198,10 @@ func (m *MockAnalyticsRunner) LogNotificationEventObject(obj *analytics.Notifica
21982198
m.Called(obj, ac)
21992199
}
22002200

2201+
func (m *MockAnalyticsRunner) Shutdown() {
2202+
m.Called()
2203+
}
2204+
22012205
type MockGDPRPerms struct {
22022206
mock.Mock
22032207
}

0 commit comments

Comments
 (0)