Skip to content
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
8701a31
add sentry logs option
JoshuaMoelans Jun 11, 2025
c83b3ee
add sentry logs option to example
JoshuaMoelans Jun 12, 2025
4ede812
feat(logs): add sentry log API + send first logs (#1272)
JoshuaMoelans Jul 28, 2025
c4737f0
Merge remote-tracking branch 'origin/master' into joshua/feat/logs
JoshuaMoelans Jul 31, 2025
c48bada
Merge branch 'master' into joshua/feat/logs
JoshuaMoelans Jul 31, 2025
ad7694b
fixed log parameter conversion
JoshuaMoelans Jul 31, 2025
8f0fa12
update example to avoid warning-as-error
JoshuaMoelans Jul 31, 2025
c18e451
feat(logs): batching (#1338)
JoshuaMoelans Sep 12, 2025
c6760e7
Merge branch 'master' into joshua/feat/logs
JoshuaMoelans Sep 12, 2025
da167be
update CHANGELOG.md
JoshuaMoelans Sep 12, 2025
97582ea
use `trace_id` from scoped spans for logs
JoshuaMoelans Sep 12, 2025
f8afac7
fix copy-paste leftover + docs
JoshuaMoelans Sep 12, 2025
d76baf2
add log_sleep for thread test + variable NUM_LOGS
JoshuaMoelans Sep 12, 2025
d109d3f
no `usleep` on windows :(
JoshuaMoelans Sep 12, 2025
7cde053
fix seconds->milliseconds
JoshuaMoelans Sep 12, 2025
acea37c
cleanup
JoshuaMoelans Sep 12, 2025
71e06b4
test(logs): add 32-bit vargs test (#1370)
JoshuaMoelans Sep 22, 2025
0d792d2
Apply suggestions from code review
JoshuaMoelans Sep 22, 2025
711b661
Merge branch 'master' into joshua/feat/logs
JoshuaMoelans Sep 22, 2025
5093025
post-merge cleanup
JoshuaMoelans Sep 22, 2025
725119c
pin ruamel version
JoshuaMoelans Sep 22, 2025
46c3211
let's unpin ruamel.yaml.clib to get 0.2.14
supervacuus Sep 23, 2025
f25a2d6
add empty payload check
JoshuaMoelans Sep 23, 2025
2cc3f46
log output of logger tests if we fail to find the pre-crash marker
supervacuus Sep 23, 2025
4e7475c
fix: move `is_tsan` marker into the `has_crashpad` condition...
supervacuus Sep 23, 2025
1d13d7d
fix: update `has_crashpad` condition comment
supervacuus Sep 23, 2025
bbb4086
really only move `is_tsan`, but keep the module level `pytestmark`
supervacuus Sep 23, 2025
52480ab
CHANGELOG.md update
JoshuaMoelans Sep 23, 2025
ad1ecd0
CHANGELOG.md update
JoshuaMoelans Sep 23, 2025
16fbfde
CHANGELOG.md update
JoshuaMoelans Sep 23, 2025
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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@

- Add `user_data` parameter to `traces_sampler`. ([#1346](https://github.com/getsentry/sentry-native/pull/1346))

**Features**:

- Add support for structured logs. ([#1271](https://github.com/getsentry/sentry-native/pull/1271/))

**Internal:**

- Support downstream Xbox SDK specifying networking initialization mechanism. ([#1359](https://github.com/getsentry/sentry-native/pull/1359))
Expand Down
129 changes: 128 additions & 1 deletion examples/example.c
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
# define sleep_s(SECONDS) Sleep((SECONDS) * 1000)
#else

# include <pthread.h>
# include <signal.h>
# include <unistd.h>

Expand Down Expand Up @@ -158,6 +159,33 @@ discarding_before_transaction_callback(sentry_value_t tx, void *user_data)
return tx;
}

static sentry_value_t
before_send_log_callback(sentry_value_t log, void *user_data)
{
(void)user_data;
sentry_value_t attribute = sentry_value_new_object();
sentry_value_set_by_key(
attribute, "value", sentry_value_new_string("little"));
sentry_value_set_by_key(
attribute, "type", sentry_value_new_string("string"));
sentry_value_set_by_key(sentry_value_get_by_key(log, "attributes"),
"coffeepot.size", attribute);
return log;
}

static sentry_value_t
discarding_before_send_log_callback(sentry_value_t log, void *user_data)
{
(void)user_data;
if (sentry_value_is_null(
sentry_value_get_by_key(sentry_value_get_by_key(log, "attributes"),
"sentry.message.template"))) {
sentry_value_decref(log);
return sentry_value_new_null();
}
return log;
}

static void
print_envelope(sentry_envelope_t *envelope, void *unused_state)
{
Expand Down Expand Up @@ -275,6 +303,42 @@ create_debug_crumb(const char *message)
return debug_crumb;
}

#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

#if defined(SENTRY_PLATFORM_WINDOWS)
# define sleep_ms(MILLISECONDS) Sleep(MILLISECONDS)
#else
# define sleep_ms(MILLISECONDS) usleep(MILLISECONDS * 1000)
#endif

#ifdef SENTRY_PLATFORM_WINDOWS
DWORD WINAPI
log_thread_func(LPVOID lpParam)
{
(void)lpParam;
for (int i = 0; i < NUM_LOGS; i++) {
sentry_log_debug(
"thread log %d on thread %lu", i, get_current_thread_id());
sleep_ms(LOG_SLEEP_MS);
}
return 0;
}
#else
void *
log_thread_func(void *arg)
{
(void)arg;
for (int i = 0; i < NUM_LOGS; i++) {
sentry_log_debug(
"thread log %d on thread %llu", i, get_current_thread_id());
sleep_ms(LOG_SLEEP_MS);
}
return NULL;
}
#endif

int
main(int argc, char **argv)
{
Expand Down Expand Up @@ -348,6 +412,16 @@ main(int argc, char **argv)
options, discarding_before_transaction_callback, NULL);
}

if (has_arg(argc, argv, "before-send-log")) {
sentry_options_set_before_send_log(
options, before_send_log_callback, NULL);
}

if (has_arg(argc, argv, "discarding-before-send-log")) {
sentry_options_set_before_send_log(
options, discarding_before_send_log_callback, NULL);
}

if (has_arg(argc, argv, "traces-sampler")) {
sentry_options_set_traces_sampler(
options, traces_sampler_callback, NULL);
Expand Down Expand Up @@ -385,14 +459,59 @@ main(int argc, char **argv)
sentry_options_add_view_hierarchy(options, "./view-hierarchy.json");
}

sentry_init(options);
if (has_arg(argc, argv, "enable-logs")) {
sentry_options_set_enable_logs(options, true);
}

if (0 != sentry_init(options)) {
return EXIT_FAILURE;
}

if (has_arg(argc, argv, "attachment")) {
sentry_attachment_t *bytes
= sentry_attach_bytes("\xc0\xff\xee", 3, "bytes.bin");
sentry_attachment_set_content_type(bytes, "application/octet-stream");
}

if (sentry_options_get_enable_logs(options)) {
if (has_arg(argc, argv, "capture-log")) {
sentry_log_debug("I'm a log message!");
}
if (has_arg(argc, argv, "logs-timer")) {
for (int i = 0; i < 10; i++) {
sentry_log_info("Informational log nr.%d", i);
}
// sleep >5s to trigger logs timer
sleep_s(6);
// we should see two envelopes make its way to Sentry
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);

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);
}
#endif
}
}

if (!has_arg(argc, argv, "no-setup")) {
sentry_set_transaction("test-transaction");
sentry_set_level(SENTRY_LEVEL_WARNING);
Expand Down Expand Up @@ -633,9 +752,15 @@ main(int argc, char **argv)
sentry_value_t event = sentry_value_new_message_event(
SENTRY_LEVEL_INFO, "my-logger", "Hello World!");
sentry_capture_event(event);
if (has_arg(argc, argv, "logs-scoped-transaction")) {
sentry_log_debug("logging during scoped transaction event");
}
}

sentry_transaction_finish(tx);
if (has_arg(argc, argv, "logs-scoped-transaction")) {
sentry_log_debug("logging after scoped transaction event");
}
}

if (has_arg(argc, argv, "capture-minidump")) {
Expand All @@ -652,4 +777,6 @@ main(int argc, char **argv)
if (has_arg(argc, argv, "crash-after-shutdown")) {
trigger_crash();
}

return EXIT_SUCCESS;
}
78 changes: 78 additions & 0 deletions include/sentry.h
Original file line number Diff line number Diff line change
Expand Up @@ -442,6 +442,7 @@ SENTRY_API char *sentry_value_to_json(sentry_value_t value);
* Sentry levels for events and breadcrumbs.
*/
typedef enum sentry_level_e {
SENTRY_LEVEL_TRACE = -2,
SENTRY_LEVEL_DEBUG = -1,
SENTRY_LEVEL_INFO = 0,
SENTRY_LEVEL_WARNING = 1,
Expand Down Expand Up @@ -1880,6 +1881,83 @@ SENTRY_EXPERIMENTAL_API void sentry_options_set_traces_sampler(
sentry_options_t *opts, sentry_traces_sampler_function callback,
void *user_data);

/**
* Enables or disables the structured logging feature.
* When disabled, all calls to sentry_logger_X() are no-ops.
*/
SENTRY_EXPERIMENTAL_API void sentry_options_set_enable_logs(
sentry_options_t *opts, int enable_logs);
SENTRY_EXPERIMENTAL_API int sentry_options_get_enable_logs(
const sentry_options_t *opts);

/**
* The potential returns of calling any of the sentry_logs_X functions
* - Success means a log was enqueued
* - Discard means the `before_send_log` function discarded the log
* - Failed means the log wasn't enqueued. This happens if the buffers are full
* - Disabled means the option `enable_logs` was false.
*/
typedef enum {
SENTRY_LOG_RETURN_SUCCESS = 0,
SENTRY_LOG_RETURN_DISCARD = 1,
SENTRY_LOG_RETURN_FAILED = 2,
SENTRY_LOG_RETURN_DISABLED = 3
} log_return_value_t;

/**
* Structured logging interface. Minimally blocks the client trying to log,
* but is therefor lossy when enqueueing a log fails
* (e.g. when both buffers are full).
*
* Format string restrictions:
* Only a subset of printf format specifiers are supported for parameter
* extraction. Supported specifiers include:
* - %d, %i - signed integers (treated as long long)
* - %u, %x, %X, %o - unsigned integers (treated as unsigned long long)
* - %f, %F, %e, %E, %g, %G - floating point numbers (treated as double)
* - %c - single character
* - %s - null-terminated string (null pointers are handled as "(null)")
* - %p - pointer value (formatted as hexadecimal string)
*
* Unsupported format specifiers will consume their corresponding argument
* but will be recorded as "(unknown)" in the structured log data.
* Length modifiers (h, l, L, z, j, t) are parsed but ignored.
*
* Because of this, please only use 64-bit types for parameters.
*
* Flags, width, and precision specifiers are parsed but currently ignored for
* parameter extraction purposes.
*/
SENTRY_EXPERIMENTAL_API log_return_value_t sentry_log_trace(
const char *message, ...);
SENTRY_EXPERIMENTAL_API log_return_value_t sentry_log_debug(
const char *message, ...);
SENTRY_EXPERIMENTAL_API log_return_value_t sentry_log_info(
const char *message, ...);
SENTRY_EXPERIMENTAL_API log_return_value_t sentry_log_warn(
const char *message, ...);
SENTRY_EXPERIMENTAL_API log_return_value_t sentry_log_error(
const char *message, ...);
SENTRY_EXPERIMENTAL_API log_return_value_t sentry_log_fatal(
const char *message, ...);

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

/**
* Sets the `before_send_log` callback.
*/
SENTRY_EXPERIMENTAL_API void sentry_options_set_before_send_log(
sentry_options_t *opts, sentry_before_send_log_function_t func, void *data);

#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 @@ -17,6 +17,8 @@ sentry_target_sources_cwd(sentry
sentry_json.h
sentry_logger.c
sentry_logger.h
sentry_logs.c
sentry_logs.h
sentry_options.c
sentry_options.h
sentry_os.c
Expand Down
15 changes: 15 additions & 0 deletions src/sentry_core.c
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#include "sentry_core.h"
#include "sentry_database.h"
#include "sentry_envelope.h"
#include "sentry_logs.h"
#include "sentry_options.h"
#include "sentry_path.h"
#include "sentry_random.h"
Expand Down Expand Up @@ -164,6 +165,7 @@ sentry_init(sentry_options_t *options)
sentry_close();

sentry_logger_t logger = { NULL, NULL, SENTRY_LEVEL_DEBUG };

if (options->debug) {
logger = options->logger;
}
Expand Down Expand Up @@ -286,6 +288,10 @@ sentry_init(sentry_options_t *options)
sentry_start_session();
}

if (options->enable_logs) {
sentry__logs_startup();
}

sentry__mutex_unlock(&g_options_lock);
return 0;

Expand All @@ -312,6 +318,15 @@ sentry_flush(uint64_t timeout)
int
sentry_close(void)
{
// Shutdown logs system before locking options to ensure logs are flushed.
// This prevents a potential deadlock on the options during log envelope
// creation.
SENTRY_WITH_OPTIONS (options) {
if (options->enable_logs) {
sentry__logs_shutdown(options->shutdown_timeout);
}
}

SENTRY__MUTEX_INIT_DYN_ONCE(g_options_lock);
// this function is to be called only once, so we do not allow more than one
// caller
Expand Down
32 changes: 32 additions & 0 deletions src/sentry_envelope.c
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,9 @@ sentry__envelope_add_event(sentry_envelope_t *envelope, sentry_value_t event)
item->event = event;
sentry__jsonwriter_write_value(jw, event);
item->payload = sentry__jsonwriter_into_string(jw, &item->payload_len);
if (!item->payload) {
return NULL;
}

sentry__envelope_item_set_header(
item, "type", sentry_value_new_string("event"));
Expand Down Expand Up @@ -401,6 +404,35 @@ sentry__envelope_add_transaction(
return item;
}

sentry_envelope_item_t *
sentry__envelope_add_logs(sentry_envelope_t *envelope, sentry_value_t logs)
{
sentry_envelope_item_t *item = envelope_add_item(envelope);
if (!item) {
return NULL;
}

sentry_jsonwriter_t *jw = sentry__jsonwriter_new_sb(NULL);
if (!jw) {
return NULL;
}

sentry__jsonwriter_write_value(jw, logs);
item->payload = sentry__jsonwriter_into_string(jw, &item->payload_len);

sentry__envelope_item_set_header(
item, "type", sentry_value_new_string("log"));
sentry__envelope_item_set_header(item, "item_count",
sentry_value_new_int32((int32_t)sentry_value_get_length(
sentry_value_get_by_key(logs, "items"))));
sentry__envelope_item_set_header(item, "content_type",
sentry_value_new_string("application/vnd.sentry.items.log+json"));
sentry_value_t length = sentry_value_new_int32((int32_t)item->payload_len);
sentry__envelope_item_set_header(item, "length", length);

return item;
}

sentry_envelope_item_t *
sentry__envelope_add_user_report(
sentry_envelope_t *envelope, sentry_value_t user_report)
Expand Down
6 changes: 6 additions & 0 deletions src/sentry_envelope.h
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,12 @@ sentry_envelope_item_t *sentry__envelope_add_transaction(
sentry_envelope_item_t *sentry__envelope_add_user_report(
sentry_envelope_t *envelope, sentry_value_t user_report);

/**
* Add a list of logs to this envelope.
*/
sentry_envelope_item_t *sentry__envelope_add_logs(
sentry_envelope_t *envelope, sentry_value_t logs);

/**
* Add a user feedback to this envelope.
*/
Expand Down
Loading
Loading