Skip to content

Geneva exporter: Support resource attributes in log exporter#3646

Merged
rajkumar-rangaraj merged 20 commits intoopen-telemetry:mainfrom
mattsains:logs-resources
Jan 13, 2026
Merged

Geneva exporter: Support resource attributes in log exporter#3646
rajkumar-rangaraj merged 20 commits intoopen-telemetry:mainfrom
mattsains:logs-resources

Conversation

@mattsains
Copy link
Contributor

Related to #3214

Changes

  • Allow specifying resource attributes to export in Geneva Log exporter.
  • Changed behaviour of trace exporter to make resource attributes and prepopulated fields mutually exclusive

Merge requirement checklist

  • CONTRIBUTING guidelines followed (license requirements, nullable enabled, static analysis, etc.)
  • Unit tests added/updated
  • Appropriate CHANGELOG.md files updated for non-trivial changes
  • [] Changes in public API reviewed (if applicable)

@linux-foundation-easycla
Copy link

linux-foundation-easycla bot commented Dec 30, 2025

CLA Signed

The committers listed above are authorized under a signed CLA.

@github-actions github-actions bot added comp:exporter.geneva Things related to OpenTelemetry.Exporter.Geneva perf Performance related labels Dec 30, 2025
@codecov
Copy link

codecov bot commented Dec 30, 2025

Codecov Report

❌ Patch coverage is 92.70833% with 7 lines in your changes missing coverage. Please review.
✅ Project coverage is 71.72%. Comparing base (8ca7383) to head (1469019).
⚠️ Report is 23 commits behind head on main.
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
...rter.Geneva/Internal/MsgPack/MsgPackLogExporter.cs 88.88% 7 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main    #3646      +/-   ##
==========================================
+ Coverage   71.52%   71.72%   +0.19%     
==========================================
  Files         455      445      -10     
  Lines       17617    17624       +7     
==========================================
+ Hits        12601    12640      +39     
+ Misses       5016     4984      -32     
Flag Coverage Δ
unittests-Exporter.Geneva 54.15% <92.70%> (+0.37%) ⬆️
unittests-Instrumentation.Cassandra ?

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
...Telemetry.Exporter.Geneva/GenevaExporterOptions.cs 89.18% <ø> (ø)
...OpenTelemetry.Exporter.Geneva/GenevaLogExporter.cs 90.38% <100.00%> (+2.88%) ⬆️
...enTelemetry.Exporter.Geneva/GenevaTraceExporter.cs 86.04% <100.00%> (+3.99%) ⬆️
...er.Geneva/Internal/MsgPack/MsgPackTraceExporter.cs 94.21% <100.00%> (+1.40%) ⬆️
...rter.Geneva/Internal/MsgPack/MsgPackLogExporter.cs 92.52% <88.88%> (+0.15%) ⬆️

... and 13 files with indirect coverage changes

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link
Contributor Author

@mattsains mattsains left a comment

Choose a reason for hiding this comment

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

