Skip to content

Conversation

@JoshuaMoelans
Copy link
Member

@JoshuaMoelans JoshuaMoelans commented Oct 30, 2025

fixes #1405

With the logs_with_attributes option set to true, we can still parse the message string as a format string, but we take the first varg passed into sentry_log_X as an attributes object.
If the passed-in value is not a sentry_value_t object, we will just ignore it (as long as it is still a sentry_value_t type, otherwise we hit UNREACHABLE("invalid value type"); when trying to parse it)

sentry_options_set_logs_with_attributes(options, true);
...
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);

sentry_log_debug("logging with %d custom attributes", attributes, 3);

sentry_log_debug("logging with %s custom attributes", sentry_value_new_object(), "no");

Which shows up in the UI as such:

Screenshot 2025-10-31 at 13 35 00

This is also useful for downstream SDKs which might not need string interpolation, but still want to pass down message.parameter.X 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_set_by_key(param_attributes,"message.parameter.0", param_attr);
sentry_log_fatal("logging with a custom parameter attributes", param_attributes);

‼️ custom attributes take precedence over the default attributes, except for sentry.message.parameter.X values; if you pass in a string with format specifiers, these are used for the message parameter values.

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 %s attributes", param_attributes, "and format-string");

--> the custom-sdk-name shows up, but the sentry.message.parameter.0 value is "and format-string", not "parameter".
Screenshot 2025-11-03 at 15 11 55

@github-actions
Copy link

github-actions bot commented Oct 30, 2025

Messages
📖 Do not forget to update Sentry-docs with your feature once the pull request gets approved.

Generated by 🚫 dangerJS against 660cc77

@limbonaut
Copy link
Collaborator

I gave it a spin in the Godot SDK, and it works fine!
2025-11-03_14-08-21
image

Comment on lines 537 to 544
sentry_value_set_by_key(
param_attributes, "message.parameter.0", param_attr);
sentry_value_set_by_key(
param_attributes, "sentry.sdk.name", param_attr_2);
// TODO discuss; param_attributes[message.parameter.0] gets overwritten
// by 'and format string'. Is this expected?
sentry_log_fatal("logging with a custom parameter %s attributes",
param_attributes, "and format-string");
Copy link
Member Author

Choose a reason for hiding this comment

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

IMO we can overwrite all default attributes except for string format values that are passed in; otherwise there would be a mismatch between the string template + sentry.message.parameter.X and the log body value

Copy link
Collaborator

Choose a reason for hiding this comment

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

I think using both the attributes to pass message.parameter.0 and the sentry-native interpolation API is already a misuse. I don't think we need to be protective about it. Let it be an undefined behavior.

Comment on lines 683 to 688
if (sentry_value_get_type(custom_attributes)
== SENTRY_VALUE_TYPE_OBJECT) {
// TODO do we want to inspect that the object is attribute-like?
sentry_value_decref(attributes);
attributes = sentry__value_clone(custom_attributes);
} else {
Copy link
Member Author

Choose a reason for hiding this comment

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

I'm not sure how far we need to go to protect from user error. An invalid attribute (no "type" and "value" keys) seems to just get ignored in processing (the log still makes it to the UI)

@JoshuaMoelans JoshuaMoelans marked this pull request as ready for review November 4, 2025 10:20
@JoshuaMoelans
Copy link
Member Author

@sentry review

Comment on lines 673 to +680
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);
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.

@@ -708,14 +744,6 @@
// 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.

Copy link
Collaborator

@limbonaut limbonaut left a comment

Choose a reason for hiding this comment

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

LGTM 🙃

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Structured Logs: follow up - custom attributes

3 participants