Repository: open-telemetry/opentelemetry-cpp
Pull request #3417: [EXPORTER] ostream log exporter, fix memory ownership issues
Author: login: marcalff
Created: 2025-05-15T19:31:07Z | Merged: 2025-05-21T11:29:02Z

Fixes #3135
Fixes #2651

## Changes

Fixed the following memory ownership issues in the ostream log exporter:

* In class `ReadWriteLogRecord`, member `body_` now owns a copy of the log body
* In class `ReadWriteLogRecord`, member `attributes_map_` now owns a copy of log attributes

This prevents the use of stale pointers, that could lead to crashes previously.

For significant contributions please make sure you have completed the following items:

* [X] `CHANGELOG.md` updated for non-trivial changes
* [ ] Unit tests have been added
* [ ] Changes in public API reviewed

Linked issue #3135: heap-use-after-free with BatchLogRecordProcessor
**Describe your environment**
OpenTelemetry v1.17.0

**Steps to reproduce**
Sample code: https://github.com/sjinks/otel-heap-use-after-free
Sample run: https://github.com/sjinks/otel-heap-use-after-free/actions/runs/11758831588/job/32757617053

**What is the expected behavior?**
There should be no errors.

**What is the actual behavior?**
The application crashed because of the heap-use-after-free-error

