Skip to content

perf(logs): avoid string allocation when no parameters are passed#4697

Open
Flash0ver wants to merge 11 commits intomainfrom
logs/perf-no-string-alloc-when-no-params
Open

perf(logs): avoid string allocation when no parameters are passed#4697
Flash0ver wants to merge 11 commits intomainfrom
logs/perf-no-string-alloc-when-no-params

Conversation

@Flash0ver
Copy link
Copy Markdown
Member

@Flash0ver Flash0ver commented Nov 6, 2025

Closes #5118.

Avoid a string allocation when there are no parameters that the template string should be formatted with.
Also, ensure that the Parameters of type ImmutableArray`1 are never the default-struct, so that we can avoid ImmutableArray.IsDefault checks afterwards.

@bitsandfoxes, we talked about this optimization quite a while back. It's mainly for this code path:

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Nov 6, 2025

Fails
🚫 Please consider adding a changelog entry for the next release.

Instructions and example for changelog

Please add an entry to CHANGELOG.md to the "Unreleased" section. Make sure the entry includes this PR's number.

Example:

## Unreleased

### Performance

- avoid string allocation when no parameters are passed ([#4697](https://github.com/getsentry/sentry-dotnet/pull/4697))

If none of the above apply, you can opt out of this check by adding #skip-changelog to the PR description or adding a skip-changelog label.

Generated by 🚫 dangerJS against 786ce11

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Feb 13, 2026

Semver Impact of This PR

None (no version bump detected)

📋 Changelog Preview

This is how your changes will appear in the changelog.
Entries from this PR are highlighted with a left border (blockquote style).


  • perf(logs): avoid string allocation when no parameters are passed by Flash0ver in #4697

🤖 This preview updates automatically when you update the PR.

@Flash0ver Flash0ver self-assigned this Mar 17, 2026
@codecov
Copy link
Copy Markdown

codecov bot commented Apr 9, 2026

Codecov Report

❌ Patch coverage is 93.33333% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 74.01%. Comparing base (39ea4d8) to head (ab926d9).

Files with missing lines Patch % Lines
...c/Sentry/Internal/DefaultSentryStructuredLogger.cs 87.50% 0 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #4697      +/-   ##
==========================================
- Coverage   74.12%   74.01%   -0.12%     
==========================================
  Files         499      499              
  Lines       18067    18071       +4     
  Branches     3520     3518       -2     
==========================================
- Hits        13392    13375      -17     
- Misses       3813     3838      +25     
+ Partials      862      858       -4     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Comment on lines +11 to +14
| Method | Mean | Error | StdDev | Gen0 | Gen1 | Allocated |
|--------------------- |---------:|--------:|--------:|-------:|-------:|----------:|
| LogWithoutParameters | 102.3 ns | 1.28 ns | 1.19 ns | 0.0640 | 0.0001 | 536 B |
| LogWithParameters | 248.5 ns | 4.86 ns | 5.40 ns | 0.1087 | - | 912 B |
Copy link
Copy Markdown
Member Author

@Flash0ver Flash0ver Apr 9, 2026

Choose a reason for hiding this comment

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

note: perf comparison

Before this change:

Method Mean Error StdDev Median Gen0 Allocated
LogWithoutParameters 122.2 ns 2.31 ns 5.87 ns 120.5 ns 0.0696 584 B
LogWithParameters 242.6 ns 0.79 ns 0.74 ns 242.5 ns 0.1087 912 B

After this change:

Method Mean Error StdDev Gen0 Gen1 Allocated
LogWithoutParameters 102.3 ns 1.28 ns 1.19 ns 0.0640 0.0001 536 B
LogWithParameters 248.5 ns 4.86 ns 5.40 ns 0.1087 - 912 B

Result:

  • A tiny pessimization in time (IMO neglectable) with params/args
    • one more jump and assignment
  • A notable optimization (a bit of time and 48 bytes in space) without params/args
    • no invocation of System.String.Format

Comment on lines +56 to +58
message = template;
template = null!;
@params = ImmutableArray<KeyValuePair<string, object>>.Empty;
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

question: Breaking Change?

This improvement is - technically speaking - also a behavioral (breaking?) change, when Logging a plain message without positional arguments, observable by user code through the SetBeforeSendLog callback:

options.SetBeforeSendLog(static (SentryLog log) =>
{
    _ = (log.Message, log.Template, log.Parameters);
    return log;
});

No changes to the resulting serialized payload, though - we have a check in place that does not serialize a sentry.message.template when there is no sentry.message.parameter ... but there are user-code observable changes to the SentryLog.Template and SentryLog.Parameters properties.


when Logging without positional Parameters: e.g. SentrySdk.Logger.LogError("Without parameters");

  • Before
    • log.Template == log.Message
    • log.Parameters.IsDefault == true
  • After
    • log.Template == null
    • log.Parameters.IsDefault == false
  • Justification
    • SentryLog.Template was already declared nullable (string?)
      • no contractual change
      • and this now also mirrors what is actually serialized for the payload
    • the ImmutableArray`1 of Parameters may no longer be default
      • when an ImmutableArray`1 is default, invoking any instance member other than IsDefault or IsDefaultOrEmpty will throw a NullReferenceException, because the underlying Array is null
      • this makes user-callbacks "safer"
      • but also the down-stream sentry-unity SDK

Another benefit of ImmutableArray<KeyValuePair<string, object>> Parameters no longer being default when logging without positional arguments is that we can omit IsDefault and IsDefaultOrEmpty checks in the ISentryJsonSerializable.WriteTo serialization logic, which makes it slightly faster, too.


when Logging with positional Parameters: e.g. SentrySdk.Logger.LogError("With parameters {0} {1} {2}", 1, 2, 3);

  • no observable changes

See also https://develop.sentry.dev/sdk/telemetry/logs/#default-attributes.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

and a follow-up question:

Shall we note this in the CHANGELOG?

Comment on lines +28 to +29
// ensure the ImmutableArray`1 is not default, so we can omit IsDefault checks before accessing other members
Parameters = ImmutableArray<KeyValuePair<string, object>>.Empty;
Copy link
Copy Markdown
Member Author

@Flash0ver Flash0ver Apr 9, 2026

Choose a reason for hiding this comment

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

note: this ensures that the ImmutableArray`1 of Parameters is never default

In sentry-dotnet, there is currently no code path that would require this, as we always set the instance to at least "Empty".

But in the down-stream sentry-unity, there actually is a code path where it may be default.

With this assignment, we ensure that it's never default, and both us and users can omit IsDefault/IsDefaultOrEmpty checks.


internal void WriteTo(Utf8JsonWriter writer, IDiagnosticLogger? logger)
{
Debug.Assert(!Parameters.IsDefault);
Copy link
Copy Markdown
Member Author

@Flash0ver Flash0ver Apr 9, 2026

Choose a reason for hiding this comment

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

note: omit default checks

Since we ensured in the internal .ctor, as well as the init-only property, that the ImmutableArray is never default, and with that the underlying array never null, we can omit further IsDefault and IsDefaultOrEmpty checks.

Comment on lines +25 to +26
[Fact]
public void Create_Default_HasMinimalSpecification()
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

note: I wanted to keep the tests for both Logs and Metrics in sync

@Flash0ver Flash0ver requested a review from bitsandfoxes April 9, 2026 11:03
@Flash0ver Flash0ver marked this pull request as ready for review April 9, 2026 12:54
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.

Avoid string allocation when no parameters are passed to methods of Structured Logger

1 participant