diff --git a/CHANGELOG.md b/CHANGELOG.md index 313f4dfc3..003e48505 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ **Fixes**: - Fix event ownership (potential double-decref) in sentry_capture_minidump. ([#1669](https://github.com/getsentry/sentry-native/pull/1669)) +- Finish active trace on crash. ([#1667](https://github.com/getsentry/sentry-native/pull/1667)) ## 0.13.8 diff --git a/examples/example.c b/examples/example.c index afcc5a5f7..74d0bfd03 100644 --- a/examples/example.c +++ b/examples/example.c @@ -570,7 +570,8 @@ main(int argc, char **argv) options, sentry_transport_new(print_envelope)); } - if (has_arg(argc, argv, "capture-transaction")) { + if (has_arg(argc, argv, "capture-transaction") + || has_arg(argc, argv, "open-transaction")) { sentry_options_set_traces_sample_rate(options, 1.0); } @@ -1008,6 +1009,20 @@ main(int argc, char **argv) fflush(stdout); } + if (has_arg(argc, argv, "open-transaction")) { + // Leave a transaction + nested children unfinished; the crash + // auto-finalize should close them all. + sentry_transaction_context_t *otx_ctx + = sentry_transaction_context_new("open.tx", "op"); + sentry_transaction_t *otx + = sentry_transaction_start(otx_ctx, sentry_value_new_null()); + sentry_set_transaction_object(otx); + sentry_span_t *ospan + = sentry_transaction_start_child(otx, "open.span", NULL); + sentry_set_span( + sentry_span_start_child(ospan, "open.grand.span", NULL)); + } + if (has_arg(argc, argv, "crash")) { trigger_crash(); } diff --git a/src/backends/sentry_backend_breakpad.cpp b/src/backends/sentry_backend_breakpad.cpp index 20d9f878f..5506ebd25 100644 --- a/src/backends/sentry_backend_breakpad.cpp +++ b/src/backends/sentry_backend_breakpad.cpp @@ -18,6 +18,7 @@ extern "C" { #include "sentry_screenshot.h" #include "sentry_string.h" #include "sentry_sync.h" +#include "sentry_tracing.h" #include "sentry_transport.h" #include "sentry_unix_pageallocator.h" #include "transports/sentry_disk_transport.h" @@ -129,6 +130,9 @@ breakpad_backend_callback(const google_breakpad::MinidumpDescriptor &descriptor, SENTRY_WITH_OPTIONS (options) { sentry__write_crash_marker(options); + sentry_value_t transaction + = sentry__trace_finish(SENTRY_SPAN_STATUS_ABORTED); + bool should_handle = true; if (options->on_crash_func) { @@ -202,9 +206,17 @@ breakpad_backend_callback(const google_breakpad::MinidumpDescriptor &descriptor, } if (!sentry__launch_external_crash_reporter(options, envelope)) { - // capture the envelope with the disk transport sentry_transport_t *disk_transport = sentry_new_disk_transport(options->run); + if (!sentry_value_is_null(transaction)) { + sentry_envelope_t *tx_envelope + = sentry__prepare_transaction( + options, transaction, nullptr); + if (tx_envelope) { + sentry__capture_envelope( + disk_transport, tx_envelope, options); + } + } sentry__capture_envelope(disk_transport, envelope, options); sentry__transport_dump_queue(disk_transport, options->run); sentry_transport_free(disk_transport); @@ -218,6 +230,7 @@ breakpad_backend_callback(const google_breakpad::MinidumpDescriptor &descriptor, SENTRY_SIGNAL_SAFE_LOG( "DEBUG event was discarded by the `on_crash` hook"); sentry_value_decref(event); + sentry_value_decref(transaction); } // after capturing the crash event, try to dump all the in-flight diff --git a/src/backends/sentry_backend_inproc.c b/src/backends/sentry_backend_inproc.c index 6b7d6b395..ea3b8e718 100644 --- a/src/backends/sentry_backend_inproc.c +++ b/src/backends/sentry_backend_inproc.c @@ -18,6 +18,7 @@ #include "sentry_scope.h" #include "sentry_screenshot.h" #include "sentry_sync.h" +#include "sentry_tracing.h" #include "sentry_transport.h" #include "sentry_unix_pageallocator.h" #include "transports/sentry_disk_transport.h" @@ -1156,6 +1157,9 @@ process_ucontext_deferred(const sentry_ucontext_t *uctx, bool should_handle = true; sentry__write_crash_marker(options); + sentry_value_t transaction + = sentry__trace_finish(SENTRY_SPAN_STATUS_ABORTED); + if (options->on_crash_func && !skip_hooks) { SENTRY_DEBUG("invoking `on_crash` hook"); event = options->on_crash_func(uctx, event, options->on_crash_data); @@ -1185,9 +1189,6 @@ process_ucontext_deferred(const sentry_ucontext_t *uctx, sentry_envelope_t *envelope = sentry__prepare_event(options, event, NULL, !options->on_crash_func && !skip_hooks, NULL); - // TODO(tracing): Revisit when investigating transaction flushing - // during hard crashes. - sentry_session_t *session = sentry__end_current_session_with_status( SENTRY_SESSION_STATUS_CRASHED); sentry__envelope_add_session(envelope, session); @@ -1203,9 +1204,16 @@ process_ucontext_deferred(const sentry_ucontext_t *uctx, } if (!sentry__launch_external_crash_reporter(options, envelope)) { - // capture the envelope with the disk transport sentry_transport_t *disk_transport = sentry_new_disk_transport(options->run); + if (!sentry_value_is_null(transaction)) { + sentry_envelope_t *tx_envelope + = sentry__prepare_transaction(options, transaction, NULL); + if (tx_envelope) { + sentry__capture_envelope( + disk_transport, tx_envelope, options); + } + } sentry__capture_envelope(disk_transport, envelope, options); sentry__transport_dump_queue(disk_transport, options->run); sentry_transport_free(disk_transport); @@ -1213,6 +1221,7 @@ process_ucontext_deferred(const sentry_ucontext_t *uctx, } else { SENTRY_DEBUG("event was discarded by the `on_crash` hook"); sentry_value_decref(event); + sentry_value_decref(transaction); } // after capturing the crash event, dump all the envelopes to disk diff --git a/src/backends/sentry_backend_native.c b/src/backends/sentry_backend_native.c index 9d43d6c3a..5ec7c2cea 100644 --- a/src/backends/sentry_backend_native.c +++ b/src/backends/sentry_backend_native.c @@ -34,6 +34,7 @@ #include "sentry_scope.h" #include "sentry_session.h" #include "sentry_sync.h" +#include "sentry_tracing.h" #include "sentry_transport.h" #include "transports/sentry_disk_transport.h" @@ -847,6 +848,9 @@ native_backend_except(sentry_backend_t *backend, const sentry_ucontext_t *uctx) // Write crash marker sentry__write_crash_marker(options); + sentry_value_t transaction + = sentry__trace_finish(SENTRY_SPAN_STATUS_ABORTED); + // Create crash event sentry_value_t event = sentry_value_new_event(); sentry_value_set_by_key( @@ -948,26 +952,33 @@ native_backend_except(sentry_backend_t *backend, const sentry_ucontext_t *uctx) = sentry__end_current_session_with_status( SENTRY_SESSION_STATUS_CRASHED); - if (session) { - sentry_envelope_t *envelope = sentry__envelope_new(); - if (envelope) { - sentry__envelope_add_session(envelope, session); - - // Write session envelope to disk - sentry_transport_t *disk_transport - = sentry_new_disk_transport(options->run); - if (disk_transport) { - // sentry__capture_envelope takes ownership of - // envelope - sentry__capture_envelope( - disk_transport, envelope, options); - sentry__transport_dump_queue( - disk_transport, options->run); - sentry_transport_free(disk_transport); - } else { - // Failed to create transport, free envelope - sentry_envelope_free(envelope); + if (session || !sentry_value_is_null(transaction)) { + sentry_transport_t *disk_transport + = sentry_new_disk_transport(options->run); + if (disk_transport) { + if (!sentry_value_is_null(transaction)) { + sentry_envelope_t *tx_envelope + = sentry__prepare_transaction( + options, transaction, NULL); + if (tx_envelope) { + sentry__capture_envelope( + disk_transport, tx_envelope, options); + } + } + if (session) { + sentry_envelope_t *envelope + = sentry__envelope_new(); + if (envelope) { + sentry__envelope_add_session(envelope, session); + sentry__capture_envelope( + disk_transport, envelope, options); + } } + sentry__transport_dump_queue( + disk_transport, options->run); + sentry_transport_free(disk_transport); + } else { + sentry_value_decref(transaction); } } @@ -980,6 +991,7 @@ native_backend_except(sentry_backend_t *backend, const sentry_ucontext_t *uctx) } else { SENTRY_DEBUG("event was discarded by the `on_crash` hook"); sentry_value_decref(event); + sentry_value_decref(transaction); } } } diff --git a/src/sentry_core.c b/src/sentry_core.c index 36d0b5b69..65085ece3 100644 --- a/src/sentry_core.c +++ b/src/sentry_core.c @@ -1302,6 +1302,20 @@ sentry_transaction_finish(sentry_transaction_t *opaque_tx) sentry_uuid_t sentry_transaction_finish_ts( sentry_transaction_t *opaque_tx, uint64_t timestamp) +{ + sentry_value_t tx = sentry__transaction_finish_value(opaque_tx, timestamp); + if (sentry_value_is_null(tx)) { + return sentry_uuid_nil(); + } + + // This takes ownership of the transaction, generates an event ID, merges + // scope + return sentry__capture_event(tx, NULL); +} + +sentry_value_t +sentry__transaction_finish_value( + sentry_transaction_t *opaque_tx, uint64_t timestamp) { if (!opaque_tx || sentry_value_is_null(opaque_tx->inner)) { SENTRY_WARN("no transaction available to finish"); @@ -1382,12 +1396,10 @@ sentry_transaction_finish_ts( sentry__transaction_decref(opaque_tx); - // This takes ownership of the transaction, generates an event ID, merges - // scope - return sentry__capture_event(tx, NULL); + return tx; fail: sentry__transaction_decref(opaque_tx); - return sentry_uuid_nil(); + return sentry_value_new_null(); } void @@ -1542,6 +1554,8 @@ sentry_span_finish_ts(sentry_span_t *opaque_span, uint64_t timestamp) goto fail; } + sentry__transaction_remove_child(opaque_root_transaction, opaque_span); + sentry_value_t root_transaction = opaque_root_transaction->inner; if (!sentry_value_is_true( diff --git a/src/sentry_core.h b/src/sentry_core.h index c0b6f60fb..ad3026e9b 100644 --- a/src/sentry_core.h +++ b/src/sentry_core.h @@ -89,6 +89,9 @@ sentry_uuid_t sentry__capture_event( sentry_envelope_t *sentry__prepare_transaction(const sentry_options_t *options, sentry_value_t transaction, sentry_uuid_t *event_id); +sentry_value_t sentry__transaction_finish_value( + sentry_transaction_t *opaque_tx, uint64_t timestamp); + /** * This function will submit the `envelope` to the given `transport`, first * checking for consent. diff --git a/src/sentry_sampling_context.h b/src/sentry_sampling_context.h index 59a069258..3d7ec5253 100644 --- a/src/sentry_sampling_context.h +++ b/src/sentry_sampling_context.h @@ -2,7 +2,8 @@ #ifndef SENTRY_SAMPLING_CONTEXT_H_INCLUDED #define SENTRY_SAMPLING_CONTEXT_H_INCLUDED -#include "sentry_tracing.h" +#include "sentry_boot.h" +#include "sentry_value.h" typedef struct sentry_sampling_context_s { sentry_transaction_context_t *transaction_context; diff --git a/src/sentry_tracing.c b/src/sentry_tracing.c index 8ef7690bc..c43da69f1 100644 --- a/src/sentry_tracing.c +++ b/src/sentry_tracing.c @@ -336,6 +336,10 @@ sentry__transaction_new(sentry_value_t inner) } tx->inner = inner; + sentry__mutex_init(&tx->children_mutex); + tx->children = NULL; + tx->children_count = 0; + tx->children_cap = 0; return tx; } @@ -357,12 +361,30 @@ sentry__transaction_decref(sentry_transaction_t *tx) if (sentry_value_refcount(tx->inner) <= 1) { sentry_value_decref(tx->inner); + sentry_free(tx->children); + sentry__mutex_free(&tx->children_mutex); sentry_free(tx); } else { sentry_value_decref(tx->inner); } } +void +sentry__transaction_remove_child(sentry_transaction_t *tx, sentry_span_t *span) +{ + if (!tx || !span) { + return; + } + sentry__mutex_lock(&tx->children_mutex); + for (size_t i = 0; i < tx->children_count; i++) { + if (tx->children[i] == span) { + tx->children[i] = tx->children[--tx->children_count]; + break; + } + } + sentry__mutex_unlock(&tx->children_mutex); +} + void sentry__span_incref(sentry_span_t *span) { @@ -379,6 +401,7 @@ sentry__span_decref(sentry_span_t *span) } if (sentry_value_refcount(span->inner) <= 1) { + sentry__transaction_remove_child(span->transaction, span); sentry_value_decref(span->inner); sentry__transaction_decref(span->transaction); sentry_free(span); @@ -404,6 +427,28 @@ sentry__span_new(sentry_transaction_t *tx, sentry_value_t inner) sentry__transaction_incref(tx); span->transaction = tx; + sentry__mutex_lock(&tx->children_mutex); + if (tx->children_count == tx->children_cap) { + size_t new_cap = tx->children_cap ? tx->children_cap * 2 : 4; + sentry_span_t **new_children + = sentry_malloc(new_cap * sizeof(sentry_span_t *)); + if (new_children) { + if (tx->children) { + memcpy(new_children, tx->children, + tx->children_count * sizeof(sentry_span_t *)); + sentry_free(tx->children); + } + tx->children = new_children; + tx->children_cap = new_cap; + } else { + SENTRY_WARN("failed to track live span for crash auto-finalize"); + } + } + if (tx->children_count < tx->children_cap) { + tx->children[tx->children_count++] = span; + } + sentry__mutex_unlock(&tx->children_mutex); + return span; } @@ -831,3 +876,95 @@ sentry_transaction_iter_headers(sentry_transaction_t *tx, sentry__span_iter_headers(tx->inner, callback, userdata); } } + +typedef struct { + sentry_span_t *saved_span; + sentry_transaction_t *saved_tx_obj; + sentry_transaction_t *active_tx; +} saved_trace_t; + +static saved_trace_t +save_active_trace(void) +{ + saved_trace_t s = { 0 }; + SENTRY_WITH_SCOPE (scope) { + if (scope->span) { + sentry__span_incref(scope->span); + s.saved_span = scope->span; + } + if (scope->transaction_object) { + sentry__transaction_incref(scope->transaction_object); + s.saved_tx_obj = scope->transaction_object; + } + } + s.active_tx = s.saved_span && s.saved_span->transaction + ? s.saved_span->transaction + : s.saved_tx_obj; + if (s.active_tx) { + sentry__transaction_incref(s.active_tx); + } + return s; +} + +static void +restore_active_trace(saved_trace_t *s) +{ + SENTRY_WITH_SCOPE_MUT (scope) { + if (!scope->span && s->saved_span) { + scope->span = s->saved_span; + s->saved_span = NULL; + } + if (!scope->transaction_object && s->saved_tx_obj) { + scope->transaction_object = s->saved_tx_obj; + s->saved_tx_obj = NULL; + } + } + sentry__span_decref(s->saved_span); + sentry__transaction_decref(s->saved_tx_obj); +} + +// Atomically swap the live-children list off `tx` and finish each span. +// The swap ensures `sentry_span_finish_ts`'s per-span remove-scan is a no-op. +static void +finish_children( + sentry_transaction_t *tx, sentry_span_status_t status, uint64_t end_ts) +{ + sentry__mutex_lock(&tx->children_mutex); + sentry_span_t **children = tx->children; + size_t count = tx->children_count; + tx->children = NULL; + tx->children_count = 0; + tx->children_cap = 0; + sentry__mutex_unlock(&tx->children_mutex); + + for (size_t i = count; i-- > 0;) { + sentry_span_t *child = children[i]; + sentry__span_incref(child); + sentry_span_set_status(child, status); + sentry_span_finish_ts(child, end_ts); + } + sentry_free(children); +} + +sentry_value_t +sentry__trace_finish(sentry_span_status_t status) +{ + // Save/restore scope around the finish so the crash event captured next + // still inherits the active trace context (cf. sentry-cocoa's + // `finishTracer:shouldCleanUp:NO`). Finished spans retain their ids; only + // `timestamp` is added. + saved_trace_t s = save_active_trace(); + if (!s.active_tx) { + restore_active_trace(&s); + return sentry_value_new_null(); + } + + uint64_t end_ts = sentry__usec_time(); + finish_children(s.active_tx, status, end_ts); + + sentry_transaction_set_status(s.active_tx, status); + sentry_value_t tx = sentry__transaction_finish_value(s.active_tx, end_ts); + + restore_active_trace(&s); + return tx; +} diff --git a/src/sentry_tracing.h b/src/sentry_tracing.h index f72836f6f..44e8b23a0 100644 --- a/src/sentry_tracing.h +++ b/src/sentry_tracing.h @@ -2,6 +2,7 @@ #define SENTRY_TRACING_H_INCLUDED #include "sentry_slice.h" +#include "sentry_sync.h" #include "sentry_value.h" // W3C traceparent header: 00--- @@ -33,6 +34,13 @@ struct sentry_transaction_context_s { */ struct sentry_transaction_s { sentry_value_t inner; + // Live (unfinished) child spans, so `sentry__trace_finish` can close them + // out on crash. Weak pointers: entries do not own a ref — spans remove + // themselves via `sentry__transaction_remove_child` on finish or decref. + sentry_mutex_t children_mutex; + sentry_span_t **children; + size_t children_count; + size_t children_cap; }; void sentry__transaction_context_free(sentry_transaction_context_t *tx_ctx); @@ -41,6 +49,22 @@ sentry_transaction_t *sentry__transaction_new(sentry_value_t inner); void sentry__transaction_incref(sentry_transaction_t *tx); void sentry__transaction_decref(sentry_transaction_t *tx); +/** + * Unlists `span` from the transaction's live-children list. No-op if not + * found. Does not decref (the list holds weak pointers). + */ +void sentry__transaction_remove_child( + sentry_transaction_t *tx, sentry_span_t *span); + +/** + * Finishes the active transaction (if any) with `status`, closing out every + * in-flight child span in leaf-first order and returning the tx payload. + * `scope->span` / `scope->transaction_object` are preserved so a + * subsequently-captured crash event still inherits the active trace context. + * Returns null if nothing is active. + */ +sentry_value_t sentry__trace_finish(sentry_span_status_t status); + void sentry__span_incref(sentry_span_t *span); void sentry__span_decref(sentry_span_t *span); diff --git a/tests/test_integration_http.py b/tests/test_integration_http.py index a8a3a3565..60f45e869 100644 --- a/tests/test_integration_http.py +++ b/tests/test_integration_http.py @@ -851,6 +851,86 @@ def test_native_crash_http(cmake, httpserver): assert_breadcrumb(envelope) assert_attachment(envelope) +@pytest.mark.parametrize( + "backend", + [ + "inproc", + pytest.param( + "breakpad", + marks=pytest.mark.skipif( + not has_breakpad or is_qemu, reason="test needs breakpad backend" + ), + ), + pytest.param( + "native", + marks=pytest.mark.skipif( + not has_native or is_qemu or is_kcov, + reason="test needs native backend", + ), + ), + ], +) +def test_trace_finish_on_crash(cmake, httpserver, backend): + """The backend's crash handler calls `sentry__trace_finish`, so an + unfinished transaction on the scope ships alongside the crash.""" + tmp_path = cmake(["sentry_example"], {"SENTRY_BACKEND": backend}) + + httpserver.expect_oneshot_request( + "/api/123456/envelope/", + headers={"x-sentry-auth": auth_header}, + ).respond_with_data("OK") + httpserver.expect_oneshot_request( + "/api/123456/envelope/", + headers={"x-sentry-auth": auth_header}, + ).respond_with_data("OK") + env = dict(os.environ, SENTRY_DSN=make_dsn(httpserver)) + + with httpserver.wait(timeout=10) as waiting: + run( + tmp_path, + "sentry_example", + ["log", "open-transaction", "crash"], + expect_failure=True, + env=env, + ) + if backend != "native": + # inproc/breakpad cache to disk; the next launch ships them. + run(tmp_path, "sentry_example", ["log", "no-setup"], env=env) + assert waiting.result + + tx_items = [ + item + for req, _ in httpserver.log + for item in Envelope.deserialize(req.get_data()).items + if item.headers.get("type") == "transaction" + ] + assert tx_items + + tx = tx_items[0].payload.json + assert tx["contexts"]["trace"]["status"] == "aborted" + spans = tx.get("spans", []) + # Every in-flight child is finished, not just the deepest. + for op in ("open.span", "open.grand.span"): + span = next((s for s in spans if s.get("op") == op), None) + assert span is not None, f"missing {op} in {[s.get('op') for s in spans]}" + assert span.get("status") == "aborted" + assert span.get("timestamp") + + # The crash event nests under the deepest active span via matching + # trace_id + span_id. + event_items = [ + item + for req, _ in httpserver.log + for item in Envelope.deserialize(req.get_data()).items + if item.headers.get("type") == "event" + ] + assert event_items + event = event_items[0].payload.json + grand = next(s for s in spans if s.get("op") == "open.grand.span") + assert event["contexts"]["trace"]["trace_id"] == tx["contexts"]["trace"]["trace_id"] + assert event["contexts"]["trace"]["span_id"] == grand["span_id"] + assert event.get("level") == "fatal" + @pytest.mark.skipif(not has_files, reason="test needs a local filesystem") def test_http_retry_on_network_error(cmake, httpserver, unreachable_dsn): diff --git a/tests/unit/test_tracing.c b/tests/unit/test_tracing.c index 96843011b..680ad074c 100644 --- a/tests/unit/test_tracing.c +++ b/tests/unit/test_tracing.c @@ -776,6 +776,71 @@ check_spans(sentry_envelope_t *envelope, void *data) sentry_envelope_free(envelope); } +static void +count_envelope(sentry_envelope_t *envelope, void *data) +{ + uint64_t *called = data; + *called += 1; + sentry_envelope_free(envelope); +} + +SENTRY_TEST(trace_finish) +{ + // No active span/tx: no-op, no crash. + sentry_value_decref(sentry__trace_finish(SENTRY_SPAN_STATUS_ABORTED)); + + uint64_t called = 0; + SENTRY_TEST_OPTIONS_NEW(options); + sentry_options_set_dsn(options, "https://foo@sentry.invalid/42"); + sentry_options_set_auto_session_tracking(options, 0); + + sentry_transport_t *transport = sentry_transport_new(count_envelope); + sentry_transport_set_state(transport, &called); + sentry_options_set_transport(options, transport); + sentry_options_set_traces_sample_rate(options, 1.0); + sentry_init(options); + + sentry_transaction_context_t *ctx + = sentry_transaction_context_new("root", "op"); + sentry_transaction_t *tx + = sentry_transaction_start(ctx, sentry_value_new_null()); + sentry_set_transaction_object(tx); + + sentry_span_t *child + = sentry_transaction_start_child(tx, "child-op", "child"); + sentry_span_t *grand = sentry_span_start_child(child, "grand-op", "grand"); + sentry_set_span(grand); + + sentry_value_t finished = sentry__trace_finish(SENTRY_SPAN_STATUS_ABORTED); + TEST_CHECK(!sentry_value_is_null(finished)); + CHECK_STRING_PROPERTY(sentry_value_get_by_key( + sentry_value_get_by_key(finished, "contexts"), "trace"), + "status", "aborted"); + + sentry_value_t spans = sentry_value_get_by_key(finished, "spans"); + TEST_CHECK_INT_EQUAL(sentry_value_get_length(spans), 2); + for (size_t i = 0; i < sentry_value_get_length(spans); i++) { + sentry_value_t span = sentry_value_get_by_index(spans, i); + CHECK_STRING_PROPERTY(span, "status", "aborted"); + TEST_CHECK( + !sentry_value_is_null(sentry_value_get_by_key(span, "timestamp"))); + } + sentry_value_decref(finished); + + // Scope still points at the (finished) span so a subsequent crash event + // inherits its trace context. + SENTRY_WITH_SCOPE (scope) { + TEST_CHECK(scope->span != NULL); + } + + sentry__span_decref(grand); + sentry__span_decref(child); + sentry__transaction_decref(tx); + + sentry_close(); + TEST_CHECK_INT_EQUAL(called, 0); +} + SENTRY_TEST(drop_unfinished_spans) { uint64_t called_transport = 0; diff --git a/tests/unit/tests.inc b/tests/unit/tests.inc index 8d8a37981..463c451b0 100644 --- a/tests/unit/tests.inc +++ b/tests/unit/tests.inc @@ -264,6 +264,7 @@ XX(string_address_format) XX(symbolizer) XX(task_queue) XX(thread_without_name_still_valid) +XX(trace_finish) XX(traceparent_header_disabled_by_default) XX(traceparent_header_generation) XX(transaction_name_backfill_on_finish)