Skip to content

Commit 1060af7

Browse files
committed
Add logging capture to Sync Function diagnostic API
1 parent 918df77 commit 1060af7

File tree

4 files changed

+117
-13
lines changed

4 files changed

+117
-13
lines changed

channels/sync_runner.go

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -116,12 +116,10 @@ type SyncRunner struct {
116116
expiry *uint32 // document expiry (in seconds) specified via expiry() callback
117117
}
118118

119-
func NewSyncRunner(ctx context.Context, funcSource string, timeout time.Duration) (*SyncRunner, error) {
119+
func NewSyncRunnerWithLogging(ctx context.Context, funcSource string, timeout time.Duration, errorLogFunc, infoLogFunc func(string)) (*SyncRunner, error) {
120120
funcSource = wrappedFuncSource(funcSource)
121121
runner := &SyncRunner{}
122-
err := runner.InitWithLogging(funcSource, timeout,
123-
func(s string) { base.ErrorfCtx(ctx, base.KeyJavascript.String()+": Sync %s", base.UD(s)) },
124-
func(s string) { base.InfofCtx(ctx, base.KeyJavascript, "Sync %s", base.UD(s)) })
122+
err := runner.InitWithLogging(funcSource, timeout, errorLogFunc, infoLogFunc)
125123
if err != nil {
126124
return nil, err
127125
}
@@ -212,6 +210,12 @@ func NewSyncRunner(ctx context.Context, funcSource string, timeout time.Duration
212210
return runner, nil
213211
}
214212

213+
func NewSyncRunner(ctx context.Context, funcSource string, timeout time.Duration) (*SyncRunner, error) {
214+
errorLogFunc := func(s string) { base.ErrorfCtx(ctx, base.KeyJavascript.String()+": Sync %s", base.UD(s)) }
215+
infoLogFunc := func(s string) { base.InfofCtx(ctx, base.KeyJavascript, "Sync %s", base.UD(s)) }
216+
return NewSyncRunnerWithLogging(ctx, funcSource, timeout, errorLogFunc, infoLogFunc)
217+
}
218+
215219
func (runner *SyncRunner) SetFunction(funcSource string) (bool, error) {
216220
funcSource = wrappedFuncSource(funcSource)
217221
return runner.JSRunner.SetFunction(funcSource)

db/crud.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1697,7 +1697,7 @@ func (db *DatabaseCollectionWithUser) PutExistingRevWithBody(ctx context.Context
16971697
// SyncFnDryrun Runs the given document body through a sync function and returns expiry, channels doc was placed in,
16981698
// access map for users, roles, handler errors and sync fn exceptions.
16991699
// If syncFn is provided, it will be used instead of the one configured on the database.
1700-
func (db *DatabaseCollectionWithUser) SyncFnDryrun(ctx context.Context, newDoc, oldDoc *Document, syncFn string) (*channels.ChannelMapperOutput, error) {
1700+
func (db *DatabaseCollectionWithUser) SyncFnDryrun(ctx context.Context, newDoc, oldDoc *Document, syncFn string, errorLogFunc, infoLogFunc func(string)) (*channels.ChannelMapperOutput, error) {
17011701

17021702
mutableBody, metaMap, _, err := db.prepareSyncFn(oldDoc, newDoc)
17031703
if err != nil {
@@ -1722,7 +1722,7 @@ func (db *DatabaseCollectionWithUser) SyncFnDryrun(ctx context.Context, newDoc,
17221722
syncFn = channels.GetDefaultSyncFunction(scopeAndCollectionName.Scope, scopeAndCollectionName.Collection)
17231723
}
17241724
jsTimeout := time.Duration(base.DefaultJavascriptTimeoutSecs) * time.Second
1725-
syncRunner, err := channels.NewSyncRunner(ctx, syncFn, jsTimeout)
1725+
syncRunner, err := channels.NewSyncRunnerWithLogging(ctx, syncFn, jsTimeout, errorLogFunc, infoLogFunc)
17261726
if err != nil {
17271727
return nil, fmt.Errorf("failed to create sync runner: %v", err)
17281728
}

rest/diagnostic_doc_api.go

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,18 @@ import (
2121
"github.com/couchbase/sync_gateway/db"
2222
)
2323

24+
type SyncFnDryRunLogging struct {
25+
Errors []string `json:"errors"`
26+
Info []string `json:"info"`
27+
}
28+
2429
type SyncFnDryRun struct {
25-
Channels base.Set `json:"channels"`
26-
Access channels.AccessMap `json:"access"`
27-
Roles channels.AccessMap `json:"roles"`
28-
Exception string `json:"exception"`
29-
Expiry *uint32 `json:"expiry,omitempty"`
30+
Channels base.Set `json:"channels"`
31+
Access channels.AccessMap `json:"access"`
32+
Roles channels.AccessMap `json:"roles"`
33+
Exception string `json:"exception"`
34+
Expiry *uint32 `json:"expiry,omitempty"`
35+
Logging SyncFnDryRunLogging `json:"logging"`
3036
}
3137

3238
type ImportFilterDryRun struct {
@@ -144,7 +150,16 @@ func (h *handler) handleSyncFnDryRun() error {
144150
newRev := db.CreateRevIDWithBytes(generation, matchRev, rawDocBytes)
145151
newDoc.RevID = newRev
146152

147-
output, err := h.collection.SyncFnDryrun(h.ctx(), newDoc, oldDoc, syncDryRunPayload.Function)
153+
logErrors := make([]string, 0)
154+
logInfo := make([]string, 0)
155+
errorLogFn := func(s string) {
156+
logErrors = append(logErrors, s)
157+
}
158+
infoLogFn := func(s string) {
159+
logInfo = append(logInfo, s)
160+
}
161+
162+
output, err := h.collection.SyncFnDryrun(h.ctx(), newDoc, oldDoc, syncDryRunPayload.Function, errorLogFn, infoLogFn)
148163
if err != nil {
149164
var syncFnDryRunErr *base.SyncFnDryRunError
150165
if !errors.As(err, &syncFnDryRunErr) {
@@ -154,6 +169,7 @@ func (h *handler) handleSyncFnDryRun() error {
154169
errMsg := syncFnDryRunErr.Error()
155170
resp := SyncFnDryRun{
156171
Exception: errMsg,
172+
Logging: SyncFnDryRunLogging{Errors: logErrors, Info: logInfo},
157173
}
158174
h.writeJSON(resp)
159175
return nil
@@ -169,6 +185,7 @@ func (h *handler) handleSyncFnDryRun() error {
169185
output.Roles,
170186
errorMsg,
171187
output.Expiry,
188+
SyncFnDryRunLogging{Errors: logErrors, Info: logInfo},
172189
}
173190
h.writeJSON(resp)
174191
return nil

rest/diagnostic_doc_api_test.go

Lines changed: 84 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1049,6 +1049,10 @@ func TestSyncFuncDryRun(t *testing.T) {
10491049
Access: channels.AccessMap{"user": channels.BaseSetOf(t, "dynamicChan5412")},
10501050
Roles: channels.AccessMap{},
10511051
Expiry: base.Ptr(uint32(10)),
1052+
Logging: SyncFnDryRunLogging{
1053+
Errors: []string{},
1054+
Info: []string{},
1055+
},
10521056
},
10531057
expectedStatus: http.StatusOK,
10541058
},
@@ -1062,6 +1066,10 @@ func TestSyncFuncDryRun(t *testing.T) {
10621066
Channels: base.SetFromArray([]string{}),
10631067
Access: channels.AccessMap{},
10641068
Roles: channels.AccessMap{"user": channels.BaseSetOf(t, "role1")},
1069+
Logging: SyncFnDryRunLogging{
1070+
Errors: []string{},
1071+
Info: []string{},
1072+
},
10651073
},
10661074
expectedStatus: http.StatusOK,
10671075
},
@@ -1076,6 +1084,10 @@ func TestSyncFuncDryRun(t *testing.T) {
10761084
Access: channels.AccessMap{"user": channels.BaseSetOf(t, "dynamicChan5412")},
10771085
Roles: channels.AccessMap{},
10781086
Expiry: base.Ptr(uint32(10)),
1087+
Logging: SyncFnDryRunLogging{
1088+
Errors: []string{},
1089+
Info: []string{},
1090+
},
10791091
},
10801092
expectedStatus: http.StatusOK,
10811093
},
@@ -1090,6 +1102,10 @@ func TestSyncFuncDryRun(t *testing.T) {
10901102
Access: channels.AccessMap{"user": channels.BaseSetOf(t, "dynamicChan5412")},
10911103
Roles: channels.AccessMap{},
10921104
Expiry: base.Ptr(uint32(10)),
1105+
Logging: SyncFnDryRunLogging{
1106+
Errors: []string{},
1107+
Info: []string{},
1108+
},
10931109
},
10941110
expectedStatus: http.StatusOK,
10951111
},
@@ -1106,6 +1122,10 @@ func TestSyncFuncDryRun(t *testing.T) {
11061122
Access: channels.AccessMap{"user": channels.BaseSetOf(t, "dynamicChan5412")},
11071123
Roles: channels.AccessMap{},
11081124
Expiry: base.Ptr(uint32(10)),
1125+
Logging: SyncFnDryRunLogging{
1126+
Errors: []string{},
1127+
Info: []string{},
1128+
},
11091129
},
11101130
expectedStatus: http.StatusOK,
11111131
},
@@ -1120,6 +1140,10 @@ func TestSyncFuncDryRun(t *testing.T) {
11201140
Access: channels.AccessMap{},
11211141
Roles: channels.AccessMap{},
11221142
Exception: "403 user num too low",
1143+
Logging: SyncFnDryRunLogging{
1144+
Errors: []string{},
1145+
Info: []string{},
1146+
},
11231147
},
11241148
expectedStatus: http.StatusOK,
11251149
},
@@ -1134,6 +1158,10 @@ func TestSyncFuncDryRun(t *testing.T) {
11341158
existingDocBody: `{"user":{"num":123, "name":["user1"]}, "channel":"channel1"}`,
11351159
expectedOutput: SyncFnDryRun{
11361160
Exception: "Error returned from Sync Function: TypeError: Cannot access member '0' of undefined",
1161+
Logging: SyncFnDryRunLogging{
1162+
Errors: []string{},
1163+
Info: []string{},
1164+
},
11371165
},
11381166
expectedStatus: http.StatusOK,
11391167
},
@@ -1151,6 +1179,10 @@ func TestSyncFuncDryRun(t *testing.T) {
11511179
Access: channels.AccessMap{"user1": channels.BaseSetOf(t, "channel2")},
11521180
Roles: channels.AccessMap{},
11531181
Exception: "",
1182+
Logging: SyncFnDryRunLogging{
1183+
Errors: []string{},
1184+
Info: []string{},
1185+
},
11541186
},
11551187
expectedStatus: http.StatusOK,
11561188
},
@@ -1167,6 +1199,10 @@ func TestSyncFuncDryRun(t *testing.T) {
11671199
Access: channels.AccessMap{},
11681200
Roles: channels.AccessMap{},
11691201
Exception: "",
1202+
Logging: SyncFnDryRunLogging{
1203+
Errors: []string{},
1204+
Info: []string{},
1205+
},
11701206
},
11711207
expectedStatus: http.StatusOK,
11721208
},
@@ -1205,6 +1241,10 @@ func TestSyncFuncDryRun(t *testing.T) {
12051241
Channels: base.SetFromArray([]string{"channel2"}),
12061242
Access: channels.AccessMap{},
12071243
Roles: channels.AccessMap{},
1244+
Logging: SyncFnDryRunLogging{
1245+
Errors: []string{},
1246+
Info: []string{},
1247+
},
12081248
},
12091249
expectedStatus: http.StatusOK,
12101250
},
@@ -1221,6 +1261,10 @@ func TestSyncFuncDryRun(t *testing.T) {
12211261
Channels: base.SetFromArray([]string{"chanOld"}),
12221262
Access: channels.AccessMap{},
12231263
Roles: channels.AccessMap{},
1264+
Logging: SyncFnDryRunLogging{
1265+
Errors: []string{},
1266+
Info: []string{},
1267+
},
12241268
},
12251269
expectedStatus: http.StatusOK,
12261270
},
@@ -1244,6 +1288,10 @@ func TestSyncFuncDryRun(t *testing.T) {
12441288
Channels: base.SetFromArray([]string{defaultChannelName}),
12451289
Access: channels.AccessMap{},
12461290
Roles: channels.AccessMap{},
1291+
Logging: SyncFnDryRunLogging{
1292+
Errors: []string{},
1293+
Info: []string{},
1294+
},
12471295
},
12481296
expectedStatus: http.StatusOK,
12491297
},
@@ -1257,13 +1305,48 @@ func TestSyncFuncDryRun(t *testing.T) {
12571305
Channels: base.SetFromArray([]string{defaultChannelName}),
12581306
Access: channels.AccessMap{},
12591307
Roles: channels.AccessMap{},
1308+
Logging: SyncFnDryRunLogging{
1309+
Errors: []string{},
1310+
Info: []string{},
1311+
},
1312+
},
1313+
expectedStatus: http.StatusOK,
1314+
},
1315+
{
1316+
name: "logging",
1317+
docID: "logging",
1318+
existingDoc: true,
1319+
existingDocID: "logging",
1320+
existingDocBody: `{"channel": "chanLog", "logerror": true, "loginfo": true}`,
1321+
syncFunction: `function(doc) {
1322+
channel(doc.channel);
1323+
if (doc.logerror) {
1324+
console.error("This is a console.error log from logerror");
1325+
} else {
1326+
console.log("This is a console.log log from logerror");
1327+
}
1328+
if (doc.loginfo) {
1329+
console.log("This is a console.log log from loginfo");
1330+
} else {
1331+
console.error("This is a console.error log from logerror");
1332+
}
1333+
console.log("one more info for good measure...");
1334+
}`,
1335+
expectedOutput: SyncFnDryRun{
1336+
Channels: base.SetFromArray([]string{"chanLog"}),
1337+
Access: channels.AccessMap{},
1338+
Roles: channels.AccessMap{},
1339+
Logging: SyncFnDryRunLogging{
1340+
Errors: []string{"This is a console.error log from logerror"},
1341+
Info: []string{"This is a console.log log from loginfo", "one more info for good measure..."},
1342+
},
12601343
},
12611344
expectedStatus: http.StatusOK,
12621345
},
12631346
}
12641347

12651348
for _, test := range tests {
1266-
t.Run(test.name, func(t *testing.T) {
1349+
rt.Run(test.name, func(t *testing.T) {
12671350
dbConfig := rt.NewDbConfig()
12681351
dbConfig.Sync = &test.dbSyncFunction
12691352
RequireStatus(t, rt.SendAdminRequest("PUT", "/{{.keyspace}}/_config/sync", test.dbSyncFunction), http.StatusOK)

0 commit comments

Comments
 (0)