Custom Instrumentation
Learn how to capture performance data on any action in your app.
To capture transactions and spans customized to your organization's needs, you must first set up tracing.
To instrument certain regions of your code, you can create transactions to capture them.
// Enable tracing by setting a sample rate above 0
sentry_options_t *options = sentry_options_new();
sentry_options_set_traces_sample_rate(options, 0.2);
// ...
// (Configure tracing unrelated options as you see fit)
// ...
sentry_init(options);
// Transactions can be started by providing the name and the operation
sentry_transaction_context_t *tx_ctx = sentry_transaction_context_new(
"transaction name",
"transaction operation"
);
sentry_transaction_t *tx = sentry_transaction_start(tx_ctx, sentry_value_new_null());
// Transactions can have child spans (and those spans can have child spans as well)
sentry_span_t *child_span = sentry_transaction_start_child(
tx,
"child operation",
"child description"
);
// Starting a child of a span requires the function `sentry_span_start_child` instead
sentry_span_t *grandchild_span = sentry_span_start_child(
child_span,
"grandchild operation",
"grandchild description"
);
// ...
// (Perform the operation represented by the spans/transaction)
// ...
sentry_span_finish(grandchild_span); // Mark the spans as finished
sentry_span_finish(child_span);
sentry_transaction_finish(tx); // Mark the transaction as finished and send it to Sentry
For example, if you want to create a transaction for a user interaction in your application:
// Let's say this method is called in a background thread when a user clicks on the checkout button.
void perform_checkout() {
// This will create a new Transaction for you
sentry_transaction_context_t *tx_ctx = sentry_transaction_context_new(
"checkout",
"perform-checkout"
);
sentry_transaction_t *tx = sentry_transaction_start(tx_ctx, sentry_value_new_null());
// Validate the cart
sentry_span_t *validation_span = sentry_transaction_start_child(
tx,
"validation",
"validating shopping cart"
);
validate_shopping_cart(); // Some long process, maybe a sync http request.
sentry_span_finish(validation_span);
// Process the order
sentry_span_t *process_span = sentry_transaction_start_child(
tx,
"process",
"processing shopping cart"
);
process_shopping_cart(); // Another time consuming process.
sentry_span_finish(process_span);
sentry_transaction_finish(tx);
}
This example will send a transaction named checkout
to Sentry. The transaction will contain a validation
span that measures how long validate_shopping_cart()
took and a process
span that measures process_shopping_cart()
. Finally, the call to sentry_transaction_finish()
will finish the transaction and send it to Sentry.
By default, events will inherit the trace_id
from the propagation_context
as generated during SDK initialization.
Transactions act as automatic trace boundaries, meaning whenever you create a transaction, it will start a new trace, which the SDK will apply as soon as you scope it. Once you finish the transaction, the SDK will move the trace to the propagation_context
, from which the trace will affect any event until you scope a new transaction:
// Upon init, generates a random trace_id and span_id in the propagation_context
sentry_options_t *options = sentry_options_new();
sentry_init(options);
// Events will inherit the trace data from this propagation_context
// the transaction inside perform_checkout() will lead to a new trace in the propgation_context
perform_checkout();
// After perform_checkout() events will inherit the trace created in perform_checkout() from the propagation_context
The SDK will turn off managing automatic trace boundaries via transactions once manual management of trace boundaries was requested by either a downstream SDK (using sentry_set_trace(trace_id, parent_span_id)
) or a direct user of the Native SDK (via sentry_regenerate_trace()
):
sentry_options_t *options = sentry_options_new();
sentry_init(options);
// trace_id and parent_span_id usually originate from a downstream SDK
// (which should be the one calling `sentry_set_trace`)
const char *trace_id = "2674eb52d5874b13b560236d6c79ce8a";
const char *parent_span_id = "a0f9fdf04f1a63df";
// Set the trace propagation_context with the given data.
// Downstream SDKs should do this as early as possible.
sentry_set_trace(trace_id, parent_span_id);
// Events, transactions and spans inside authenticate_user() will be part
// of a different trace than the one created during initialization.
authenticate_user();
After the client called either function, the following transactions inherit that trace from the propagation_context
and no longer act as trace boundaries.
Downstream SDK usage
If you interact with the Native SDK in the context of a downstream SDK (for instance Android, .NET, Unity, or Unreal), we urge you not to use sentry_regenerate_trace()
since it would interfere with the traces managed from those SDKs.
Sometimes you want to measure timings in code that cannot call Native SDK functions directly (like GPU shaders). For these cases, the following explicitly timed functions can be used.
sentry_transaction_start_ts
sentry_transaction_finish_ts
sentry_transaction_start_child_ts
sentry_transaction_start_child_ts_n
sentry_span_start_child_ts
sentry_span_start_child_ts_n
sentry_span_finish_ts
They only differ from their non-timestamped counterparts by taking an additional timestamp parameter, which is the unix epoch offset in microseconds.
When using these functions, you should ensure that the provided timestamps are consistent. It is recommended to use the normal interface with automatic timestamping, and to only use the explicitly timed interface when absolutely necessary.
Be careful when spawning operations as independent threads or asynchronous tasks.
APIs provided by the SDK are not inherently thread-safe. Several constructors will contain a warning regarding thread-safety in their docstrings. Functions that operate on the return values of such constructors will also mention any locking requirements.
For example, the documentation of sentry_transaction_context_new()
, which constructs a sentry_transaction_context_t
, includes a warning in its final paragraph:
/**
* Constructs a new Transaction Context. The returned value needs to be passed
* into `sentry_transaction_start` in order to be recorded and sent to sentry.
*
* [...]
*
* The returned value is not thread-safe. Users are expected to ensure that
* appropriate locking mechanisms are implemented over the Transaction Context
* if it needs to be mutated across threads. Methods operating on the
* Transaction Context will mention what kind of expectations they carry if they
* need to mutate or access the object in a thread-safe way.
*/
Following up on that warning, sentry_transaction_context_set_name()
, which operates on a sentry_transaction_context_t
, notes that it requires a lock:
/**
* Sets the `name` on a Transaction Context, which will be used in the
* Transaction constructed off of the context.
*
* The Transaction Context should not be mutated by other functions while
* setting a name on it.
*/
Sentry errors can be linked with transactions and spans.
Errors reported to Sentry are automatically linked to any running transaction or span that is bound to the scope. There is only one global scope in the native SDK, and only one transaction or span can be bound to the scope at once. In a multi-threaded application, all errors are linked to the single transaction or span bound to the scope.
sentry_transaction_context_t *tx_ctx = sentry_transaction_context_new(
"checkout",
"perform-checkout"
);
sentry_transaction_t *tx = sentry_transaction_start(tx_ctx, sentry_value_new_null());
// Bind the transaction / span to the scope:
sentry_set_span(tx);
// Errors captured after the line above will be linked to the transaction
sentry_value_t exc = sentry_value_new_exception(
"ParseIntError",
"invalid digit found in string"
);
sentry_value_t event = sentry_value_new_event();
sentry_event_add_exception(event, exc);
sentry_capture_event(event);
sentry_transaction_finish(tx);
You can add Data Attributes to both Spans and Transactions. This data is visible in the trace explorer in Sentry. The data must be of type sentry_value_t
, which can store:
- 32-bit signed integers,
- 64-bit signed integers,
- 64-bit unsigned integers (due to a current limitation in processing, these will be converted to strings before sending),
- double-precision floating-points,
- null-terminated strings, as well as
- lists and string-keyed maps containing
sentry_value_t
entries
You can add data attributes to your transactions using the following API:
sentry_transaction_context_t *tx_ctx =
sentry_transaction_context_new("processOrderBatch()", "task");
sentry_transaction_t *tx =
sentry_transaction_start(tx_ctx, sentry_value_new_null());
sentry_transaction_set_data(tx, "my-data-attribute-1",
sentry_value_new_string("value1"));
sentry_transaction_set_data(tx, "my-data-attribute-2",
sentry_value_new_int32(42));
sentry_transaction_set_data(tx, "my-data-attribute-3",
sentry_value_new_int64(-9223372036854775808));
sentry_transaction_set_data(tx, "my-data-attribute-4",
sentry_value_new_uint64(18446744073709551615));
sentry_transaction_set_data(tx, "my-data-attribute-5",
sentry_value_new_double(3.14));
sentry_transaction_set_data(tx, "my-data-attribute-6",
sentry_value_new_bool(true));
sentry_value_t value_list = sentry_value_new_list();
sentry_value_append(value_list, sentry_value_new_string("value1"));
sentry_value_append(value_list, sentry_value_new_int32(42));
sentry_value_append(value_list, sentry_value_new_int64(-5123456789));
sentry_value_append(value_list, sentry_value_new_uint64(18446744073709551615));
sentry_value_append(value_list, sentry_value_new_double(3.14));
sentry_value_append(value_list, sentry_value_new_bool(true));
sentry_transaction_set_data(tx, "my-data-attribute-7", value_list);
sentry_value_t value_object = sentry_value_new_object();
sentry_value_set_by_key(value_object, "key_1", sentry_value_new_string("value1"));
sentry_value_set_by_key(value_object, "key_2", sentry_value_new_int32(42));
sentry_value_set_by_key(value_object, "key_3", sentry_value_new_int64(-5123456789));
sentry_value_set_by_key(value_object, "key_4", sentry_value_new_uint64(18446744073709551615));
sentry_value_set_by_key(value_object, "key_5", sentry_value_new_double(3.14));
sentry_value_set_by_key(value_object, "key_6", sentry_value_new_bool(true));
sentry_transaction_set_data(tx, "my-data-attribute-8", value_object);
You can add data attributes to your spans using the following API:
sentry_transaction_context_t *tx_ctx =
sentry_transaction_context_new("processOrderBatch()", "task");
sentry_transaction_t *tx =
sentry_transaction_start(tx_ctx, sentry_value_new_null());
sentry_span_t *span =
sentry_transaction_start_child(tx, "task", "operation");
sentry_span_set_data(span, "my-data-attribute-1",
sentry_value_new_string("value1"));
sentry_span_set_data(span, "my-data-attribute-2",
sentry_value_new_int32(42));
sentry_span_set_data(span, "my-data-attribute-3",
sentry_value_new_int64(-9223372036854775808));
sentry_span_set_data(span, "my-data-attribute-4",
sentry_value_new_uint64(18446744073709551615));
sentry_span_set_data(span, "my-data-attribute-5",
sentry_value_new_double(3.14));
sentry_span_set_data(span, "my-data-attribute-6",
sentry_value_new_bool(true));
sentry_value_t value_list = sentry_value_new_list();
sentry_value_append(value_list, sentry_value_new_string("value1"));
sentry_value_append(value_list, sentry_value_new_int32(42));
sentry_value_append(value_list, sentry_value_new_int64(-5123456789));
sentry_value_append(value_list, sentry_value_new_uint64(18446744073709551615));
sentry_value_append(value_list, sentry_value_new_double(3.14));
sentry_value_append(value_list, sentry_value_new_bool(true));
sentry_span_set_data(span, "my-data-attribute-7", value_list);
sentry_value_t value_object = sentry_value_new_object();
sentry_value_set_by_key(value_object, "key_1", sentry_value_new_string("value1"));
sentry_value_set_by_key(value_object, "key_2", sentry_value_new_int32(42));
sentry_value_set_by_key(value_object, "key_3", sentry_value_new_int64(-5123456789));
sentry_value_set_by_key(value_object, "key_4", sentry_value_new_uint64(18446744073709551615));
sentry_value_set_by_key(value_object, "key_5", sentry_value_new_double(3.14));
sentry_value_set_by_key(value_object, "key_6", sentry_value_new_bool(true));
sentry_span_set_data(span, "my-data-attribute-8", value_object);
In order to use distributed tracing with the Native SDK, follow the custom instrumentation steps.
Our documentation is open source and available on GitHub. Your contributions are welcome, whether fixing a typo (drat!) or suggesting an update ("yeah, this would be better").