@@ -306,19 +306,20 @@ TEST_CASE("span") {
306
306
std::string span_tag;
307
307
};
308
308
309
+ // The resulting "error" tag is always unset.
310
+ // See the next section for more information.
309
311
auto error_tag_test_case = GENERATE (values<ErrorTagTestCase>({
310
- {" 0" , 0 , " 0 " },
311
- {0 , 0 , " 0 " },
312
+ {" 0" , 0 , " " },
313
+ {0 , 0 , " " },
312
314
{" " , 0 , " " },
313
- {" false" , 0 , " false" },
314
- {false , 0 , " false" },
315
- {" 1" , 1 , " 1" },
316
- {1 , 1 , " 1" },
317
- {" any random truth-ish string or value lol" , 1 ,
318
- " any random truth-ish string or value lol" },
319
- {std::vector<ot::Value>{" hi" , 420 , true }, 1 , " [\" hi\" ,420,true]" },
320
- {" true" , 1 , " true" },
321
- {true , 1 , " true" },
315
+ {" false" , 0 , " " },
316
+ {false , 0 , " " },
317
+ {" 1" , 1 , " " },
318
+ {1 , 1 , " " },
319
+ {" any random truth-ish string or value lol" , 1 , " " },
320
+ {std::vector<ot::Value>{" hi" , 420 , true }, 1 , " " },
321
+ {" true" , 1 , " " },
322
+ {true , 1 , " " },
322
323
}));
323
324
324
325
span.SetTag (" error" , error_tag_test_case.value );
@@ -329,6 +330,127 @@ TEST_CASE("span") {
329
330
REQUIRE (result->meta [" error" ] == error_tag_test_case.span_tag );
330
331
}
331
332
333
+ SECTION (" error.* tags override error tag" ) {
334
+ // The tag name `opentracing::ext::error` is "error", which is also the
335
+ // first part of the nested tags "error.msg", "error.stack", and
336
+ // "error.type". The latter tags are significant to Error Tracking
337
+ // <https://docs.datadoghq.com/tracing/error_tracking/>.
338
+ //
339
+ // It was found that setting the "error" tag makes some parts of Datadog
340
+ // behave as if all "error.*" tags had been removed. A user who set both
341
+ // the "error" tag and the "error.msg" tag, for example, might find that
342
+ // only the "error" tag appeared in the Datadog UI.
343
+ //
344
+ // This test section verifies that the above does not happen.
345
+
346
+ auto span_id = get_id ();
347
+ Span span{logger, nullptr , buffer, get_time,
348
+ span_id, span_id, 0 , SpanContext{logger, span_id, span_id, " " , {}},
349
+ get_time (), " " , " " , " " ,
350
+ " " , " " };
351
+
352
+ using OptionalString = ot::util::variant<std::nullptr_t , std::string>;
353
+
354
+ // For each member of `ErrorTags`, `nullptr` denotes the absence of the tag.
355
+ struct ErrorTags {
356
+ OptionalString error; // "error" tag
357
+ OptionalString msg; // "error.msg" tag
358
+ OptionalString stack; // "error.stack" tag
359
+ OptionalString type; // "error.type" tag
360
+ };
361
+
362
+ struct Case {
363
+ int index; // for debugging
364
+ ErrorTags before; // before span finishes
365
+ ErrorTags after; // after span finishes
366
+ bool error_property_after;
367
+ };
368
+
369
+ // clang-format off
370
+ auto test_case = GENERATE (values<Case>({
371
+ // Before span finishes After span finishes
372
+ // ---------------------------------- ------------------------------------
373
+ // error .msg .stack .type error .msg .stack .type error?
374
+ // ---------------------------------- --------------------------------------------
375
+ // No error tags means no error.
376
+ {0 , {nullptr , nullptr , nullptr , nullptr }, {nullptr , nullptr , nullptr , nullptr }, false },
377
+ // Setting any of the "error.*" tags sets the error property.
378
+ {1 , {nullptr , " dummy" , nullptr , nullptr }, {nullptr , " dummy" , nullptr , nullptr }, true },
379
+ {2 , {nullptr , nullptr , " dummy" , nullptr }, {nullptr , nullptr , " dummy" , nullptr }, true },
380
+ {3 , {nullptr , nullptr , nullptr , " dummy" }, {nullptr , nullptr , nullptr , " dummy" }, true },
381
+ // Truthy "error" without "error.*" marks as error but sets no tags.
382
+ {4 , {" true" , nullptr , nullptr , nullptr }, {nullptr , nullptr , nullptr , nullptr }, true },
383
+ {5 , {" TRUE" , nullptr , nullptr , nullptr }, {nullptr , nullptr , nullptr , nullptr }, true },
384
+ {6 , {" 1" , nullptr , nullptr , nullptr }, {nullptr , nullptr , nullptr , nullptr }, true },
385
+ {7 , {" true" , nullptr , nullptr , nullptr }, {nullptr , nullptr , nullptr , nullptr }, true },
386
+ {8 , {" True" , nullptr , nullptr , nullptr }, {nullptr , nullptr , nullptr , nullptr }, true },
387
+ {9 , {" T" , nullptr , nullptr , nullptr }, {nullptr , nullptr , nullptr , nullptr }, true },
388
+ {10 , {" t" , nullptr , nullptr , nullptr }, {nullptr , nullptr , nullptr , nullptr }, true },
389
+ // If "error" is nonempty, not truthy, and not falsy, then set "error.msg" instead.
390
+ {11 , {" EBADF" , nullptr , nullptr , nullptr }, {nullptr , " EBADF" , nullptr , nullptr }, true },
391
+ {12 , {" 9" , nullptr , nullptr , nullptr }, {nullptr , " 9" , nullptr , nullptr }, true },
392
+ {13 , {" oops!" , nullptr , nullptr , nullptr }, {nullptr , " oops!" , nullptr , nullptr }, true },
393
+ {14 , {" -0" , nullptr , nullptr , nullptr }, {nullptr , " -0" , nullptr , nullptr }, true },
394
+ // Setting "error.*" unsets "error", but keeps the error property.
395
+ {15 , {" true" , " dummy" , nullptr , nullptr }, {nullptr , " dummy" , nullptr , nullptr }, true },
396
+ {16 , {" true" , nullptr , " dummy" , nullptr }, {nullptr , nullptr , " dummy" , nullptr }, true },
397
+ {17 , {" true" , nullptr , nullptr , " dummy" }, {nullptr , nullptr , nullptr , " dummy" }, true },
398
+ // If "error" is falsy or empty, then "error" and "error.*" tags are removed, and no error.
399
+ {18 , {" false" , " dummy" , " dummy" , " dummy" }, {nullptr , nullptr , nullptr , nullptr }, false },
400
+ {19 , {" FALSE" , " dummy" , " dummy" , " dummy" }, {nullptr , nullptr , nullptr , nullptr }, false },
401
+ {20 , {" F" , " dummy" , " dummy" , " dummy" }, {nullptr , nullptr , nullptr , nullptr }, false },
402
+ {21 , {" f" , " dummy" , " dummy" , " dummy" }, {nullptr , nullptr , nullptr , nullptr }, false },
403
+ {22 , {" 0" , " dummy" , " dummy" , " dummy" }, {nullptr , nullptr , nullptr , nullptr }, false },
404
+ {23 , {" " , " dummy" , " dummy" , " dummy" }, {nullptr , nullptr , nullptr , nullptr }, false },
405
+ }));
406
+ // clang-format on
407
+
408
+ CAPTURE (test_case.index );
409
+
410
+ if (test_case.before .error != nullptr ) {
411
+ span.SetTag (" error" , test_case.before .error .get <std::string>());
412
+ }
413
+ if (test_case.before .msg != nullptr ) {
414
+ span.SetTag (" error.msg" , test_case.before .msg .get <std::string>());
415
+ }
416
+ if (test_case.before .stack != nullptr ) {
417
+ span.SetTag (" error.stack" , test_case.before .stack .get <std::string>());
418
+ }
419
+ if (test_case.before .type != nullptr ) {
420
+ span.SetTag (" error.type" , test_case.before .type .get <std::string>());
421
+ }
422
+
423
+ span.FinishWithOptions (finish_options);
424
+ const auto & after = *buffer->traces ().at (100 ).finished_spans ->at (0 );
425
+
426
+ REQUIRE (after.error == int (test_case.error_property_after ));
427
+
428
+ if (test_case.after .error == nullptr ) {
429
+ REQUIRE (after.meta .count (" error" ) == 0 );
430
+ } else {
431
+ REQUIRE (after.meta .count (" error" ) == 1 );
432
+ REQUIRE (after.meta .at (" error" ) == test_case.after .error .get <std::string>());
433
+ }
434
+ if (test_case.after .msg == nullptr ) {
435
+ REQUIRE (after.meta .count (" error.msg" ) == 0 );
436
+ } else {
437
+ REQUIRE (after.meta .count (" error.msg" ) == 1 );
438
+ REQUIRE (after.meta .at (" error.msg" ) == test_case.after .msg .get <std::string>());
439
+ }
440
+ if (test_case.after .stack == nullptr ) {
441
+ REQUIRE (after.meta .count (" error.stack" ) == 0 );
442
+ } else {
443
+ REQUIRE (after.meta .count (" error.stack" ) == 1 );
444
+ REQUIRE (after.meta .at (" error.stack" ) == test_case.after .stack .get <std::string>());
445
+ }
446
+ if (test_case.after .type == nullptr ) {
447
+ REQUIRE (after.meta .count (" error.type" ) == 0 );
448
+ } else {
449
+ REQUIRE (after.meta .count (" error.type" ) == 1 );
450
+ REQUIRE (after.meta .at (" error.type" ) == test_case.after .type .get <std::string>());
451
+ }
452
+ }
453
+
332
454
SECTION (" operation name can be overridden" ) {
333
455
auto span_id = get_id ();
334
456
Span span{logger,
0 commit comments