A while ago we decided that the resource attributes service.name and service.instanceId would be mapped to the message pack data even if they are not present in ResourceFieldNames. I was just thinking and realised that if someone had set cloud.role or cloud.roleInstance in PrepopulatedFields, then after my change, they will not have their logs exported, because the prepopulated field will conflict with the "built-in" resource attribute mapping for service.name and service.instanceId (which gets mapped to cloud.role and cloud.roleInstance even if you didn't specify it in ResourceFieldNames)

what do you think I should do? I can check to see if it's in resource attributes before I accept it as a prepopulated field, and then I guess just drop the prepopulated field?

@mattsains mattsains marked this pull request as ready for review December 30, 2025 22:42
@mattsains mattsains requested a review from a team as a code owner December 30, 2025 22:42
@mattsains
Copy link
Contributor Author

A while ago we decided that the resource attributes service.name and service.instanceId would be mapped to the message pack data even if they are not present in ResourceFieldNames. I was just thinking and realised that if someone had set cloud.role or cloud.roleInstance in PrepopulatedFields, then after my change, they will not have their logs exported, because the prepopulated field will conflict with the "built-in" resource attribute mapping for service.name and service.instanceId (which gets mapped to cloud.role and cloud.roleInstance even if you didn't specify it in ResourceFieldNames)

what do you think I should do? I can check to see if it's in resource attributes before I accept it as a prepopulated field, and then I guess just drop the prepopulated field?

I have updated the code and the tests to replace a prepopulated field with the equivalent auto-mapped resource attribute (ie., service.name, service.instanceId). I also added a test for this behaviour

if (this.prepopulatedFields != null)
{
for (var i = 0; i < this.prepopulatedFieldKeys.Count; i++)
foreach (var field in this.prepopulatedFields)
Copy link
Member

Choose a reason for hiding this comment

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

Existing implementation uses for loop to avoid the allocation from foreach.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

as discussed, this isn't a concern because this foreach is unlikely to have a heap allocation

Copy link
Contributor

Choose a reason for hiding this comment

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

If I recall correctly, foreach inherently cause heap allocation.

It's a major goal to keep heap allocation as low as possible for Geneva exporter. Can you run a benchmark test in Benchmark folder to confirm there is no heap allocation introduced by foreach? (PR #1504 also included a mini benchmark code)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I can't find any benchmarking code in that PR but if you can point me to a specific benchmark test I'd be happy to run it and post the results here

Copy link
Contributor

Choose a reason for hiding this comment

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

There are two places you can reference:

  1. Navigate to `./test/OpenTelemetry.Exporter.Geneva.Benchmarks` directory and run
  2. Ctrl+F "Log Benchmarks Code" on page [Exporter.Geneva] Perf improvement on .NET 8 #1504 and click on it

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Oops, I don't know how I missed that collapseable section in the PR.

Here's the results of the benchmark on my branch:

Method IncludeFormattedMessage Mean Error StdDev Gen0 Allocated
LoggerWithMessageTemplate False 944.3 ns 8.32 ns 7.78 ns - 104 B
LoggerWithDirectLoggerAPI False 1,136.2 ns 10.99 ns 8.58 ns 0.0057 320 B
LoggerWithSourceGenerator False 933.9 ns 11.04 ns 10.32 ns - 64 B
SerializeLogRecord False 316.7 ns 1.98 ns 1.76 ns - -
Export False 638.5 ns 9.73 ns 7.59 ns - -
LoggerWithMessageTemplate True 961.9 ns 8.06 ns 7.54 ns 0.0010 104 B
LoggerWithDirectLoggerAPI True 1,117.1 ns 6.50 ns 5.76 ns 0.0057 320 B
LoggerWithSourceGenerator True 929.5 ns 8.82 ns 8.25 ns - 64 B
SerializeLogRecord True 315.9 ns 0.87 ns 0.73 ns - -
Export True 641.1 ns 7.51 ns 5.86 ns - -

Copy link
Contributor

Choose a reason for hiding this comment

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

I also ran the tests on main branch. This confirmed that the PR didn't increase memory allocation. Interestingly, the memory allocation for .NET 10 is higher than .NET 8 (both on main branch):

.NET 8

Method IncludeFormattedMessage Mean Error StdDev Gen0 Allocated
LoggerWithMessageTemplate False 1,142.1 ns 22.74 ns 42.16 ns 0.0038 104 B
LoggerWithDirectLoggerAPI False 1,331.4 ns 26.41 ns 36.15 ns 0.0114 320 B
LoggerWithSourceGenerator False 1,122.7 ns 17.91 ns 28.41 ns 0.0019 64 B
SerializeLogRecord False 434.3 ns 8.42 ns 8.27 ns - -
Export False 773.8 ns 15.53 ns 35.04 ns - -
LoggerWithMessageTemplate True 1,132.5 ns 22.65 ns 44.71 ns 0.0038 104 B
LoggerWithDirectLoggerAPI True 1,285.8 ns 24.61 ns 23.02 ns 0.0114 320 B
LoggerWithSourceGenerator True 1,129.4 ns 22.21 ns 24.69 ns 0.0019 64 B
SerializeLogRecord True 434.2 ns 5.31 ns 4.71 ns - -
Export True 769.2 ns 15.25 ns 31.84 ns - -

.NET 10

Method IncludeFormattedMessage Mean Error StdDev Median Gen0 Allocated
LoggerWithMessageTemplate False 1,042.3 ns 20.88 ns 60.58 ns 1,045.5 ns 0.0038 112 B
LoggerWithDirectLoggerAPI False 1,242.6 ns 24.58 ns 47.36 ns 1,222.2 ns 0.0114 320 B
LoggerWithSourceGenerator False 1,063.2 ns 21.24 ns 44.33 ns 1,051.1 ns 0.0019 64 B
SerializeLogRecord False 383.3 ns 7.46 ns 9.70 ns 379.8 ns - -
Export False 745.4 ns 14.88 ns 32.35 ns 741.4 ns - -
LoggerWithMessageTemplate True 1,102.1 ns 31.75 ns 92.60 ns 1,082.8 ns 0.0038 112 B
LoggerWithDirectLoggerAPI True 1,219.1 ns 24.17 ns 33.08 ns 1,213.6 ns 0.0114 320 B
LoggerWithSourceGenerator True 1,069.4 ns 23.99 ns 69.61 ns 1,065.7 ns 0.0019 64 B
SerializeLogRecord True 369.3 ns 4.97 ns 4.41 ns 368.6 ns - -
Export True 763.3 ns 15.09 ns 41.57 ns 755.1 ns - -

Copy link
Member

Choose a reason for hiding this comment

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

Interesting, could you look into where the delta is coming from in .NET 10 comparing to 8?

Copy link
Contributor

Choose a reason for hiding this comment

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

Sure.

Copy link
Contributor

Choose a reason for hiding this comment

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

The 8 bytes come from the size increase of Microsoft.Extensions.Logging.FormattedLogValues, which is created via new in LoggerExtensions.LogInformation.

I checked the source code: in .NET 10, an additional field _cachedToString of type string? was introduced in FormattedLogValues: .NET 10 code vs .NET 8 code. This field is a reference, so it's 8 bytes on 64-bit CLR.

@mattsains
Copy link
Contributor Author

We talked more about the prepopulated/resource attribute thing, and decided that the mapping can only happen when prepopulated fields have not been specified. Otherwise, the default resource will silently overwrite prepopulated values.

if (this.prepopulatedFields != null)
{
for (var i = 0; i < this.prepopulatedFieldKeys.Count; i++)
foreach (var field in this.prepopulatedFields)
Copy link
Contributor

Choose a reason for hiding this comment

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

If I recall correctly, foreach inherently cause heap allocation.

It's a major goal to keep heap allocation as low as possible for Geneva exporter. Can you run a benchmark test in Benchmark folder to confirm there is no heap allocation introduced by foreach? (PR #1504 also included a mini benchmark code)

if (useMsgPackExporter)
{
var msgPackLogExporter = new MsgPackLogExporter(options);
var msgPackLogExporter = new MsgPackLogExporter(options, () =>
Copy link
Member

Choose a reason for hiding this comment

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

Should this have a null check? If ParentProvider hasn't been set when export is called (possible during SDK initialization edge cases), this will throw NRE. Consider:

() => this.ParentProvider?.GetResource() ?? Resource.Empty

Choose a reason for hiding this comment

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

I'll change it but I notice that if ParentProvider can really be null, then the existing metric code is also broken: https://github.com/open-telemetry/opentelemetry-dotnet-contrib/blob/main/src/OpenTelemetry.Exporter.Geneva/Metrics/GenevaMetricExporter.cs#L112

Copy link
Member

Choose a reason for hiding this comment

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

What does this mean? Is it a bug or not?

Copy link
Member

Choose a reason for hiding this comment

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

This is not a bug; the wording may have caused the confusion.
In the existing implementation, this.ParentProvider.GetResource() itself already returns Resource.Empty when no provider is set, so it is safe. We don’t need to add an explicit null check here or manually set Resource.Empty again.

Copy link
Member

Choose a reason for hiding this comment

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

Thanks @rajkumar-rangaraj! Then this sounds wrong and should be reverted back.

I'll change it

Copy link
Member

Choose a reason for hiding this comment

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

Yes, this needs a fix. I will send a follow-up PR.

@rajkumar-rangaraj rajkumar-rangaraj added this pull request to the merge queue Jan 13, 2026
Merged via the queue into open-telemetry:main with commit 197c092 Jan 13, 2026
78 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp:exporter.geneva Things related to OpenTelemetry.Exporter.Geneva perf Performance related

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants