Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
65c23aa
WIP: feat: metrics
jpnurmi Jan 27, 2026
a73c3bc
docs: add changelog entry for metrics feature
jpnurmi Jan 29, 2026
875c3d9
docs: add metrics to attribute documentation
jpnurmi Jan 29, 2026
3c07fa5
ref(metrics): change sentry_metrics_count to use int64_t
jpnurmi Jan 29, 2026
0e56ebb
Merge remote-tracking branch 'upstream/master' into jpnurmi/feat/metrics
jpnurmi Jan 29, 2026
ccc3870
docs(metrics): document attributes ownership transfer
jpnurmi Jan 29, 2026
52ff5d6
fix(metrics): fix memory leak when metrics disabled
jpnurmi Jan 29, 2026
f8eb035
feat(metrics): add SENTRY_UNIT_* constants for telemetry units
jpnurmi Jan 30, 2026
941a1fa
ref(metrics): remove unit parameter from sentry_metrics_count
jpnurmi Jan 30, 2026
d418485
ref(batcher): split shutdown/flush into begin/wait for parallelization
jpnurmi Jan 30, 2026
9993fc6
test(metrics): add integration tests matching logs test coverage
jpnurmi Jan 30, 2026
8b0c9c6
fix(logs): avoid modifying custom per-log attributes (#1500)
jpnurmi Jan 30, 2026
725b062
fix(metrics): scope attributes
jpnurmi Jan 30, 2026
776400e
Merge remote-tracking branch 'upstream/master' into jpnurmi/feat/metrics
jpnurmi Feb 2, 2026
d9c5e27
Update CHANGELOG.md
jpnurmi Feb 2, 2026
898c169
fix batcher shutdown split thread lifecycle guard
jpnurmi Feb 2, 2026
0326af9
consolidate sentry__envelope_add_logs/metrics shared helper
jpnurmi Feb 2, 2026
d9eec54
fix sentry__envelope_add_metrics
jpnurmi Feb 2, 2026
0154bd5
clean up unused import
jpnurmi Feb 2, 2026
c57c0c7
fix race condition in batcher force_flush_wait
jpnurmi Feb 3, 2026
a878869
Update include/sentry.h
jpnurmi Feb 4, 2026
3bff6c7
Fix formatting
jpnurmi Feb 4, 2026
9478d36
Add missing test for batching
jpnurmi Feb 4, 2026
480f943
Fix redundant batch_func initialization in batchers
jpnurmi Feb 4, 2026
ca4a0e6
Keep static batch_func initializer
jpnurmi Feb 4, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Changelog

## Unreleased

**Features**:

- Add support for metrics. It is currently experimental, and one can enable it by setting `sentry_options_set_enable_metrics`. When enabled, you can record a metric using `sentry_metrics_count()`, `sentry_metrics_gauge()`, or `sentry_metrics_distribution()`. Metrics can be filtered by setting the `before_send_metric` hook. ([#1498](https://github.com/getsentry/sentry-native/pull/1498))

## 0.12.5

**Features**:
Expand Down
151 changes: 125 additions & 26 deletions examples/example.c
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,25 @@ discarding_before_send_log_callback(sentry_value_t log, void *user_data)
return log;
}

static sentry_value_t
before_send_metric_callback(sentry_value_t metric, void *user_data)
{
(void)user_data;
sentry_value_t attribute
= sentry_value_new_attribute(sentry_value_new_string("little"), NULL);
sentry_value_set_by_key(sentry_value_get_by_key(metric, "attributes"),
"coffeepot.size", attribute);
return metric;
}

static sentry_value_t
discarding_before_send_metric_callback(sentry_value_t metric, void *user_data)
{
(void)user_data;
sentry_value_decref(metric);
return sentry_value_new_null();
}

// Test logger that outputs in a format the integration tests can parse
static void
test_logger_callback(
Expand Down Expand Up @@ -317,8 +336,8 @@ create_debug_crumb(const char *message)
}

#define NUM_THREADS 50
#define NUM_LOGS 100 // how many log calls each thread makes
#define LOG_SLEEP_MS 1 // time (in ms) between log calls
#define NUM_TELEMETRY 100 // how many telemetry calls each thread makes
#define TELEMETRY_SLEEP_MS 1 // time (in ms) between telemetry calls

#if defined(SENTRY_PLATFORM_WINDOWS)
# define sleep_ms(MILLISECONDS) Sleep(MILLISECONDS)
Expand All @@ -327,29 +346,80 @@ create_debug_crumb(const char *message)
#endif

#ifdef SENTRY_PLATFORM_WINDOWS
typedef DWORD(WINAPI *thread_func_t)(LPVOID);

DWORD WINAPI
log_thread_func(LPVOID lpParam)
{
(void)lpParam;
for (int i = 0; i < NUM_LOGS; i++) {
for (int i = 0; i < NUM_TELEMETRY; i++) {
sentry_log_debug(
"thread log %d on thread %lu", i, get_current_thread_id());
sleep_ms(LOG_SLEEP_MS);
sleep_ms(TELEMETRY_SLEEP_MS);
}
return 0;
}

DWORD WINAPI
metric_thread_func(LPVOID lpParam)
{
(void)lpParam;
for (int i = 0; i < NUM_TELEMETRY; i++) {
sentry_metrics_count("thread.counter", 1, sentry_value_new_null());
sleep_ms(TELEMETRY_SLEEP_MS);
}
return 0;
}

static void
run_threads(thread_func_t func)
{
HANDLE threads[NUM_THREADS];
for (int t = 0; t < NUM_THREADS; t++) {
threads[t] = CreateThread(NULL, 0, func, NULL, 0, NULL);
}
WaitForMultipleObjects(NUM_THREADS, threads, TRUE, INFINITE);
for (int t = 0; t < NUM_THREADS; t++) {
CloseHandle(threads[t]);
}
}
#else
typedef void *(*thread_func_t)(void *);

void *
log_thread_func(void *arg)
{
(void)arg;
for (int i = 0; i < NUM_LOGS; i++) {
for (int i = 0; i < NUM_TELEMETRY; i++) {
sentry_log_debug(
"thread log %d on thread %llu", i, get_current_thread_id());
sleep_ms(LOG_SLEEP_MS);
sleep_ms(TELEMETRY_SLEEP_MS);
}
return NULL;
}

void *
metric_thread_func(void *arg)
{
(void)arg;
for (int i = 0; i < NUM_TELEMETRY; i++) {
sentry_metrics_count("thread.counter", 1, sentry_value_new_null());
sleep_ms(TELEMETRY_SLEEP_MS);
}
return NULL;
}

static void
run_threads(thread_func_t func)
{
pthread_t threads[NUM_THREADS];
for (int t = 0; t < NUM_THREADS; t++) {
pthread_create(&threads[t], NULL, func, NULL);
}
for (int t = 0; t < NUM_THREADS; t++) {
pthread_join(threads[t], NULL);
}
}
#endif

int
Expand Down Expand Up @@ -504,6 +574,20 @@ main(int argc, char **argv)
sentry_options_set_logs_with_attributes(options, true);
}

if (has_arg(argc, argv, "enable-metrics")) {
sentry_options_set_enable_metrics(options, true);
}

if (has_arg(argc, argv, "before-send-metric")) {
sentry_options_set_before_send_metric(
options, before_send_metric_callback, NULL);
}

if (has_arg(argc, argv, "discarding-before-send-metric")) {
sentry_options_set_before_send_metric(
options, discarding_before_send_metric_callback, NULL);
}

if (0 != sentry_init(options)) {
return EXIT_FAILURE;
}
Expand Down Expand Up @@ -580,28 +664,39 @@ main(int argc, char **argv)
sentry_log_debug("post-sleep log");
}
if (has_arg(argc, argv, "logs-threads")) {
// Spawn multiple threads to test concurrent logging
#ifdef SENTRY_PLATFORM_WINDOWS
HANDLE threads[NUM_THREADS];
for (int t = 0; t < NUM_THREADS; t++) {
threads[t]
= CreateThread(NULL, 0, log_thread_func, NULL, 0, NULL);
}

WaitForMultipleObjects(NUM_THREADS, threads, TRUE, INFINITE);
run_threads(log_thread_func);
}
}

for (int t = 0; t < NUM_THREADS; t++) {
CloseHandle(threads[t]);
}
#else
pthread_t threads[NUM_THREADS];
for (int t = 0; t < NUM_THREADS; t++) {
pthread_create(&threads[t], NULL, log_thread_func, NULL);
}
for (int t = 0; t < NUM_THREADS; t++) {
pthread_join(threads[t], NULL);
if (sentry_options_get_enable_metrics(options)) {
if (has_arg(argc, argv, "capture-metric")) {
sentry_metrics_count("test.counter", 1, sentry_value_new_null());
}
if (has_arg(argc, argv, "capture-metric-all-types")) {
sentry_metrics_count("test.counter", 1, sentry_value_new_null());
sentry_metrics_gauge("test.gauge", 42.5, SENTRY_UNIT_PERCENT,
sentry_value_new_null());
sentry_metrics_distribution("test.distribution", 123.456,
SENTRY_UNIT_MILLISECOND, sentry_value_new_null());
}
if (has_arg(argc, argv, "metric-with-attributes")) {
sentry_value_t attributes = sentry_value_new_object();
sentry_value_t attr = sentry_value_new_attribute(
sentry_value_new_string("my_value"), NULL);
sentry_value_set_by_key(attributes, "my.custom.attribute", attr);
sentry_metrics_count("test.counter.with.attributes", 1, attributes);
}
if (has_arg(argc, argv, "metrics-timer")) {
for (int i = 0; i < 10; i++) {
sentry_metrics_count(
"batch.counter", 1, sentry_value_new_null());
}
#endif
sleep_s(6);
sentry_metrics_count(
"post.sleep.counter", 1, sentry_value_new_null());
}
if (has_arg(argc, argv, "metrics-threads")) {
run_threads(metric_thread_func);
}
}

Expand Down Expand Up @@ -881,6 +976,10 @@ main(int argc, char **argv)
if (has_arg(argc, argv, "logs-scoped-transaction")) {
sentry_log_debug("logging during scoped transaction event");
}
if (has_arg(argc, argv, "metrics-scoped-transaction")) {
sentry_metrics_count(
"scoped.transaction.metric", 1, sentry_value_new_null());
}
}

sentry_transaction_finish(tx);
Expand Down
128 changes: 127 additions & 1 deletion include/sentry.h
Original file line number Diff line number Diff line change
Expand Up @@ -324,9 +324,49 @@ SENTRY_API sentry_value_t sentry_value_new_user_n(const char *id, size_t id_len,
const char *username, size_t username_len, const char *email,
size_t email_len, const char *ip_address, size_t ip_address_len);

/**
* Measurement units for telemetry.
*
* These constants represent the standardized units supported by Sentry.
* Custom units can also be passed as arbitrary strings.
*
* See: https://develop.sentry.dev/sdk/telemetry/attributes/#units
*/

/* Duration units */
#define SENTRY_UNIT_NANOSECOND "nanosecond"
#define SENTRY_UNIT_MICROSECOND "microsecond"
#define SENTRY_UNIT_MILLISECOND "millisecond"
#define SENTRY_UNIT_SECOND "second"
#define SENTRY_UNIT_MINUTE "minute"
#define SENTRY_UNIT_HOUR "hour"
#define SENTRY_UNIT_DAY "day"
#define SENTRY_UNIT_WEEK "week"

/* Information units */
#define SENTRY_UNIT_BIT "bit"
#define SENTRY_UNIT_BYTE "byte"
#define SENTRY_UNIT_KILOBYTE "kilobyte"
#define SENTRY_UNIT_KIBIBYTE "kibibyte"
#define SENTRY_UNIT_MEGABYTE "megabyte"
#define SENTRY_UNIT_MEBIBYTE "mebibyte"
#define SENTRY_UNIT_GIGABYTE "gigabyte"
#define SENTRY_UNIT_GIBIBYTE "gibibyte"
#define SENTRY_UNIT_TERABYTE "terabyte"
#define SENTRY_UNIT_TEBIBYTE "tebibyte"
#define SENTRY_UNIT_PETABYTE "petabyte"
#define SENTRY_UNIT_PEBIBYTE "pebibyte"
#define SENTRY_UNIT_EXABYTE "exabyte"
#define SENTRY_UNIT_EXBIBYTE "exbibyte"

/* Fraction units */
#define SENTRY_UNIT_RATIO "ratio"
#define SENTRY_UNIT_PERCENT "percent"

/**
* Creates a new attribute object.
* value is required, unit is optional.
* value is required, unit is optional, but has to be one of the
* `SENTRY_UNIT_X` macros.
*
* value must be a bool, int, double or string `sentry_value_t`
* OR a list of bool, int, double or string (with all items being the same type)
Expand Down Expand Up @@ -1863,6 +1903,7 @@ SENTRY_API void sentry_remove_extra_n(const char *key, size_t key_len);
* Sets attributes created with `sentry_value_new_attribute` to be applied to
* all:
* - logs
* - metrics
*/
SENTRY_API void sentry_set_attribute(const char *key, sentry_value_t attribute);
SENTRY_API void sentry_set_attribute_n(
Expand Down Expand Up @@ -2126,6 +2167,91 @@ typedef sentry_value_t (*sentry_before_send_log_function_t)(
SENTRY_EXPERIMENTAL_API void sentry_options_set_before_send_log(
sentry_options_t *opts, sentry_before_send_log_function_t func, void *data);

/**
* Enables or disables the metrics feature.
* When disabled, all calls to `sentry_metrics_*()` are no-ops.
*/
SENTRY_EXPERIMENTAL_API void sentry_options_set_enable_metrics(
sentry_options_t *opts, int enable_metrics);
SENTRY_EXPERIMENTAL_API int sentry_options_get_enable_metrics(
const sentry_options_t *opts);

/**
* Type of the `before_send_metric` callback.
*
* The callback takes ownership of the `metric` and should usually return
* that same metric. In case the metric should be discarded, the
* callback needs to call `sentry_value_decref` on the provided metric and
* return a `sentry_value_new_null()` instead.
*/
typedef sentry_value_t (*sentry_before_send_metric_function_t)(
sentry_value_t metric, void *user_data);

/**
* Sets the `before_send_metric` callback.
*/
SENTRY_EXPERIMENTAL_API void sentry_options_set_before_send_metric(
sentry_options_t *opts, sentry_before_send_metric_function_t func,
void *data);

/**
* Result type for metric operations.
* - Success means the metric was enqueued
* - Discard means the `before_send_metric` callback discarded the metric
* - Failed means the metric wasn't enqueued (buffers are full)
* - Disabled means metrics are disabled
*/
typedef enum {
SENTRY_METRICS_RESULT_SUCCESS = 0,
SENTRY_METRICS_RESULT_DISCARD = 1,
SENTRY_METRICS_RESULT_FAILED = 2,
SENTRY_METRICS_RESULT_DISABLED = 3
} sentry_metrics_result_t;

/**
* Metrics interface for recording application metrics.
*
* Metrics are buffered and sent in batches. Each metric includes:
* - name: Hierarchical name with dot separators (e.g., "api.requests")
* - value: The numeric value to record
* - unit: Optional measurement unit (e.g., SENTRY_UNIT_MILLISECOND), or NULL
* - attributes: Optional sentry_value_t object with custom attributes, or
* sentry_value_new_null(). Each attribute should be created with
* sentry_value_new_attribute().
*
* Ownership of the attributes is transferred to the metric function.
*
* To re-use the same attributes, call `sentry_value_incref` on it
* before passing the attributes to the metric function.
*
* Metrics are automatically associated with the current trace and span if
* available. Default attributes (environment, release, SDK info) are attached
* automatically.
*/

/**
* Records a counter metric. Counters track incrementing values like
* request counts or error counts.
*/
SENTRY_EXPERIMENTAL_API sentry_metrics_result_t sentry_metrics_count(
const char *name, int64_t value, sentry_value_t attributes);

/**
* Records a gauge metric. Gauges track values that can go up or down,
* like memory usage or active connections.
*/
SENTRY_EXPERIMENTAL_API sentry_metrics_result_t sentry_metrics_gauge(
const char *name, double value, const char *unit,
sentry_value_t attributes);

/**
* Records a distribution metric. Distributions track the statistical
* distribution of values, useful for timing data and percentiles.
*/
SENTRY_EXPERIMENTAL_API sentry_metrics_result_t sentry_metrics_distribution(
const char *name, double value, const char *unit,
sentry_value_t attributes);

#ifdef SENTRY_PLATFORM_LINUX

/**
Expand Down
2 changes: 2 additions & 0 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ sentry_target_sources_cwd(sentry
sentry_logger.h
sentry_logs.c
sentry_logs.h
sentry_metrics.c
sentry_metrics.h
sentry_options.c
sentry_options.h
sentry_os.c
Expand Down
Loading
Loading