Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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 @@ -2,6 +2,10 @@

## Unreleased

**Features**:

- Add custom attributes API for logs. When `logs_with_attributes` is set to `true`, treats the first `varg` passed into `sentry_logs_X(message,...)` as a `sentry_value_t` object of attributes. ([#1435](https://github.com/getsentry/sentry-native/pull/1435))

**Fixes**:

- PS5/Switch compilation regression (`sentry__process_spawn` signature change) ([#1436](https://github.com/getsentry/sentry-native/pull/1436))
Expand Down
49 changes: 40 additions & 9 deletions examples/example.c
Original file line number Diff line number Diff line change
Expand Up @@ -163,11 +163,8 @@ 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_t attribute
= sentry_value_new_attribute(sentry_value_new_string("little"), NULL);
sentry_value_set_by_key(sentry_value_get_by_key(log, "attributes"),
"coffeepot.size", attribute);
return log;
Expand Down Expand Up @@ -503,11 +500,47 @@ main(int argc, char **argv)
options, "./sentry_crash_reporter");
#endif
}
if (has_arg(argc, argv, "log-attributes")) {
sentry_options_set_logs_with_attributes(options, true);
}

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

sentry_get_crashed_last_run();

if (has_arg(argc, argv, "log-attributes")) {
sentry_value_t attributes = sentry_value_new_object();
sentry_value_t attr = sentry_value_new_attribute(
sentry_value_new_string("my_attribute"), NULL);
sentry_value_t attr_2 = sentry_value_new_attribute(
sentry_value_new_int64(INT64_MAX), "fermions");
sentry_value_t attr_3 = sentry_value_new_attribute(
sentry_value_new_int64(INT64_MIN), "bosons");
sentry_value_set_by_key(attributes, "my.custom.attribute", attr);
sentry_value_set_by_key(attributes, "number.first", attr_2);
sentry_value_set_by_key(attributes, "number.second", attr_3);
// testing multiple attributes
sentry_log_debug(
"logging with %d custom attributes", attributes, (int64_t)3);
// testing no attributes
sentry_log_debug("logging with %s custom attributes",
sentry_value_new_object(), "no");
// testing overwriting default attributes
sentry_value_t param_attributes = sentry_value_new_object();
sentry_value_t param_attr = sentry_value_new_attribute(
sentry_value_new_string("parameter"), NULL);
sentry_value_t param_attr_2 = sentry_value_new_attribute(
sentry_value_new_string("custom-sdk-name"), NULL);
sentry_value_set_by_key(
param_attributes, "sentry.message.parameter.0", param_attr);
sentry_value_set_by_key(
param_attributes, "sentry.sdk.name", param_attr_2);
sentry_log_fatal(
"logging with a custom parameter attribute", param_attributes);
}

if (has_arg(argc, argv, "attachment")) {
sentry_attachment_t *bytes
= sentry_attach_bytes("\xc0\xff\xee", 3, "bytes.bin");
Expand Down Expand Up @@ -571,10 +604,8 @@ main(int argc, char **argv)
context, "name", sentry_value_new_string("testing-runtime"));
sentry_set_context("runtime", context);

sentry_value_t user = sentry_value_new_object();
sentry_value_set_by_key(user, "id", sentry_value_new_string("42"));
sentry_value_set_by_key(
user, "username", sentry_value_new_string("some_name"));
sentry_value_t user
= sentry_value_new_user("42", "some_name", NULL, NULL);
sentry_set_user(user);

sentry_value_t default_crumb
Expand Down
39 changes: 38 additions & 1 deletion include/sentry.h
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,20 @@ 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);

/**
* Creates a new attribute object.
* value` is required, `unit` is optional.
*
*'value' must be a bool, int, double or string (not null, list, object)
*
* Moves ownership of `value` into the object. The caller does not
* have to call `sentry_value_decref` on it.
*/
SENTRY_API sentry_value_t sentry_value_new_attribute(
sentry_value_t value, const char *unit);
SENTRY_API sentry_value_t sentry_value_new_attribute_n(
sentry_value_t value, const char *unit, size_t unit_len);

/**
* Returns the type of the value passed.
*/
Expand Down Expand Up @@ -1992,13 +2006,27 @@ SENTRY_EXPERIMENTAL_API int sentry_options_get_propagate_traceparent(

/**
* Enables or disables the structured logging feature.
* When disabled, all calls to sentry_logger_X() are no-ops.
* When disabled, all calls to `sentry_log_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);

/**
* Enables or disables custom attributes parsing for structured logging.
*
* When enabled, all `sentry_log_X()` functions expect a `sentry_value_t` object
* as the first variadic argument for custom log attributes. Remaining
* arguments are used for format string substitution.
*
* Disabled by default.
*/
SENTRY_EXPERIMENTAL_API void sentry_options_set_logs_with_attributes(
sentry_options_t *opts, int logs_with_attributes);
SENTRY_EXPERIMENTAL_API int sentry_options_get_logs_with_attributes(
const sentry_options_t *opts);

/**
* The potential returns of calling any of the sentry_log_X functions
* - Success means a log was enqueued
Expand Down Expand Up @@ -2036,6 +2064,15 @@ typedef enum {
*
* Flags, width, and precision specifiers are parsed but currently ignored for
* parameter extraction purposes.
*
* When the option `logs_with_attributes` is enabled, the first varg is parsed
* as a `sentry_value_t` object containing the initial attributes for the log.
* You can pass `sentry_value_new_null()` to logs which don't need attributes.
*
* Ownership of the attributes is transferred to the log function.
*
* To re-use the same attributes, call `sentry_value_incref` on it
* before passing the attributes to the log function.
*/
SENTRY_EXPERIMENTAL_API log_return_value_t sentry_log_trace(
const char *message, ...);
Expand Down
88 changes: 58 additions & 30 deletions src/sentry_logs.c
Original file line number Diff line number Diff line change
Expand Up @@ -546,15 +546,15 @@ static
* This function assumes that `value` is owned, so we have to make sure that the
* `value` was created or cloned by the caller or even better inc_refed.
*
* Replaces attributes[name] if it already exists.
* No-op if 'name' already exists in the attributes.
*/
static void
add_attribute(sentry_value_t attributes, sentry_value_t value, const char *type,
const char *name)
{
if (!sentry_value_is_null(sentry_value_get_by_key(attributes, name))) {
// already exists, so we remove and create a new one
sentry_value_remove_by_key(attributes, name);
sentry_value_decref(value);
return;
}
sentry_value_t param_obj = sentry_value_new_object();
sentry_value_set_by_key(param_obj, "value", value);
Expand Down Expand Up @@ -648,10 +648,6 @@ add_scope_and_options_data(sentry_value_t log, sentry_value_t attributes)
}
}

// fallback in case options doesn't set it
add_attribute(attributes, sentry_value_new_string(SENTRY_SDK_NAME),
"string", "sentry.sdk.name");

SENTRY_WITH_OPTIONS (options) {
if (options->environment) {
add_attribute(attributes,
Expand All @@ -677,25 +673,65 @@ construct_log(sentry_level_t level, const char *message, va_list args)
sentry_value_t log = sentry_value_new_object();
sentry_value_t attributes = sentry_value_new_object();

va_list args_copy_1, args_copy_2, args_copy_3;
va_copy(args_copy_1, args);
va_copy(args_copy_2, args);
va_copy(args_copy_3, args);
int len = vsnprintf(NULL, 0, message, args_copy_1) + 1;
va_end(args_copy_1);
size_t size = (size_t)len;
char *fmt_message = sentry_malloc(size);
if (!fmt_message) {
SENTRY_WITH_OPTIONS (options) {
// Extract custom attributes if the option is enabled
if (sentry_options_get_logs_with_attributes(options)) {
va_list args_copy;
va_copy(args_copy, args);
Comment on lines 673 to +680
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The removal of the fallback sentry.sdk.name attribute (previously added unconditionally) is now handled by the add_attribute() no-op behavior when custom attributes override it. However, verify that in the non-custom-attributes path, sentry.sdk.name is still being added consistently by add_scope_and_options_data(). The logic depends on add_attribute() being called with sentry.sdk.name from somewhere else in the flow.
Severity: MEDIUM

🤖 Prompt for AI Agent

Review the code at the location below. A potential bug has been identified by an AI
agent.
Verify if this is a real issue. If it is, propose a fix; if not, explain why it's not
valid.

Location: src/sentry_logs.c#L647-L680

Potential issue: The removal of the fallback `sentry.sdk.name` attribute (previously
added unconditionally) is now handled by the `add_attribute()` no-op behavior when
custom attributes override it. However, verify that in the non-custom-attributes path,
`sentry.sdk.name` is still being added consistently by `add_scope_and_options_data()`.
The logic depends on `add_attribute()` being called with `sentry.sdk.name` from
somewhere else in the flow.

Did we get this right? 👍 / 👎 to inform future reviews.

sentry_value_t custom_attributes
= va_arg(args_copy, sentry_value_t);
va_end(args_copy);
if (sentry_value_get_type(custom_attributes)
== SENTRY_VALUE_TYPE_OBJECT) {
sentry_value_decref(attributes);
attributes = sentry__value_clone(custom_attributes);
} else {
SENTRY_DEBUG("Discarded custom attributes on log: non-object "
"sentry_value_t passed in");
}
sentry_value_decref(custom_attributes);
}

// Format the message with remaining args (or all args if not using
// custom attributes)
va_list args_copy_1, args_copy_2, args_copy_3;
va_copy(args_copy_1, args);
va_copy(args_copy_2, args);
va_copy(args_copy_3, args);

// Skip the first argument (attributes) if using custom attributes
if (sentry_options_get_logs_with_attributes(options)) {
va_arg(args_copy_1, sentry_value_t);
va_arg(args_copy_2, sentry_value_t);
va_arg(args_copy_3, sentry_value_t);
}

int len = vsnprintf(NULL, 0, message, args_copy_1) + 1;
va_end(args_copy_1);
size_t size = (size_t)len;
char *fmt_message = sentry_malloc(size);
if (!fmt_message) {
va_end(args_copy_2);
va_end(args_copy_3);
return sentry_value_new_null();
}

vsnprintf(fmt_message, size, message, args_copy_2);
va_end(args_copy_2);

sentry_value_set_by_key(
log, "body", sentry_value_new_string(fmt_message));
sentry_free(fmt_message);

// Parse variadic arguments and add them to attributes
if (populate_message_parameters(attributes, message, args_copy_3)) {
// only add message template if we have parameters
add_attribute(attributes, sentry_value_new_string(message),
"string", "sentry.message.template");
}
va_end(args_copy_3);
return sentry_value_new_null();
}

vsnprintf(fmt_message, size, message, args_copy_2);
va_end(args_copy_2);

sentry_value_set_by_key(log, "body", sentry_value_new_string(fmt_message));
sentry_free(fmt_message);
sentry_value_set_by_key(
log, "level", sentry_value_new_string(level_as_string(level)));

Expand All @@ -708,14 +744,6 @@ construct_log(sentry_level_t level, const char *message, va_list args)
// to the log
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The entire message formatting and parameter parsing logic is now wrapped inside SENTRY_WITH_OPTIONS. While this ensures thread-safe access to options, it's a behavioral change from the original implementation. Verify that the lock scope doesn't negatively impact performance for high-frequency logging scenarios, and consider documenting why this lock scope is necessary.
Severity: MEDIUM

🤖 Prompt for AI Agent

Review the code at the location below. A potential bug has been identified by an AI
agent.
Verify if this is a real issue. If it is, propose a fix; if not, explain why it's not
valid.

Location: src/sentry_logs.c#L679-L744

Potential issue: The entire message formatting and parameter parsing logic is now
wrapped inside `SENTRY_WITH_OPTIONS`. While this ensures thread-safe access to options,
it's a behavioral change from the original implementation. Verify that the lock scope
doesn't negatively impact performance for high-frequency logging scenarios, and consider
documenting why this lock scope is necessary.

Did we get this right? 👍 / 👎 to inform future reviews.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is no exclusive lock over the options, since we only increment the refcount (and the options themselves are read-only). So there is no real performance impact here.

add_scope_and_options_data(log, attributes);

// Parse variadic arguments and add them to attributes
if (populate_message_parameters(attributes, message, args_copy_3)) {
// only add message template if we have parameters
add_attribute(attributes, sentry_value_new_string(message), "string",
"sentry.message.template");
}
va_end(args_copy_3);

sentry_value_set_by_key(log, "attributes", attributes);

return log;
Expand Down
13 changes: 13 additions & 0 deletions src/sentry_options.c
Original file line number Diff line number Diff line change
Expand Up @@ -744,6 +744,19 @@ sentry_options_get_enable_logs(const sentry_options_t *opts)
return opts->enable_logs;
}

void
sentry_options_set_logs_with_attributes(
sentry_options_t *opts, int logs_with_attributes)
{
opts->logs_with_attributes = !!logs_with_attributes;
}

int
sentry_options_get_logs_with_attributes(const sentry_options_t *opts)
{
return opts->logs_with_attributes;
}

#ifdef SENTRY_PLATFORM_LINUX

sentry_handler_strategy_t
Expand Down
3 changes: 3 additions & 0 deletions src/sentry_options.h
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,9 @@ struct sentry_options_s {
void *traces_sampler_data;
size_t max_spans;
bool enable_logs;
// takes the first varg as a `sentry_value_t` object containing attributes
// if no custom attributes are to be passed, use `sentry_value_new_object()`
bool logs_with_attributes;

/* everything from here on down are options which are stored here but
not exposed through the options API */
Expand Down
45 changes: 45 additions & 0 deletions src/sentry_value.c
Original file line number Diff line number Diff line change
Expand Up @@ -509,6 +509,51 @@ sentry_value_new_user(const char *id, const char *username, const char *email,
ip_address, ip_address ? strlen(ip_address) : 0);
}

sentry_value_t
sentry_value_new_attribute_n(
sentry_value_t value, const char *unit, size_t unit_len)
{
char *type;
switch (sentry_value_get_type(value)) {
case SENTRY_VALUE_TYPE_BOOL:
type = "boolean";
break;
case SENTRY_VALUE_TYPE_INT32:
case SENTRY_VALUE_TYPE_INT64:
case SENTRY_VALUE_TYPE_UINT64:
type = "integer";
break;
case SENTRY_VALUE_TYPE_DOUBLE:
type = "double";
break;
case SENTRY_VALUE_TYPE_STRING:
type = "string";
break;
case SENTRY_VALUE_TYPE_NULL:
case SENTRY_VALUE_TYPE_LIST:
case SENTRY_VALUE_TYPE_OBJECT:
default:
sentry_value_decref(value);
return sentry_value_new_null();
}
sentry_value_t attribute = sentry_value_new_object();

sentry_value_set_by_key(
attribute, "type", sentry_value_new_string_n(type, strlen(type)));
sentry_value_set_by_key(attribute, "value", value);
if (unit && unit_len) {
sentry_value_set_by_key(
attribute, "unit", sentry_value_new_string_n(unit, unit_len));
}
return attribute;
}

sentry_value_t
sentry_value_new_attribute(sentry_value_t value, const char *unit)
{
return sentry_value_new_attribute_n(value, unit, unit ? strlen(unit) : 0);
}

sentry_value_type_t
sentry_value_get_type(sentry_value_t value)
{
Expand Down
Loading
Loading