Custom Instrumentation

To instrument certain regions of your code, you can create transactions to capture them.

Copied
// Enable performance monitoring by setting a sample rate above 0
sentry_options_t *options = sentry_options_new();
sentry_options_set_traces_sample_rate(options, 0.2);
...
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 *span = sentry_transaction_start_child(
    tx,
    "child operation",
    "child description",
);

// ...
// (Perform the operation represented by the span/transaction)
// ...

sentry_span_finish(span); // Mark the span as finished
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:

Copied
// 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.

Thread Safety

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:

Copied
/**
 * 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:

Copied
/**
 * 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.
 */

Connect Errors With Spans

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.

Copied
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);

Distributed Tracing

Traces can bridge across multiple software services. Each span in a trace can be represented as a sentry-trace header, containing the trace id, span id, and sampling details. This sentry-trace header can be passed to downstream services so that they can create spans that are a continuation of the trace started in the originating service.

To obtain trace headers from a transaction, use sentry_transaction_iter_headers(). For a span, use sentry_span_iter_headers().Pass the returned value to the downstream service. If communication happens over HTTP, we recommend you attach all headers to the outgoing HTTP request.

Continuing a trace from an upstream service requires using sentry_transaction_context_update_from_header(). Before starting a transaction, pass its transaction context into the previous function along with the sentry-trace header. The transaction started with the transaction context will contain everything needed to continue the trace.

To obtain headers from a transaction so it can be continued from a downstream service, define a function which merges the headers into some aggregate object. Use the function in sentry_transaction_iter_headers() as a callback. The following example uses sentry_value_t as the aggregate object:

Copied
static void
copy_headers_to(const char *key, const char *value, void *userdata)
{
    sentry_value_t *headers
        = (sentry_value_t *)userdata;
    sentry_value_set_by_key(*headers, key, value);
}

int main(int argc, char **argv) {
  ...
	// Transaction to continue off of
	sentry_transaction_context_t *tx_ctx = sentry_transaction_context_new(
        "honk",
        NULL,
    );
    sentry_transaction_t *tx = sentry_transaction_start(tx_ctx, sentry_value_new_null());

	sentry_value_t *headers = sentry_value_new_object();
	sentry_transaction_iter_headers(tx, copy_headers_to, (void *)headers);
}

To create a transaction as a continuation of a trace retrieved from an upstream service, pass an iterator of the incoming headers to the transaction context:

Copied
	sentry_transaction_context_t *tx_ctx = sentry_transaction_context_new(
        "honk",
        NULL,
    );
    sentry_transaction_context_update_from_header(
        tx_ctx,
        "sentry-trace",
        "atraceid-aspanid-issampled",
    );
Help improve this content
Our documentation is open source and available on GitHub. Your contributions are welcome, whether fixing a typo (drat!) to suggesting an update ("yeah, this would be better").