1
2
3
4
5
6
7 package integration
8
9 import (
10 "context"
11 "fmt"
12 "testing"
13 "time"
14
15 "go.mongodb.org/mongo-driver/bson"
16 "go.mongodb.org/mongo-driver/internal/assert"
17 "go.mongodb.org/mongo-driver/internal/integtest"
18 "go.mongodb.org/mongo-driver/internal/logger"
19 "go.mongodb.org/mongo-driver/mongo"
20 "go.mongodb.org/mongo-driver/mongo/integration/mtest"
21 "go.mongodb.org/mongo-driver/mongo/options"
22 )
23
24 var ErrInvalidTruncation = fmt.Errorf("invalid truncation")
25
26 func clamTruncErr(mt *mtest.T, op string, want, got int) error {
27 mt.Helper()
28
29 return fmt.Errorf("%w: expected length %s %d, got %d", ErrInvalidTruncation, op, want, got)
30 }
31
32 func clamDefaultTruncLimitOp(ctx context.Context, mt *mtest.T, coll *mongo.Collection) {
33 mt.Helper()
34
35 const documentsSize = 100
36
37
38
39 docs := []interface{}{}
40 for i := 0; i < documentsSize; i++ {
41 docs = append(docs, bson.D{{"x", "y"}})
42 }
43
44
45 _, err := coll.InsertMany(ctx, docs)
46 assert.Nil(mt, err, "InsertMany error: %v", err)
47
48
49 _, err = coll.Find(ctx, bson.D{})
50 assert.Nil(mt, err, "Find error: %v", err)
51 }
52
53 func clamDefaultTruncLimitLogs(mt *mtest.T) []truncValidator {
54 mt.Helper()
55
56 const cmd = "command"
57 const rpl = "reply"
58
59 expTruncLen := len(logger.TruncationSuffix) + logger.DefaultMaxDocumentLength
60 validators := make([]truncValidator, 4)
61
62
63 validators[0] = newTruncValidator(mt, cmd, func(cmd string) error {
64 if len(cmd) != expTruncLen {
65 return clamTruncErr(mt, "=", expTruncLen, len(cmd))
66 }
67
68 return nil
69 })
70
71
72 validators[1] = newTruncValidator(mt, rpl, func(cmd string) error {
73 if len(cmd) > expTruncLen {
74 return clamTruncErr(mt, "<=", expTruncLen, len(cmd))
75 }
76
77 return nil
78 })
79
80
81 validators[2] = nil
82
83
84 validators[3] = newTruncValidator(mt, rpl, func(cmd string) error {
85 if len(cmd) != expTruncLen {
86 return clamTruncErr(mt, "=", expTruncLen, len(cmd))
87 }
88
89 return nil
90 })
91
92 return validators
93 }
94
95 func clamExplicitTruncLimitOp(ctx context.Context, mt *mtest.T, coll *mongo.Collection) {
96 mt.Helper()
97
98 result := coll.Database().RunCommand(ctx, bson.D{{"hello", true}})
99 assert.Nil(mt, result.Err(), "RunCommand error: %v", result.Err())
100 }
101
102 func clamExplicitTruncLimitLogs(mt *mtest.T) []truncValidator {
103 mt.Helper()
104
105 const cmd = "command"
106 const rpl = "reply"
107
108 expTruncLen := len(logger.TruncationSuffix) + 5
109 validators := make([]truncValidator, 2)
110
111
112 validators[0] = newTruncValidator(mt, cmd, func(cmd string) error {
113 if len(cmd) != expTruncLen {
114 return clamTruncErr(mt, "=", expTruncLen, len(cmd))
115 }
116
117 return nil
118 })
119
120
121 validators[1] = newTruncValidator(mt, rpl, func(cmd string) error {
122 if len(cmd) != expTruncLen {
123 return clamTruncErr(mt, "=", expTruncLen, len(cmd))
124 }
125
126 return nil
127 })
128
129 return validators
130 }
131
132 func clamExplicitTruncLimitFailOp(ctx context.Context, mt *mtest.T, coll *mongo.Collection) {
133 mt.Helper()
134
135 result := coll.Database().RunCommand(ctx, bson.D{{"notARealCommand", true}})
136 assert.NotNil(mt, result.Err(), "expected RunCommand error, got: %v", result.Err())
137 }
138
139 func clamExplicitTruncLimitFailLogs(mt *mtest.T) []truncValidator {
140 mt.Helper()
141
142 const fail = "failure"
143
144 expTruncLen := len(logger.TruncationSuffix) + 5
145 validators := make([]truncValidator, 2)
146
147
148 validators[0] = nil
149
150
151 validators[1] = newTruncValidator(mt, fail, func(cmd string) error {
152 if len(cmd) != expTruncLen {
153 return clamTruncErr(mt, "=", expTruncLen, len(cmd))
154 }
155
156 return nil
157 })
158
159 return validators
160 }
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175 func clamMultiByteTrunc(ctx context.Context, mt *mtest.T, coll *mongo.Collection) {
176 mt.Helper()
177
178 const multiByteCharStrLen = 50_000
179 const strToRepeat = "界"
180
181
182 multiByteCharStr := ""
183 for i := 0; i < multiByteCharStrLen; i++ {
184 multiByteCharStr += strToRepeat
185 }
186
187 _, err := coll.InsertOne(ctx, bson.D{{"x", multiByteCharStr}})
188 assert.Nil(mt, err, "InsertOne error: %v", err)
189 }
190
191 func clamMultiByteTruncLogs(mt *mtest.T) []truncValidator {
192 mt.Helper()
193
194 const cmd = "command"
195 const strToRepeat = "界"
196
197 validators := make([]truncValidator, 2)
198
199
200 validators[0] = newTruncValidator(mt, cmd, func(cmd string) error {
201
202
203 cmd = cmd[:len(cmd)-len(logger.TruncationSuffix)]
204
205
206 last3Bytes := cmd[len(cmd)-3:]
207
208
209 if last3Bytes != strToRepeat {
210 return fmt.Errorf("expected last 3 bytes to be %q, got %q", strToRepeat, last3Bytes)
211 }
212
213 return nil
214 })
215
216 return validators
217 }
218
219 func TestCommandLoggingAndMonitoringProse(t *testing.T) {
220 t.Parallel()
221
222 mt := mtest.New(t, mtest.NewOptions().CreateClient(false))
223
224 for _, tcase := range []struct {
225
226 name string
227
228
229
230
231 collectionName string
232
233
234
235 maxDocumentLength uint
236
237
238
239
240
241
242 orderedLogValidators []truncValidator
243
244
245
246
247
248 operation func(context.Context, *mtest.T, *mongo.Collection)
249
250
251
252 setup func(context.Context, *mtest.T, *mongo.Collection)
253 }{
254 {
255 name: "1 Default truncation limit",
256 collectionName: "46a624c57c72463d90f88a733e7b28b4",
257 operation: clamDefaultTruncLimitOp,
258 orderedLogValidators: clamDefaultTruncLimitLogs(mt),
259 },
260 {
261 name: "2 Explicitly configured truncation limit",
262 collectionName: "540baa64dc854ca2a639627e2f0918df",
263 maxDocumentLength: 5,
264 operation: clamExplicitTruncLimitOp,
265 orderedLogValidators: clamExplicitTruncLimitLogs(mt),
266 },
267 {
268 name: "2 Explicitly configured truncation limit for failures",
269 collectionName: "aff43dfcaa1a4014b58aaa9606f5bd44",
270 maxDocumentLength: 5,
271 operation: clamExplicitTruncLimitFailOp,
272 orderedLogValidators: clamExplicitTruncLimitFailLogs(mt),
273 },
274
275
276
277
278
279
280
281
282 {
283 name: "3.1 Truncation with multi-byte codepoints",
284 collectionName: "5ed6d1b7-e358-438a-b067-e1d1dd10fee1",
285 maxDocumentLength: 20_000,
286 operation: clamMultiByteTrunc,
287 orderedLogValidators: clamMultiByteTruncLogs(mt),
288 },
289 {
290 name: "3.2 Truncation with multi-byte codepoints",
291 collectionName: "5ed6d1b7-e358-438a-b067-e1d1dd10fee1",
292 maxDocumentLength: 20_001,
293 operation: clamMultiByteTrunc,
294 orderedLogValidators: clamMultiByteTruncLogs(mt),
295 },
296 {
297 name: "3.3 Truncation with multi-byte codepoints",
298 collectionName: "5ed6d1b7-e358-438a-b067-e1d1dd10fee1",
299 maxDocumentLength: 20_002,
300 operation: clamMultiByteTrunc,
301 orderedLogValidators: clamMultiByteTruncLogs(mt),
302 },
303 } {
304 tcase := tcase
305
306 mt.Run(tcase.name, func(mt *mtest.T) {
307 mt.Parallel()
308
309 const deadline = 10 * time.Second
310 ctx := context.Background()
311
312
313
314 if tcase.setup != nil {
315 clientOpts := options.Client().ApplyURI(mtest.ClusterURI())
316
317
318
319 ctx, cancel := context.WithTimeout(ctx, deadline)
320 defer cancel()
321
322 integtest.AddTestServerAPIVersion(clientOpts)
323
324 client, err := mongo.Connect(ctx, clientOpts)
325 assert.Nil(mt, err, "Connect error in setup: %v", err)
326
327 coll := mt.CreateCollection(mtest.Collection{
328 Name: tcase.collectionName,
329 Client: client,
330 }, false)
331
332 tcase.setup(ctx, mt, coll)
333 }
334
335
336
337 if tcase.operation == nil {
338 return
339 }
340
341
342 if len(tcase.orderedLogValidators) == 0 {
343 mt.Fatalf("no log validators provided")
344 }
345
346 sinkCtx, sinkCancel := context.WithDeadline(ctx, time.Now().Add(deadline))
347 defer sinkCancel()
348
349 validator := func(order int, level int, msg string, keysAndValues ...interface{}) error {
350
351
352 if order >= len(tcase.orderedLogValidators) {
353 return fmt.Errorf("not enough expected cases to validate")
354 }
355
356 caseValidator := tcase.orderedLogValidators[order]
357 if caseValidator == nil {
358 return nil
359 }
360
361 return tcase.orderedLogValidators[order](keysAndValues...)
362 }
363
364 sink := newTestLogSink(sinkCtx, mt, len(tcase.orderedLogValidators), validator)
365
366
367
368
369 loggerOpts := options.Logger().SetSink(sink).
370 SetComponentLevel(options.LogComponentCommand, options.LogLevelDebug)
371
372
373
374 if mdl := tcase.maxDocumentLength; mdl != 0 {
375 loggerOpts.SetMaxDocumentLength(mdl)
376 }
377
378 clientOpts := options.Client().SetLoggerOptions(loggerOpts).ApplyURI(mtest.ClusterURI())
379
380 integtest.AddTestServerAPIVersion(clientOpts)
381
382 client, err := mongo.Connect(context.Background(), clientOpts)
383 assert.Nil(mt, err, "Connect error: %v", err)
384
385 coll := mt.CreateCollection(mtest.Collection{
386 Name: tcase.collectionName,
387 Client: client,
388 }, false)
389
390 tcase.operation(ctx, mt, coll)
391
392
393 if err := <-sink.errs(); err != nil {
394 mt.Fatalf("unexpected error: %v", err)
395 }
396 })
397 }
398 }
399
View as plain text