```
=5853==ERROR: AddressSanitizer: heap-use-after-free on address 0x6030000005c8 at pc 0x55707b183c86 bp 0x7f82e12fe770 sp 0x7f82e12fdf38
READ of size 6 at 0x6030000005c8 thread T1
    #0 0x55707b183c85 in __interceptor_strlen (/home/runner/work/otel-heap-use-after-free/otel-heap-use-after-free/build/bug-repro+0x46c85) (BuildId: 3dbd37ecfe9134972fe6a448909a1b651fdd1e62)
    #1 0x7f82e3b3ceac in std::basic_ostream<char, std::char_traits<char> >& std::operator<<<std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*) (/lib/x86_64-linux-gnu/libstdc++.so.6+0x13ceac) (BuildId: e37fe1a879783838de78cbc8c80621fa685d58a2)
    #2 0x55707b247a33 in void opentelemetry::v1::exporter::ostream_common::print_value<char const*>(char const* const&, std::ostream&) /home/runner/work/otel-heap-use-after-free/otel-heap-use-after-free/vcpkg/buildtrees/opentelemetry-cpp/src/v1.17.0-b9ea5cab17.clean/exporters/ostream/include/opentelemetry/exporters/ostream/common_utils.h:25:8
    #3 0x55707b2435cb in auto opentelemetry::v1::exporter::ostream_common::print_value(std::variant<bool, int, long, unsigned int, double, char const*, opentelemetry::v1::nostd::string_view, opentelemetry::v1::nostd::span<bool const, 18446744073709551615ul>, opentelemetry::v1::nostd::span<int const, 18446744073709551615ul>, opentelemetry::v1::nostd::span<long const, 18446744073709551615ul>, opentelemetry::v1::nostd::span<unsigned int const, 18446744073709551615ul>, opentelemetry::v1::nostd::span<double const, 18446744073709551615ul>, opentelemetry::v1::nostd::span<opentelemetry::v1::nostd::string_view const, 18446744073709551615ul>, unsigned long, opentelemetry::v1::nostd::span<unsigned long const, 18446744073709551615ul>, opentelemetry::v1::nostd::span<unsigned char const, 18446744073709551615ul> > const&, std::ostream&)::'lambda'(auto&&)::operator()<char const* const&>(auto&&) const /home/runner/work/otel-heap-use-after-free/otel-heap-use-after-free/vcpkg/buildtrees/opentelemetry-cpp/src/v1.17.0-b9ea5cab17.
    #4 0x55707b24ab4d in auto std::__invoke_impl<void, opentelemetry::v1::exporter::ostream_common::print_value(std::variant<bool, int, long, unsigned int, double, char const*, opentelemetry::v1::nostd::string_view, opentelemetry::v1::nostd::span<bool const, 18446744073709551615ul>, opentelemetry::v1::nostd::span<int const, 18446744073709551615ul>, opentelemetry::v1::nostd::span<long const, 18446744073709551615ul>, opentelemetry::v1::nostd::span<unsigned int const, 18446744073709551615ul>, opentelemetry::v1::nostd::span<double const, 18446744073709551615ul>, opentelemetry::v1::nostd::span<opentelemetry::v1::nostd::string_view const, 18446744073709551615ul>, unsigned long, opentelemetry::v1::nostd::span<unsigned long const, 18446744073709551615ul>, opentelemetry::v1::nostd::span<unsigned char const, 18446744073709551615ul> > const&, std::ostream&)::'lambda'(auto&&), char const* const&>(std::__invoke_other, opentelemetry::v1::exporter::ostream_common::print_value(std::variant<bool, int, long, unsigned int, doub
    #5 0x55707b248277 in std::__invoke_result<auto, char const* const&>::type std::__invoke<opentelemetry::v1::exporter::ostream_common::print_value(std::variant<bool, int, long, unsigned int, double, char const*, opentelemetry::v1::nostd::string_view, opentelemetry::v1::nostd::span<bool const, 18446744073709551615ul>, opentelemetry::v1::nostd::span<int const, 18446744073709551615ul>, opentelemetry::v1::nostd::span<long const, 18446744073709551615ul>, opentelemetry::v1::nostd::span<unsigned int const, 18446744073709551615ul>, opentelemetry::v1::nostd::span<double const, 18446744073709551615ul>, opentelemetry::v1::nostd::span<opentelemetry::v1::nostd::string_view const, 18446744073709551615ul>, unsigned long, opentelemetry::v1::nostd::span<unsigned long const, 18446744073709551615ul>, opentelemetry::v1::nostd::span<unsigned char const, 18446744073709551615ul> > const&, std::ostream&)::'lambda'(auto&&), char const* const&>(auto&&, char const* const&) /usr/include/c++/11/bits/invoke.h:96:40
    #6 0x55707b243a33 in std::__detail::__variant::__gen_vtable_impl<std::__detail::__variant::_Multi_array<std::__detail::__variant::__deduce_visit_result<void> (*)(opentelemetry::v1::exporter::ostream_common::print_value(std::variant<bool, int, long, unsigned int, double, char const*, opentelemetry::v1::nostd::string_view, opentelemetry::v1::nostd::span<bool const, 18446744073709551615ul>, opentelemetry::v1::nostd::span<int const, 18446744073709551615ul>, opentelemetry::v1::nostd::span<long const, 18446744073709551615ul>, opentelemetry::v1::nostd::span<unsigned int const, 18446744073709551615ul>, opentelemetry::v1::nostd::span<double const, 18446744073709551615ul>, opentelemetry::v1::nostd::span<opentelemetry::v1::nostd::string_view const, 18446744073709551615ul>, unsigned long, opentelemetry::v1::nostd::span<unsigned long const, 18446744073709551615ul>, opentelemetry::v1::nostd::span<unsigned char const, 18446744073709551615ul> > const&, std::ostream&)::'lambda'(auto&&)&&, std::variant<bool, int, long, uns
    #7 0x55707b243f3a in decltype(auto) std::__do_visit<std::__detail::__variant::__deduce_visit_result<void>, opentelemetry::v1::exporter::ostream_common::print_value(std::variant<bool, int, long, unsigned int, double, char const*, opentelemetry::v1::nostd::string_view, opentelemetry::v1::nostd::span<bool const, 18446744073709551615ul>, opentelemetry::v1::nostd::span<int const, 18446744073709551615ul>, opentelemetry::v1::nostd::span<long const, 18446744073709551615ul>, opentelemetry::v1::nostd::span<unsigned int const, 18446744073709551615ul>, opentelemetry::v1::nostd::span<double const, 18446744073709551615ul>, opentelemetry::v1::nostd::span<opentelemetry::v1::nostd::string_view const, 18446744073709551615ul>, unsigned long, opentelemetry::v1::nostd::span<unsigned long const, 18446744073709551615ul>, opentelemetry::v1::nostd::span<unsigned char const, 18446744073709551615ul> > const&, std::ostream&)::'lambda'(auto&&), std::variant<bool, int, long, unsigned int, double, char const*, opentelemetry::v1::nostd:
    #8 0x55707b243fa1 in std::invoke_result<auto, std::conditional<is_lvalue_reference_v<std::variant<bool, int, long, unsigned int, double, char const*, opentelemetry::v1::nostd::string_view, opentelemetry::v1::nostd::span<bool const, 18446744073709551615ul>, opentelemetry::v1::nostd::span<int const, 18446744073709551615ul>, opentelemetry::v1::nostd::span<long const, 18446744073709551615ul>, opentelemetry::v1::nostd::span<unsigned int const, 18446744073709551615ul>, opentelemetry::v1::nostd::span<double const, 18446744073709551615ul>, opentelemetry::v1::nostd::span<opentelemetry::v1::nostd::string_view const, 18446744073709551615ul>, unsigned long, opentelemetry::v1::nostd::span<unsigned long const, 18446744073709551615ul>, opentelemetry::v1::nostd::span<unsigned char const, 18446744073709551615ul> > const&>, std::variant_alternative<0ul, std::remove_reference<decltype(__as(declval<std::variant<bool, int, long, unsigned int, double, char const*, opentelemetry::v1::nostd::string_view, opentelemetry::v1::nostd
    #9 0x55707b23f9a8 in opentelemetry::v1::exporter::ostream_common::print_value(std::variant<bool, int, long, unsigned int, double, char const*, opentelemetry::v1::nostd::string_view, opentelemetry::v1::nostd::span<bool const, 18446744073709551615ul>, opentelemetry::v1::nostd::span<int const, 18446744073709551615ul>, opentelemetry::v1::nostd::span<long const, 18446744073709551615ul>, opentelemetry::v1::nostd::span<unsigned int const, 18446744073709551615ul>, opentelemetry::v1::nostd::span<double const, 18446744073709551615ul>, opentelemetry::v1::nostd::span<opentelemetry::v1::nostd::string_view const, 18446744073709551615ul>, unsigned long, opentelemetry::v1::nostd::span<unsigned long const, 18446744073709551615ul>, opentelemetry::v1::nostd::span<unsigned char const, 18446744073709551615ul> > const&, std::ostream&) /home/runner/work/otel-heap-use-after-free/otel-heap-use-after-free/vcpkg/buildtrees/opentelemetry-cpp/src/v1.17.0-b9ea5cab17.clean/exporters/ostream/include/opentelemetry/exporters/ostream/commo
    #10 0x55707b23e5a4 in opentelemetry::v1::exporter::logs::OStreamLogRecordExporter::Export(opentelemetry::v1::nostd::span<std::unique_ptr<opentelemetry::v1::sdk::logs::Recordable, std::default_delete<opentelemetry::v1::sdk::logs::Recordable> >, 18446744073709551615ul> const&) /home/runner/work/otel-heap-use-after-free/otel-heap-use-after-free/vcpkg/buildtrees/opentelemetry-cpp/src/v1.17.0-b9ea5cab17.clean/exporters/ostream/src/log_record_exporter.cc:111:57
    #11 0x55707b276fe7 in opentelemetry::v1::sdk::logs::BatchLogRecordProcessor::Export() /home/runner/work/otel-heap-use-after-free/otel-heap-use-after-free/vcpkg/buildtrees/opentelemetry-cpp/src/v1.17.0-b9ea5cab17.clean/sdk/src/logs/batch_log_record_processor.cc:223:22
    #12 0x55707b276cc6 in opentelemetry::v1::sdk::logs::BatchLogRecordProcessor::DoBackgroundWork() /home/runner/work/otel-heap-use-after-free/otel-heap-use-after-free/vcpkg/buildtrees/opentelemetry-cpp/src/v1.17.0-b9ea5cab17.clean/sdk/src/logs/batch_log_record_processor.cc:178:11
    #13 0x55707b27c429 in void std::__invoke_impl<void, void (opentelemetry::v1::sdk::logs::BatchLogRecordProcessor::*)(), opentelemetry::v1::sdk::logs::BatchLogRecordProcessor*>(std::__invoke_memfun_deref, void (opentelemetry::v1::sdk::logs::BatchLogRecordProcessor::*&&)(), opentelemetry::v1::sdk::logs::BatchLogRecordProcessor*&&) /usr/include/c++/11/bits/invoke.h:74:46
    #14 0x55707b27c37c in std::__invoke_result<void (opentelemetry::v1::sdk::logs::BatchLogRecordProcessor::*)(), opentelemetry::v1::sdk::logs::BatchLogRecordProcessor*>::type std::__invoke<void (opentelemetry::v1::sdk::logs::BatchLogRecordProcessor::*)(), opentelemetry::v1::sdk::logs::BatchLogRecordProcessor*>(void (opentelemetry::v1::sdk::logs::BatchLogRecordProcessor::*&&)(), opentelemetry::v1::sdk::logs::BatchLogRecordProcessor*&&) /usr/include/c++/11/bits/invoke.h:96:40
    #15 0x55707b27c2dc in void std::thread::_Invoker<std::tuple<void (opentelemetry::v1::sdk::logs::BatchLogRecordProcessor::*)(), opentelemetry::v1::sdk::logs::BatchLogRecordProcessor*> >::_M_invoke<0ul, 1ul>(std::_Index_tuple<0ul, 1ul>) /usr/include/c++/11/bits/std_thread.h:259:26
    #16 0x55707b27c22b in std::thread::_Invoker<std::tuple<void (opentelemetry::v1::sdk::logs::BatchLogRecordProcessor::*)(), opentelemetry::v1::sdk::logs::BatchLogRecordProcessor*> >::operator()() /usr/include/c++/11/bits/std_thread.h:266:20
    #17 0x55707b27c1c3 in std::thread::_State_impl<std::thread::_Invoker<std::tuple<void (opentelemetry::v1::sdk::logs::BatchLogRecordProcessor::*)(), opentelemetry::v1::sdk::logs::BatchLogRecordProcessor*> > >::_M_run() /usr/include/c++/11/bits/std_thread.h:211:20
    #18 0x7f82e3adc252  (/lib/x86_64-linux-gnu/libstdc++.so.6+0xdc252) (BuildId: e37fe1a879783838de78cbc8c80621fa685d58a2)
    #19 0x7f82e3694ac2  (/lib/x86_64-linux-gnu/libc.so.6+0x94ac2) (BuildId: 490fef8403240c91833978d494d39e537409b92e)
    #20 0x7f82e372684f  (/lib/x86_64-linux-gnu/libc.so.6+0x12684f) (BuildId: 490fef8403240c91833978d494d39e537409b92e)
0x6030000005c8 is located 24 bytes inside of 30-byte region [0x6030000005b0,0x6030000005ce)
freed by thread T0 here:
    #0 0x55707b22bfbd in operator delete(void*) (/home/runner/work/otel-heap-use-after-free/otel-heap-use-after-free/build/bug-repro+0xeefbd) (BuildId: 3dbd37ecfe9134972fe6a448909a1b651fdd1e62)
    #1 0x7f82e3ac46f2 in std::runtime_error::~runtime_error() (/lib/x86_64-linux-gnu/libstdc++.so.6+0xc46f2) (BuildId: e37fe1a879783838de78cbc8c80621fa685d58a2)
    #2 0x7f82e3629d8f  (/lib/x86_64-linux-gnu/libc.so.6+0x29d8f) (BuildId: 490fef8403240c91833978d494d39e537409b92e)
previously allocated by thread T0 here:
    #0 0x55707b22b75d in operator new(unsigned long) (/home/runner/work/otel-heap-use-after-free/otel-heap-use-after-free/build/bug-repro+0xee75d) (BuildId: 3dbd37ecfe9134972fe6a448909a1b651fdd1e62)
    #1 0x7f82e3af4978 in std::string::_Rep::_S_create(unsigned long, unsigned long, std::allocator<char> const&) (/lib/x86_64-linux-gnu/libstdc++.so.6+0xf4978) (BuildId: e37fe1a879783838de78cbc8c80621fa685d58a2)
Thread T1 created by T0 here:
    #0 0x55707b1d9e0c in __interceptor_pthread_create (/home/runner/work/otel-heap-use-after-free/otel-heap-use-after-free/build/bug-repro+0x9ce0c) (BuildId: 3dbd37ecfe9134972fe6a448909a1b651fdd1e62)
    #1 0x7f82e3adc328 in std::thread::_M_start_thread(std::unique_ptr<std::thread::_State, std::default_delete<std::thread::_State> >, void (*)()) (/lib/x86_64-linux-gnu/libstdc++.so.6+0xdc328) (BuildId: e37fe1a879783838de78cbc8c80621fa685d58a2)
    #2 0x55707b2764d0 in opentelemetry::v1::sdk::logs::BatchLogRecordProcessor::BatchLogRecordProcessor(std::unique_ptr<opentelemetry::v1::sdk::logs::LogRecordExporter, std::default_delete<opentelemetry::v1::sdk::logs::LogRecordExporter> >&&, opentelemetry::v1::sdk::logs::BatchLogRecordProcessorOptions const&) /home/runner/work/otel-heap-use-after-free/otel-heap-use-after-free/vcpkg/buildtrees/opentelemetry-cpp/src/v1.17.0-b9ea5cab17.clean/sdk/src/logs/batch_log_record_processor.cc:58:7
    #3 0x55707b26c501 in opentelemetry::v1::sdk::logs::BatchLogRecordProcessorFactory::Create(std::unique_ptr<opentelemetry::v1::sdk::logs::LogRecordExporter, std::default_delete<opentelemetry::v1::sdk::logs::LogRecordExporter> >&&, opentelemetry::v1::sdk::logs::BatchLogRecordProcessorOptions const&) /home/runner/work/otel-heap-use-after-free/otel-heap-use-after-free/vcpkg/buildtrees/opentelemetry-cpp/src/v1.17.0-b9ea5cab17.clean/sdk/src/logs/batch_log_record_processor_factory.cc:25:63
    #4 0x55707b22e18b in main /home/runner/work/otel-heap-use-after-free/otel-heap-use-after-free/repro.cpp:19:22
    #5 0x7f82e3629d8f  (/lib/x86_64-linux-gnu/libc.so.6+0x29d8f) (BuildId: 490fef8403240c91833978d494d39e537409b92e)
SUMMARY: AddressSanitizer: heap-use-after-free (/home/runner/work/otel-heap-use-after-free/otel-heap-use-after-free/build/bug-repro+0x46c85) (BuildId: 3dbd37ecfe9134972fe6a448909a1b651fdd1e62) in __interceptor_strlen
Shadow bytes around the buggy address:
  0x0c067fff8060: fa fa fd fd fd fa fa fa fd fd fd fa fa fa fd fd
  0x0c067fff8070: fd fa fa fa fd fd fd fa fa fa fd fd fd fa fa fa
  0x0c067fff8080: fd fd fd fa fa fa 00 00 06 fa fa fa 00 00 03 fa
  0x0c067fff8090: fa fa 00 00 07 fa fa fa 00 00 00 00 fa fa 00 00
  0x0c067fff80a0: 00 fa fa fa 00 00 00 fa fa fa fd fd fd fa fa fa
=>0x0c067fff80b0: fd fd fd fa fa fa fd fd fd[fd]fa fa 00 00 00 fa
  0x0c067fff80c0: fa fa fd fd fd fa fa fa fd fd fd fa fa fa fd fd
  0x0c067fff80d0: fd fd fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c067fff80e0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c067fff80f0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c067fff8100: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
Shadow byte legend (one shadow byte represents 8 application bytes):
  Addressable:           00
  Partially addressable: 01 02 03 04 05 06 07 
  Heap left redzone:       fa
  Freed heap region:       fd
  Stack left redzone:      f1
  Stack mid redzone:       f2
  Stack right redzone:     f3
  Stack after return:      f5
  Stack use after scope:   f8
  Global redzone:          f9
  Global init order:       f6
  Poisoned by user:        f7
  Container overflow:      fc
  Array cookie:            ac
  Intra object redzone:    bb
  ASan internal:           fe
  Left alloca redzone:     ca
  Right alloca redzone:    cb
==5853==ABORTING
```

**Additional context**
```cpp
    opentelemetry::sdk::logs::BatchLogRecordProcessorOptions options{2048, std::chrono::milliseconds(5000), 512};
    auto processor = opentelemetry::sdk::logs::BatchLogRecordProcessorFactory::Create(std::move(exporter), options);

//...

    try {
        throw std::runtime_error("error");
    }
    catch (const std::exception &e) {
        auto logger = opentelemetry::logs::Provider::GetLoggerProvider()->GetLogger("example");
        logger->Error(e.what());
    }
```

The bug happens when the `catch` block is left before the `BatchLogRecordProcessor` processes the data.

My understanding is that the log message is stored `ReadWriteLogRecord::body_` which is `opentelemetry::common::AttributeValue`, a `nostd::variant` holding a `const char*` value. When `e` goes out of the scope and gets destructed, the memory pointed to by `e.what()` is also freed. However, because `AttributeValue` does not own the data, it is left with a dangling pointer.

The fix will probably be to replace `AttributeValue` with `OwnedAttributeValue` (this will affect `body_` and `attributes_map_`).

Refs:
* https://github.com/open-telemetry/opentelemetry-cpp/blob/v1.17.0/sdk/include/opentelemetry/sdk/logs/read_write_log_record.h#L191
* https://github.com/open-telemetry/opentelemetry-cpp/blob/v1.17.0/sdk/include/opentelemetry/sdk/logs/read_write_log_record.h#L190

Linked issue comments: user: login: ThomsonTan
created_at: 2024-11-13T19:54:11Z
body: Looks the same issue as https://github.com/open-telemetry/opentelemetry-cpp/issues/2651.

The ostream exporter is for testing purpose and is not meant to be used with BatchLogExporter. It uses `ReadWriteRecordable` which doesn't make a copy the parameter in type `char *`.
author_association: CONTRIBUTOR
user: login: sjinks
created_at: 2024-11-14T14:13:41Z
body: Confirmed, it works fine with `OtlpHttpLogRecordExporter`.

author_association: CONTRIBUTOR
user: login: marcalff
created_at: 2024-11-14T14:25:48Z
body: Thanks for the confirmation.

I forgot to say, impressive steps to reproduce, with a dedicated git repository and all.

Feel free to close this report, we will keep #2651 opened to fix the ostream exporter.

author_association: MEMBER

Diff:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 127391fb58..741642377b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -61,6 +61,28 @@ Increment the:
 * [SEMANTIC CONVENTIONS] Upgrade to semantic conventions 1.34.0
   [#3428](https://github.com/open-telemetry/opentelemetry-cpp/pull/3428)
 
+* [EXPORTER] ostream log exporter, fixed memory ownership issues
+  [#3417](https://github.com/open-telemetry/opentelemetry-cpp/pull/3417)
+
+Important changes:
+
+* [EXPORTER] ostream log exporter, fixed memory ownership issues
+  [#3417](https://github.com/open-telemetry/opentelemetry-cpp/pull/3417)
+
+  * In the SDK, the following classes implementation has changed:
+
+    * opentelemetry::sdk::logs::ReadableLogRecord
+    * opentelemetry::sdk::logs::ReadWriteLogRecord
+
+  * An application implementing a custom log record exporter,
+    that reuses these classes from the opentelemetry-cpp SDK,
+    will need code adjustments, in particular for methods:
+
+    * GetBody()
+    * GetAttributes()
+
+  * Applications not using these SDK classes directly are not affected.
+
 ## [1.20 2025-04-01]
 
 * [BUILD] Update opentelemetry-proto version
diff --git a/sdk/include/opentelemetry/sdk/logs/read_write_log_record.h b/sdk/include/opentelemetry/sdk/logs/read_write_log_record.h
index 9831eaa1c9..af28273c49 100644
--- a/sdk/include/opentelemetry/sdk/logs/read_write_log_record.h
+++ b/sdk/include/opentelemetry/sdk/logs/read_write_log_record.h
@@ -82,7 +82,7 @@ class ReadWriteLogRecord final : public ReadableLogRecord
    * Get body field of this log.
    * @return the body field for this log.
    */
-  const opentelemetry::common::AttributeValue &GetBody() const noexcept override;
+  const opentelemetry::sdk::common::OwnedAttributeValue &GetBody() const noexcept override;
 
   /**
    * Set the Event Id object
@@ -151,8 +151,8 @@ class ReadWriteLogRecord final : public ReadableLogRecord
    * Get attributes of this log.
    * @return the body field of this log
    */
-  const std::unordered_map<std::string, opentelemetry::common::AttributeValue> &GetAttributes()
-      const noexcept override;
+  const std::unordered_map<std::string, opentelemetry::sdk::common::OwnedAttributeValue> &
+  GetAttributes() const noexcept override;
 
   /**
    * Get resource of this log
@@ -187,8 +187,8 @@ class ReadWriteLogRecord final : public ReadableLogRecord
   const opentelemetry::sdk::resource::Resource *resource_;
   const opentelemetry::sdk::instrumentationscope::InstrumentationScope *instrumentation_scope_;
 
-  std::unordered_map<std::string, opentelemetry::common::AttributeValue> attributes_map_;
-  opentelemetry::common::AttributeValue body_;
+  std::unordered_map<std::string, opentelemetry::sdk::common::OwnedAttributeValue> attributes_map_;
+  opentelemetry::sdk::common::OwnedAttributeValue body_;
   opentelemetry::common::SystemTimestamp timestamp_;
   opentelemetry::common::SystemTimestamp observed_timestamp_;
 
diff --git a/sdk/include/opentelemetry/sdk/logs/readable_log_record.h b/sdk/include/opentelemetry/sdk/logs/readable_log_record.h
index 2222def9d2..3245717ce2 100644
--- a/sdk/include/opentelemetry/sdk/logs/readable_log_record.h
+++ b/sdk/include/opentelemetry/sdk/logs/readable_log_record.h
@@ -75,7 +75,7 @@ class ReadableLogRecord : public Recordable
    * Get body field of this log.
    * @return the body field for this log.
    */
-  virtual const opentelemetry::common::AttributeValue &GetBody() const noexcept = 0;
+  virtual const opentelemetry::sdk::common::OwnedAttributeValue &GetBody() const noexcept = 0;
 
   /**
    * Get the Event id.
@@ -111,7 +111,7 @@ class ReadableLogRecord : public Recordable
    * Get attributes of this log.
    * @return the body field of this log
    */
-  virtual const std::unordered_map<std::string, opentelemetry::common::AttributeValue> &
+  virtual const std::unordered_map<std::string, opentelemetry::sdk::common::OwnedAttributeValue> &
   GetAttributes() const noexcept = 0;
 
   /**
diff --git a/sdk/src/logs/read_write_log_record.cc b/sdk/src/logs/read_write_log_record.cc
index 84a4bed2f4..7743fbd5db 100644
--- a/sdk/src/logs/read_write_log_record.cc
+++ b/sdk/src/logs/read_write_log_record.cc
@@ -12,6 +12,7 @@
 #include "opentelemetry/logs/severity.h"
 #include "opentelemetry/nostd/string_view.h"
 #include "opentelemetry/nostd/variant.h"
+#include "opentelemetry/sdk/common/attribute_utils.h"
 #include "opentelemetry/sdk/instrumentationscope/instrumentation_scope.h"
 #include "opentelemetry/sdk/logs/read_write_log_record.h"
 #include "opentelemetry/sdk/resource/resource.h"
@@ -30,7 +31,7 @@ ReadWriteLogRecord::ReadWriteLogRecord()
     : severity_(opentelemetry::logs::Severity::kInvalid),
       resource_(nullptr),
       instrumentation_scope_(nullptr),
-      body_(nostd::string_view()),
+      body_(std::string()),
       observed_timestamp_(std::chrono::system_clock::now()),
       event_id_(0),
       event_name_("")
@@ -71,10 +72,11 @@ opentelemetry::logs::Severity ReadWriteLogRecord::GetSeverity() const noexcept
 
 void ReadWriteLogRecord::SetBody(const opentelemetry::common::AttributeValue &message) noexcept
 {
-  body_ = message;
+  opentelemetry::sdk::common::AttributeConverter converter;
+  body_ = nostd::visit(converter, message);
 }
 
-const opentelemetry::common::AttributeValue &ReadWriteLogRecord::GetBody() const noexcept
+const opentelemetry::sdk::common::OwnedAttributeValue &ReadWriteLogRecord::GetBody() const noexcept
 {
   return body_;
 }
@@ -161,10 +163,12 @@ const opentelemetry::trace::TraceFlags &ReadWriteLogRecord::GetTraceFlags() cons
 void ReadWriteLogRecord::SetAttribute(nostd::string_view key,
                                       const opentelemetry::common::AttributeValue &value) noexcept
 {
-  attributes_map_[static_cast<std::string>(key)] = value;
+  std::string safe_key(key);
+  opentelemetry::sdk::common::AttributeConverter converter;
+  attributes_map_[safe_key] = nostd::visit(converter, value);
 }
 
-const std::unordered_map<std::string, opentelemetry::common::AttributeValue> &
+const std::unordered_map<std::string, opentelemetry::sdk::common::OwnedAttributeValue> &
 ReadWriteLogRecord::GetAttributes() const noexcept
 {
   return attributes_map_;
diff --git a/sdk/test/logs/log_record_test.cc b/sdk/test/logs/log_record_test.cc
index 1a9ac328cf..48d0754060 100644
--- a/sdk/test/logs/log_record_test.cc
+++ b/sdk/test/logs/log_record_test.cc
@@ -9,8 +9,8 @@
 #include <string>
 #include <unordered_map>
 #include <utility>
+#include <vector>
 
-#include "opentelemetry/common/attribute_value.h"
 #include "opentelemetry/common/key_value_iterable.h"
 #include "opentelemetry/common/timestamp.h"
 #include "opentelemetry/logs/log_record.h"
@@ -22,6 +22,7 @@
 #include "opentelemetry/nostd/string_view.h"
 #include "opentelemetry/nostd/unique_ptr.h"
 #include "opentelemetry/nostd/variant.h"
+#include "opentelemetry/sdk/common/attribute_utils.h"
 #include "opentelemetry/sdk/logs/read_write_log_record.h"
 #include "opentelemetry/sdk/resource/resource.h"
 #include "opentelemetry/trace/span_id.h"
@@ -72,13 +73,9 @@ TEST(ReadWriteLogRecord, SetAndGet)
 
   // Test that all fields match what was set
   ASSERT_EQ(record.GetSeverity(), logs_api::Severity::kInvalid);
-  if (nostd::holds_alternative<const char *>(record.GetBody()))
+  if (nostd::holds_alternative<std::string>(record.GetBody()))
   {
-    ASSERT_EQ(std::string(nostd::get<const char *>(record.GetBody())), "Message");
-  }
-  else if (nostd::holds_alternative<nostd::string_view>(record.GetBody()))
-  {
-    ASSERT_TRUE(nostd::get<nostd::string_view>(record.GetBody()) == "Message");
+    ASSERT_EQ(std::string(nostd::get<std::string>(record.GetBody())), "Message");
   }
   ASSERT_TRUE(nostd::get<bool>(record.GetResource().GetAttributes().at("res1")));
   ASSERT_EQ(nostd::get<int64_t>(record.GetAttributes().at("attr1")), 314159);
@@ -109,13 +106,13 @@ class TestBodyLogger : public opentelemetry::logs::Logger
     }
   }
 
-  const opentelemetry::common::AttributeValue &GetLastLogRecord() const noexcept
+  const opentelemetry::sdk::common::OwnedAttributeValue &GetLastLogRecord() const noexcept
   {
     return last_body_;
   }
 
 private:
-  opentelemetry::common::AttributeValue last_body_;
+  opentelemetry::sdk::common::OwnedAttributeValue last_body_;
 };
 
 // Define a basic LoggerProvider class that returns an instance of the logger class defined above
@@ -173,28 +170,19 @@ TEST(LogBody, BodyConversation)
 
   real_logger->EmitLogRecord(opentelemetry::logs::Severity::kInfo, "128");
   ASSERT_TRUE(
-      opentelemetry::nostd::holds_alternative<const char *>(real_logger->GetLastLogRecord()) ||
-      opentelemetry::nostd::holds_alternative<nostd::string_view>(real_logger->GetLastLogRecord()));
-  if (opentelemetry::nostd::holds_alternative<const char *>(real_logger->GetLastLogRecord()))
-  {
-    ASSERT_EQ(nostd::string_view{"128"},
-              opentelemetry::nostd::get<const char *>(real_logger->GetLastLogRecord()));
-  }
-  else
-  {
-    ASSERT_EQ(nostd::string_view{"128"},
-              opentelemetry::nostd::get<nostd::string_view>(real_logger->GetLastLogRecord()));
-  }
+      opentelemetry::nostd::holds_alternative<std::string>(real_logger->GetLastLogRecord()));
+  ASSERT_EQ(nostd::string_view{"128"},
+            opentelemetry::nostd::get<std::string>(real_logger->GetLastLogRecord()));
 
   {
     bool data[]                       = {true, false, true};
     nostd::span<const bool> data_span = data;
     real_logger->EmitLogRecord(opentelemetry::logs::Severity::kInfo, data_span);
-    ASSERT_TRUE(opentelemetry::nostd::holds_alternative<nostd::span<const bool>>(
+    ASSERT_TRUE(opentelemetry::nostd::holds_alternative<std::vector<bool>>(
         real_logger->GetLastLogRecord()));
 
-    nostd::span<const bool> output =
-        opentelemetry::nostd::get<nostd::span<const bool>>(real_logger->GetLastLogRecord());
+    std::vector<bool> output =
+        opentelemetry::nostd::get<std::vector<bool>>(real_logger->GetLastLogRecord());
 
     ASSERT_EQ(data_span.size(), output.size());
 
@@ -208,11 +196,11 @@ TEST(LogBody, BodyConversation)
     int32_t data[]                       = {221, 222, 223};
     nostd::span<const int32_t> data_span = data;
     real_logger->EmitLogRecord(opentelemetry::logs::Severity::kInfo, data_span);
-    ASSERT_TRUE(opentelemetry::nostd::holds_alternative<nostd::span<const int32_t>>(
+    ASSERT_TRUE(opentelemetry::nostd::holds_alternative<std::vector<int32_t>>(
         real_logger->GetLastLogRecord()));
 
     nostd::span<const int32_t> output =
-        opentelemetry::nostd::get<nostd::span<const int32_t>>(real_logger->GetLastLogRecord());
+        opentelemetry::nostd::get<std::vector<int32_t>>(real_logger->GetLastLogRecord());
 
     ASSERT_EQ(data_span.size(), output.size());
 
@@ -226,11 +214,11 @@ TEST(LogBody, BodyConversation)
     uint32_t data[]                       = {231, 232, 233};
     nostd::span<const uint32_t> data_span = data;
     real_logger->EmitLogRecord(opentelemetry::logs::Severity::kInfo, data_span);
-    ASSERT_TRUE(opentelemetry::nostd::holds_alternative<nostd::span<const uint32_t>>(
+    ASSERT_TRUE(opentelemetry::nostd::holds_alternative<std::vector<uint32_t>>(
         real_logger->GetLastLogRecord()));
 
-    nostd::span<const uint32_t> output =
-        opentelemetry::nostd::get<nostd::span<const uint32_t>>(real_logger->GetLastLogRecord());
+    std::vector<uint32_t> output =
+        opentelemetry::nostd::get<std::vector<uint32_t>>(real_logger->GetLastLogRecord());
 
     ASSERT_EQ(data_span.size(), output.size());
 
@@ -244,11 +232,11 @@ TEST(LogBody, BodyConversation)
     int64_t data[]                       = {241, 242, 243};
     nostd::span<const int64_t> data_span = data;
     real_logger->EmitLogRecord(opentelemetry::logs::Severity::kInfo, data_span);
-    ASSERT_TRUE(opentelemetry::nostd::holds_alternative<nostd::span<const int64_t>>(
+    ASSERT_TRUE(opentelemetry::nostd::holds_alternative<std::vector<int64_t>>(
         real_logger->GetLastLogRecord()));
 
-    nostd::span<const int64_t> output =
-        opentelemetry::nostd::get<nostd::span<const int64_t>>(real_logger->GetLastLogRecord());
+    std::vector<int64_t> output =
+        opentelemetry::nostd::get<std::vector<int64_t>>(real_logger->GetLastLogRecord());
 
     ASSERT_EQ(data_span.size(), output.size());
 
@@ -262,11 +250,11 @@ TEST(LogBody, BodyConversation)
     uint64_t data[]                       = {251, 252, 253};
     nostd::span<const uint64_t> data_span = data;
     real_logger->EmitLogRecord(opentelemetry::logs::Severity::kInfo, data_span);
-    ASSERT_TRUE(opentelemetry::nostd::holds_alternative<nostd::span<const uint64_t>>(
+    ASSERT_TRUE(opentelemetry::nostd::holds_alternative<std::vector<uint64_t>>(
         real_logger->GetLastLogRecord()));
 
-    nostd::span<const uint64_t> output =
-        opentelemetry::nostd::get<nostd::span<const uint64_t>>(real_logger->GetLastLogRecord());
+    std::vector<uint64_t> output =
+        opentelemetry::nostd::get<std::vector<uint64_t>>(real_logger->GetLastLogRecord());
 
     ASSERT_EQ(data_span.size(), output.size());
 
@@ -280,11 +268,11 @@ TEST(LogBody, BodyConversation)
     uint8_t data[]                       = {161, 162, 163};
     nostd::span<const uint8_t> data_span = data;
     real_logger->EmitLogRecord(opentelemetry::logs::Severity::kInfo, data_span);
-    ASSERT_TRUE(opentelemetry::nostd::holds_alternative<nostd::span<const uint8_t>>(
+    ASSERT_TRUE(opentelemetry::nostd::holds_alternative<std::vector<uint8_t>>(
         real_logger->GetLastLogRecord()));
 
-    nostd::span<const uint8_t> output =
-        opentelemetry::nostd::get<nostd::span<const uint8_t>>(real_logger->GetLastLogRecord());
+    std::vector<uint8_t> output =
+        opentelemetry::nostd::get<std::vector<uint8_t>>(real_logger->GetLastLogRecord());
 
     ASSERT_EQ(data_span.size(), output.size());
 
@@ -298,11 +286,11 @@ TEST(LogBody, BodyConversation)
     double data[]                       = {271.0, 272.0, 273.0};
     nostd::span<const double> data_span = data;
     real_logger->EmitLogRecord(opentelemetry::logs::Severity::kInfo, data_span);
-    ASSERT_TRUE(opentelemetry::nostd::holds_alternative<nostd::span<const double>>(
+    ASSERT_TRUE(opentelemetry::nostd::holds_alternative<std::vector<double>>(
         real_logger->GetLastLogRecord()));
 
-    nostd::span<const double> output =
-        opentelemetry::nostd::get<nostd::span<const double>>(real_logger->GetLastLogRecord());
+    std::vector<double> output =
+        opentelemetry::nostd::get<std::vector<double>>(real_logger->GetLastLogRecord());
 
     ASSERT_EQ(data_span.size(), output.size());
 
@@ -317,12 +305,11 @@ TEST(LogBody, BodyConversation)
     nostd::string_view data[] = {data_origin[0], data_origin[1], data_origin[2]};
     nostd::span<const nostd::string_view> data_span = data;
     real_logger->EmitLogRecord(opentelemetry::logs::Severity::kInfo, data_span);
-    ASSERT_TRUE(opentelemetry::nostd::holds_alternative<nostd::span<const nostd::string_view>>(
+    ASSERT_TRUE(opentelemetry::nostd::holds_alternative<std::vector<std::string>>(
         real_logger->GetLastLogRecord()));
 
-    nostd::span<const nostd::string_view> output =
-        opentelemetry::nostd::get<nostd::span<const nostd::string_view>>(
-            real_logger->GetLastLogRecord());
+    std::vector<std::string> output =
+        opentelemetry::nostd::get<std::vector<std::string>>(real_logger->GetLastLogRecord());
 
     ASSERT_EQ(data_span.size(), output.size());
 


Reviews:
user: login: dbarker
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-15T22:25:08Z
user: login: dbarker
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-15T22:39:04Z
user: login: lalitb
body: Nicely done, thanks. I would ideally prefer to get rid of `ReadableLogRecord`, and just have `ReadWriteLogRecord` (probably renamed to `LogData`), and so have the API consistent with `SpanData`, but that's not related to the issue :)
state: APPROVED
author_association: MEMBER
submitted_at: 2025-05-15T23:20:47Z
user: login: marcalff
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-19T15:55:08Z
user: login: owent
state: APPROVED
author_association: MEMBER
submitted_at: 2025-05-21T07:39:55Z

Review comments:
diff_hunk: @@ -30,7 +31,7 @@ ReadWriteLogRecord::ReadWriteLogRecord()
     : severity_(opentelemetry::logs::Severity::kInvalid),
       resource_(nullptr),
       instrumentation_scope_(nullptr),
-      body_(nostd::string_view()),
+      body_(std::string()),
path: sdk/src/logs/read_write_log_record.cc
user: login: dbarker
body: Should we not initialize the body since it is an "any" type and optional? 
created_at: 2025-05-15T22:25:08Z
author_association: MEMBER
diff_hunk: @@ -30,7 +31,7 @@ ReadWriteLogRecord::ReadWriteLogRecord()
     : severity_(opentelemetry::logs::Severity::kInvalid),
       resource_(nullptr),
       instrumentation_scope_(nullptr),
-      body_(nostd::string_view()),
+      body_(std::string()),
path: sdk/src/logs/read_write_log_record.cc
user: login: dbarker
body: Looks like we will have to initialize the variant to some value (no std::monostate like type available to signal empty). Perhaps an empty string is okay.
created_at: 2025-05-15T22:39:03Z
author_association: MEMBER
diff_hunk: @@ -30,7 +31,7 @@ ReadWriteLogRecord::ReadWriteLogRecord()
     : severity_(opentelemetry::logs::Severity::kInvalid),
       resource_(nullptr),
       instrumentation_scope_(nullptr),
-      body_(nostd::string_view()),
+      body_(std::string()),
path: sdk/src/logs/read_write_log_record.cc
user: login: marcalff
body: Initializing body to an empty string by default, which is the same as previous behavior.
created_at: 2025-05-19T15:55:08Z
author_association: MEMBER

PR comments:
user: login: netlify[bot]
created_at: 2025-05-15T19:31:15Z
body: ### <span aria-hidden="true">✅</span> Deploy Preview for *opentelemetry-cpp-api-docs* canceled.


|  Name | Link |
|:-:|------------------------|
|<span aria-hidden="true">🔨</span> Latest commit | 9303dee735922d00e45ad3ceaa95ebe123520d06 |
|<span aria-hidden="true">🔍</span> Latest deploy log | https://app.netlify.com/projects/opentelemetry-cpp-api-docs/deploys/682d9d3ab897e40008da7177 |
author_association: NONE
user: login: codecov[bot]
created_at: 2025-05-15T19:45:36Z
body: ## [Codecov](https://app.codecov.io/gh/open-telemetry/opentelemetry-cpp/pull/3417?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-telemetry) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Project coverage is 89.86%. Comparing base [(`f7babf1`)](https://app.codecov.io/gh/open-telemetry/opentelemetry-cpp/commit/f7babf1bca68ec6a2c021eab8d1e8b13570562c6?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-telemetry) to head [(`9303dee`)](https://app.codecov.io/gh/open-telemetry/opentelemetry-cpp/commit/9303dee735922d00e45ad3ceaa95ebe123520d06?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-telemetry).
> Report is 1 commits behind head on main.

<details><summary>Additional details and impacted files</summary>


[![Impacted file tree graph](https://app.codecov.io/gh/open-telemetry/opentelemetry-cpp/pull/3417/graphs/tree.svg?width=650&height=150&src=pr&token=FJESTYQ2AD&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-telemetry)](https://app.codecov.io/gh/open-telemetry/opentelemetry-cpp/pull/3417?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-telemetry)

```diff
@@            Coverage Diff             @@
##             main    #3417      +/-   ##
==========================================
- Coverage   90.06%   89.86%   -0.19%     
==========================================
  Files         212      212              
  Lines        6937     6941       +4     
==========================================
- Hits         6247     6237      -10     
- Misses        690      704      +14     
```

| [Files with missing lines](https://app.codecov.io/gh/open-telemetry/opentelemetry-cpp/pull/3417?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-telemetry) | Coverage Δ | |
|---|---|---|
| [sdk/src/logs/read\_write\_log\_record.cc](https://app.codecov.io/gh/open-telemetry/opentelemetry-cpp/pull/3417?src=pr&el=tree&filepath=sdk%2Fsrc%2Flogs%2Fread_write_log_record.cc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-telemetry#diff-c2RrL3NyYy9sb2dzL3JlYWRfd3JpdGVfbG9nX3JlY29yZC5jYw==) | `96.43% <100.00%> (+0.05%)` | :arrow_up: |

... and [7 files with indirect coverage changes](https://app.codecov.io/gh/open-telemetry/opentelemetry-cpp/pull/3417/indirect-changes?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-telemetry)

</details>
<details><summary> :rocket: New features to boost your workflow: </summary>

- :snowflake: [Test Analytics](https://docs.codecov.com/docs/test-analytics): Detect flaky tests, report on failures, and find test suite problems.
</details>
author_association: NONE
user: login: ThomsonTan
created_at: 2025-05-15T23:31:27Z
body: Is this a breaking change if `ReadWriteLogRecord` is used by the user? Mention this in the changelog if so.
author_association: CONTRIBUTOR
user: login: ThomsonTan
created_at: 2025-05-15T23:36:14Z
body: > Nicely done, thanks. I would ideally prefer to get rid of `ReadableLogRecord`, and just have `ReadWriteLogRecord` (probably renamed to `LogData`), and so have the API consistent with `SpanData`, but that's not related to the issue :)

Seems https://github.com/open-telemetry/opentelemetry-cpp/pull/3147 includes the renaming to `LogRecordData`.
author_association: CONTRIBUTOR
user: login: marcalff
created_at: 2025-05-18T15:01:27Z
body: Added an important changes section in the CHANGELOG, to address @ThomsonTan comments.

Waiting for @owent approval, as an alternative fix to PR #3147.
author_association: MEMBER
user: login: marcalff
created_at: 2025-05-18T15:36:01Z
body: Additional testing:

* Manually ported the test case patch from #2651 to a plain (unchanged) main repo, executed the tests, inspected the output: issue reproduced using the main branch
* Manually ported the same with this fix, executed the tests, inspected the output: issue is fixed.
author_association: MEMBER
user: login: owent
created_at: 2025-05-19T07:36:05Z
body: > Added an important changes section in the CHANGELOG, to address @ThomsonTan comments.
> 
> Waiting for @owent approval, as an alternative fix to PR #3147.

This changes API, and may make users can not compile. Personally, I prefer to use #3147, which only change the APIs in v2 version.
author_association: MEMBER
user: login: lalitb
created_at: 2025-05-19T14:52:32Z
body: > This changes API, and may make users can not compile. Personally, I prefer to use https://github.com/open-telemetry/opentelemetry-cpp/pull/3147, which only change the APIs in v2 version.

@owent - this is only SDK breaking change, so do we still need to have v2 ?
author_association: MEMBER
user: login: marcalff
created_at: 2025-05-19T15:46:19Z
body: > > This changes API, and may make users can not compile. Personally, I prefer to use #3147, which only change the APIs in v2 version.
> 
> @owent - this is only SDK breaking change, so do we still need to have v2 ?

See related discussion https://github.com/open-telemetry/opentelemetry-cpp/pull/3147#discussion_r2095741789
author_association: MEMBER
user: login: owent
created_at: 2025-05-20T02:58:13Z
body: > > This changes API, and may make users can not compile. Personally, I prefer to use #3147, which only change the APIs in v2 version.
> 
> @owent - this is only SDK breaking change, so do we still need to have v2 ?

Sorry, I shouldn't use the ABI version macro. My goal is to allow users time to migrate their code when upgrading otel-cpp, rather than causing immediate compilation errors.
author_association: MEMBER
user: login: lalitb
created_at: 2025-05-20T06:07:37Z
body: > Sorry, I shouldn't use the ABI version macro. My goal is to allow users time to migrate their code when upgrading otel-cpp, rather than causing immediate compilation errors.

Thanks for clarifying. As I understand it, compile-time errors would only occur in the following cases:
- Custom exporters using ReadWriteLogRecord or ReadableLogRecord.
- Custom processors that rely on ReadWriteLogRecord or ReadableLogRecord for record filtering or redaction.

None of the core processors and exporters are affected by this change. Given that this is likely a small subset of users, I think introducing these breaking changes should be fine.  The changelog should clearly specify the migration steps for affected users in this scenario.
author_association: MEMBER
user: login: owent
created_at: 2025-05-20T06:29:58Z
body: > > Sorry, I shouldn't use the ABI version macro. My goal is to allow users time to migrate their code when upgrading otel-cpp, rather than causing immediate compilation errors.
> 
> Thanks for clarifying. As I understand it, compile-time errors would only occur in the following cases:
> 
> * Custom exporters using ReadWriteLogRecord or ReadableLogRecord.
> * Custom processors that rely on ReadWriteLogRecord or ReadableLogRecord for record filtering or redaction.
> 
> None of the core processors and exporters are affected by this change. Given that this is likely a small subset of users, I think introducing these breaking changes should be fine. The changelog should clearly specify the migration steps for affected users in this scenario.

Yes, but in #3147 , a new record class `LogRecordData` is added, which is used by ostream exporter. This class is added only to make the similar name as `trace::SpanData`. But it's almost the same as `ReadWriteLogRecord`, the changes of `ReadWriteLogRecord` just want to keed ABI compatibility to resolve the comment in <https://github.com/open-telemetry/opentelemetry-cpp/pull/3147#issuecomment-2480527602> .
author_association: MEMBER
user: login: marcalff
created_at: 2025-05-20T21:31:28Z
body: > > > Sorry, I shouldn't use the ABI version macro. My goal is to allow users time to migrate their code when upgrading otel-cpp, rather than causing immediate compilation errors.
> > 
> > 
> > Thanks for clarifying. As I understand it, compile-time errors would only occur in the following cases:
> > 
> > * Custom exporters using ReadWriteLogRecord or ReadableLogRecord.
> > * Custom processors that rely on ReadWriteLogRecord or ReadableLogRecord for record filtering or redaction.
> > 
> > None of the core processors and exporters are affected by this change. Given that this is likely a small subset of users, I think introducing these breaking changes should be fine. The changelog should clearly specify the migration steps for affected users in this scenario.
> 

The changelog already gives details about this, in the important changes section.

I agree that the potential build break will affect only a small subset of users, which is why this should be fine.

Also, note that people affected by the build break will be users who deliberately reuse some SDK classes in their own implementation: they should be fully aware of risks when choosing to do so.

> Yes, but in #3147 , a new record class `LogRecordData` is added, which is used by ostream exporter. This class is added only to make the similar name as `trace::SpanData`. But it's almost the same as `ReadWriteLogRecord`, the changes of `ReadWriteLogRecord` just want to keed ABI compatibility to resolve the comment in [#3147 (comment)](https://github.com/open-telemetry/opentelemetry-cpp/pull/3147#issuecomment-2480527602) .

The comment about ABI changes in the SDK is technically correct, as `ReadWriteLogRecord` indeed changed (in the initial fix from #3147).

However, I think this comment should not be blocking: opentelemetry-cpp does not guarantee binary stability for the SDK implementation, so if an SDK class changes, it changes.

The rationale for creating yet another class like LogRecordData is to preserve a binary compatible `ReadWriteLogRecord`,
but if there is no need to preserve binary compatibility (there is none), everything can be greatly simplified.

This entire fix (the present PR) is +70 -57 lines, and this includes the documentation in the changelog, and unit tests adjustments.

Please, let's not blow the fix scope out of proportions, when it can be done simply.

author_association: MEMBER
user: login: owent
created_at: 2025-05-21T07:39:44Z
body: > > > > Sorry, I shouldn't use the ABI version macro. My goal is to allow users time to migrate their code when upgrading otel-cpp, rather than causing immediate compilation errors.
> > > 
> > > 
> > > Thanks for clarifying. As I understand it, compile-time errors would only occur in the following cases:
> > > 
> > > * Custom exporters using ReadWriteLogRecord or ReadableLogRecord.
> > > * Custom processors that rely on ReadWriteLogRecord or ReadableLogRecord for record filtering or redaction.
> > > 
> > > None of the core processors and exporters are affected by this change. Given that this is likely a small subset of users, I think introducing these breaking changes should be fine. The changelog should clearly specify the migration steps for affected users in this scenario.
> 
> The changelog already gives details about this, in the important changes section.
> 
> I agree that the potential build break will affect only a small subset of users, which is why this should be fine.
> 
> Also, note that people affected by the build break will be users who deliberately reuse some SDK classes in their own implementation: they should be fully aware of risks when choosing to do so.
> 
> > Yes, but in #3147 , a new record class `LogRecordData` is added, which is used by ostream exporter. This class is added only to make the similar name as `trace::SpanData`. But it's almost the same as `ReadWriteLogRecord`, the changes of `ReadWriteLogRecord` just want to keed ABI compatibility to resolve the comment in [#3147 (comment)](https://github.com/open-telemetry/opentelemetry-cpp/pull/3147#issuecomment-2480527602) .
> 
> The comment about ABI changes in the SDK is technically correct, as `ReadWriteLogRecord` indeed changed (in the initial fix from #3147).
> 
> However, I think this comment should not be blocking: opentelemetry-cpp does not guarantee binary stability for the SDK implementation, so if an SDK class changes, it changes.
> 
> The rationale for creating yet another class like LogRecordData is to preserve a binary compatible `ReadWriteLogRecord`, but if there is no need to preserve binary compatibility (there is none), everything can be greatly simplified.
> 
> This entire fix (the present PR) is +70 -57 lines, and this includes the documentation in the changelog, and unit tests adjustments.
> 
> Please, let's not blow the fix scope out of proportions, when it can be done simply.

OK, I closed #3147, let's use this PR.
author_association: MEMBER